Fullscreen for Mac OS X: Solution

hi folks,

here is the fix to get a fullscreen context under mac os x!
this is an almost clean solution. the only dirty part is that i don’t know for sure if you really want to be fullscreen. the decision is made on the size of the window that wants the context:
if the window is exactly the size of the current screens resolution, a fullscreen context is generated, otherwise a view-based context is built.

all changes i did begin with a commented ‘atze:’

void* createContext(void* shareContext, void* view,
int redBits,
int greenBits,
int blueBits,
int alphaBits,
int depthBits,
int stencilBits,
int accumRedBits,
int accumGreenBits,
int accumBlueBits,
int accumAlphaBits,
int sampleBuffers,
int numSamples)
{
if (gAutoreleasePool == nil) // atze: create a pool as early as possible
gAutoreleasePool = [[NSAutoreleasePool alloc] init];

  int colorSize = redBits + greenBits + blueBits;
  int accumSize = accumRedBits + accumGreenBits + accumBlueBits;
  
  NSOpenGLContext *nsChareCtx = (NSOpenGLContext*)shareContext;
  NSView *nsView = (NSView*)view;
  
  // atze: if the views window covers the entire screen make the gl-context fullscreen
  NSWindow     *aWindow        = [nsView window];
  NSRect       aWindowFrame    = [aWindow frame];
  NSScreen     *aScreen        = [aWindow screen];
  NSDictionary *aDictionary    = [aScreen deviceDescription];
  NSNumber       *aDisplayNumber = [aDictionary objectForKey:@"NSScreenNumber"];
  CGDirectDisplayID aDisplay = (CGDirectDisplayID)[aDisplayNumber longValue]; // this is kind of ugly

  // atze: we can not use NSScreens size, because it's the hardware resolution
  // atze: the GDDisplayPixelsXX returns the soft-resolution
  size_t aScreenWidth  = CGDisplayPixelsWide(aDisplay);
  size_t aScreenHeight = CGDisplayPixelsHigh(aDisplay);

// atze: does the window cover the entire screen?
  if(aScreenWidth == (size_t)NSWidth(aWindowFrame) && aScreenHeight == (size_t)NSHeight(aWindowFrame))
  {
        nsView = nil; // atze: ignore the view, go fullscreen!
  }

  /* atze: what are these checks for? just create a context, regardless of that views state.
  if (nsView != nil)
  {
        NSRect frame = [nsView frame];
        if ((frame.size.width == 0) || (frame.size.height == 0))
        {
              fprintf(stderr, "Error: view width or height == 0at \"%s:%s:%d\"\n", __FILE__, __FUNCTION__, __LINE__);
              // the view is not ready yet
              return NULL;
        }
        else if ([nsView lockFocusIfCanDraw] == NO)
        {
              fprintf(stderr, "Error: view not ready, cannot lock focus at \"%s:%s:%d\"\n", __FILE__, __FUNCTION__, __LINE__);
              // the view is not ready yet
              return NULL;
        }
        [nsView unlockFocus];            
  }
  */

  NSOpenGLPixelFormatAttribute attribs[] =
  {
        ((nsView == nil) ? NSOpenGLPFAFullScreen : NSOpenGLPFAWindow), // atze: allow a fullscreen context
        NSOpenGLPFANoRecovery, YES,
        NSOpenGLPFAAccelerated, YES,
        NSOpenGLPFADoubleBuffer, YES,
        NSOpenGLPFAColorSize, colorSize,
        NSOpenGLPFAAlphaSize, alphaBits,
        NSOpenGLPFADepthSize, depthBits,
        NSOpenGLPFAStencilSize, stencilBits,
        NSOpenGLPFAAccumSize, accumSize,
        NSOpenGLPFASampleBuffers, sampleBuffers,
        NSOpenGLPFASamples, numSamples,
        NSOpenGLPFAScreenMask, (NSOpenGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(aDisplay), // atze: choosen screen
        (NSOpenGLPixelFormatAttribute)0
  };

  NSOpenGLPixelFormat* fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
  NSOpenGLContext* nsContext = [[NSOpenGLContext alloc] initWithFormat:fmt shareContext:nsChareCtx];
  [fmt release];
     
  [nsContext makeCurrentContext];
  if(nsView != nil)
        [nsContext setView:nsView];
  else  // atze: here comes the fullscreen context
        [nsContext setFullScreen];
     
  // [nsContext retain]; atze: nsContext is retained already
     
  //fprintf(stderr, "      nsContext=%p\n", nsContext);
  return nsContext;

}

I would suggest you create a webstart version of the code above to get feedback on whether or not your code fix works (crosses fingers, actually, crosses everything…hopefully this will finally work!).

Regards,

Ribot.

sorry, i have no idea what webstart is.
but here is an example that works with my modification and brings the Gears-demo into fullscreen.

first: a method to select a DisplayMode to your liking.
second: a modification to main() to use fullscreen instead of a window.

please excuse that i did not built in a switch to toggle between windowed and fullscreen - that is left as en exercise to you :slight_smile:

  static public DisplayMode getBestModeForParameters(GraphicsDevice aDevice, int aDepth, int aWidth, int aHeight)
  {
        DisplayMode aModeArray[] = aDevice.getDisplayModes();
        DisplayMode aBestMode = null;
        for (int i = 0; i < aModeArray.length; i++)
        {
              DisplayMode aMode = aModeArray[i];
              if (aMode.getWidth() >= aWidth && aMode.getHeight() >= aHeight && aMode.getBitDepth() >= aDepth) // is aMode fitting at all?
              {
                    if (aBestMode != null)
                    {
                          if (aMode.getWidth() <= aBestMode.getWidth() && aMode.getHeight() <= aBestMode.getHeight() && aMode.getBitDepth() <= aBestMode.getBitDepth()) // is aMode better?
                                aBestMode = aMode;
                    }
                    else
                          aBestMode = aMode;
              }
        }
        return aBestMode;
  }



  public static void main(String[] args)
  {
        Frame frame = new Frame("Gear Demo");
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice aDevice = ge.getDefaultScreenDevice();
        DisplayMode aMode = getBestModeForParameters(aDevice, 32, 800, 600);
        if (aDevice.isFullScreenSupported())
        {
              aDevice.setFullScreenWindow(frame);
              if (aDevice.isDisplayChangeSupported())
                    aDevice.setDisplayMode(aMode);
        }
        frame.setBounds(0, 0, (int) aMode.getWidth(), (int) aMode.getHeight());

        GLCanvas canvas = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities());
        // Use debug pipeline
        //    canvas.setGL(new DebugGL(canvas.getGL()));
        System.err.println("CANVAS GL IS: " + canvas.getGL().getClass().getName());
        System.err.println("CANVAS GLU IS: " + canvas.getGLU().getClass().getName());

        canvas.addGLEventListener(new GearRenderer());
        frame.add(canvas);
        // frame.setSize(300, 300); atze: this is not needed for fullscreen!
        final Animator animator = new Animator(canvas);
        frame.addWindowListener(new WindowAdapter()
        {
              public void windowClosing(WindowEvent e)
              {
                    animator.stop();
                    System.exit(0);
              }
        });
        frame.show();
        animator.start();
  }

please give me feedback if this works for you!
if not all errors are mine and i’d like to remove them.

excuse me if i get enthusiastic, but by sight of my eye the fullscreen version is WAY faster than the original!

now for something completely different: i’d like to have vbl sync… i could build it in there, but how do i know if you want the sync?

Would i be correct in thinking that I would have to setup an envrinment to rebuild JOGL with your amendments before I even try your other sample code? If so, what setup in the end worked for you? Any hints or tips on getting set up?

Mac OS X (10.3.4)
Java 1.4.2_03

if you can build jogl on macos x at all, you’re at the start.

  • replace the method in
    jogl/src/native/jogl/MacOSXWindowSystemInterface.m
    with my modified method.
  • build jogl (ant macosx) and
  • copy the jogl.jar and libjogl.jnilib to your usual directory (/Library/java/Extensions) and you have fullscreen enabled.

then modify Gears.java with the example code and run it.

then just enjoy those gears running faster than ever in fullscreen :slight_smile:

Success!!!

The gears demo works - although a bit too fast for my eye to detect. ;D

This is great, the first time i’ve ever seen a fullscreen jogl app on the mac!!

Congrats to atze!! Thanks!

Atze: BTW this is the debug code I got whilst running the demo:

Also, just two quick notes:

  1. I don’t seem to be able to register any mouse clicks (only mouse moved events) - it may be a little too late for me to concentrate, so I may be doing something completely wrong. I’ll wait until the morning for this.

  2. The size of your new jogl.jar has reduced from thr official 900 KB to 600 - can this be explained?

  1. yeah, i see that problem too. i’m not sure who eats the events.
  2. no idea. maybe because i built it on a newer os version?

and i found a syntax error. i removed my debug code and forgot to rebuild - sorry for that.

i correct it in my first post.

I’d also like to report problems when switching renderers. This is probably a problem with my code, but the following worked with the old version of Jogl + libs (although, it should be noted that the old Jogl did output a memory leak type of error when I changed scenes).

This solution is just a hack (ie. is not going to be submitted to jogl) for a bug in Apple’s Java implementation. Mouse events will not get propageted correctly (dead mouse) and Swing components with AquaL&F will not work.

The real fix is shortly forthcoming from Apple and will be available to developers in the upcoming Java JDK 1.4.2 Developers Preview 3 http://developer.apple.com/java and then will be available later to the public in Java JDK 1.4.2 update 2.

cheers

ohhhhh, thanks for the update Gerard!

So, I suppose the developer update will be made available from the 28th onwards? :smiley:

In general, how are things going with the implementation of hardware accelerated Java2D commands? What else are you working on at the moment or aren’t I allowed to ask. ;D

Cheers,

ribot.

[quote]ohhhhh, thanks for the update Gerard!

So, I suppose the developer update will be made available from the 28th onwards? :smiley:

In general, how are things going with the implementation of hardware accelerated Java2D commands? What else are you working on at the moment or aren’t I allowed to ask. ;D

Cheers,

ribot.
[/quote]
We’re making some good progress on Java2D implementation and the underlaying surface/context management. Grab the DP3 once it is out and take it for a spin.

You’re allowed to ask anything you want, it’s just I’m not allowed to say much :wink:

cheers

Glad to hear the good progress you’re making on the new implementation! I’ve heard from a little gnome that a big update is due with regard to Jogl - I suppose I’ll just have to wait though. :smiley:

Your hard work is very much appreciated!

stack another plastic beer cup up m8 ;D

hi there.

i did a quick test with the new Java JDK 1.4.2 Developers Preview 3 (available since WWDC) and fullscreen now works out of the box on mac os x - but it is not a fullscreen NSOpenGLContext, it still renders into a view.

example:
80060032:
original code: 10000 frames in 36 seconds = 277 fps
modified code: 10000 frames in 28 seconds = 357 fps
144090032:
original code: 10000 frames in 72 seconds = 138 fps
modified code: 10000 frames in 48 seconds = 208 fps

to get the best performance we still need a modification of MacOSXWindowSystemInterface.m to switch the NSOpenGLContext to fullscreen instead of using a view.
i am pretty sure that the DP3 view-based context does not swap buffers, but instead does a bit-blit to the screen - and that’s definitely slower.

[quote]Mouse events will not get propageted correctly (dead mouse)
[/quote]
this is due to the missing window/view that you can not ask for movements. i worked around this in my objc-app by using NSApps currentEvent. here’s a sample to catch mouse movements:

  static NSTimeInterval eventNum = 0;
  NSEvent *anEvent = [NSApp currentEvent];
  if(eventNum != [anEvent timestamp] && [anEvent type] == NSMouseMoved)
  {
        eventNum = [anEvent timestamp];
        // you can now use anEvent to do what you need...
        float x = [anEvent deltaX];
        float y = [anEvent deltaY];
  }

yes, this breaks the normal behavior, but we are going to write FAST games, right?
if somebody knows a better way for this please tell me.

[quote]Swing components with AquaL&F will not work.
[/quote]
ok, let’s just accept that as a fullscreen-limitation. what i understood about jogl is that it’s an open-gl implementation for java, not a way to render swing on top of open-gl. if i go fullscreen i don’t need any swing/awt components. i just want to draw open-gl on the screen. maybe that’s just my understanding of fullscreen apps.

another possibility would be to add a new ‘true’ fullscreen mode in jogl, to circumvent the whole awt and swing stuff like this*:
GL context = GLDrawableFactory.getFactory().createGLFullscreenContext(GraphicsDevice, GLCapabilities);
*this comes right from my head, not tested, no deeper thoughts done, so don’t scream ‘hack’ again.
and while we’re at it let’s add a context.syncToVerticalBlank(boolean).

my code is not a hack and it is unfortunately not rendered useless with the modifications in DP3. it uses NSOpenGLContext as it was intended for fullscreen and brings the highest available performance to jogl on mac os x. let’s work together to bring that speed to jogl.

Looks like we could really use your help working out some issues with LWJGL on the Mac. Input things specifically. Possibly getting it to play nice with the AWT thread (it can lock up due to some contention with the AWT thread, if you let the AWT thread start).

[quote]Mouse events will not get propageted correctly (dead mouse)
[/quote]
um… just tested gears in true-fullscreen. the mouse just works! you can turn the gears around as you like. so that problem seems to be gone with DP3 as well.

Hmm, I seem to be doing something wrong with regard to getting content to show in fullscreen mode. I am able to switch to a desired resoultion and show my frame, but it’s display is just blank. I reckon I am missing a line somewhere, but can’t find out which line this is.

Is there any fullscreen sourcecode samples out there?

Any help would be appreciated.

Regards,

ribot.

hm… i don’t create a window. here’s a rip-off of my contructor-code. please keep in mind that i am working with my modified fullscreen-version of jogl.
the Bounds-class just holds four values: x,y,width,height.
canvas.addGLEventListener(this) is done to get the various callbacks, like public void init(GLDrawable aDrawable) and public void display(GLDrawable aDrawable)


      public Viewport(GraphicsDevice aDevice, DisplayMode aMode, boolean wantsFullscreen)
      {
            super();

            device = aDevice;
            bounds = new Bounds(0, 0, aMode.getWidth(), aMode.getHeight());

            Frame frame = new Frame("Viewport Frame");
            if (wantsFullscreen)
            {
                  System.out.println("will enter fullscreen: " + device);
                  if (device.isFullScreenSupported())
                  {
                        frame.setUndecorated(true); // this is important
                        device.setFullScreenWindow(frame);
                        System.out.println("will change displaymode: " + device);
                        if (device.isDisplayChangeSupported())
                        {
                              device.setDisplayMode(aMode);
                        }
                        else
                              System.err.println("DisplayChange is not supported on " + device);
                  }
                  else
                        System.err.println("Fullscreen is not supported on " + device);

            }

            DisplayMode bMode = aDevice.getDisplayMode();
            frame.setBounds((int) (bMode.getWidth() - bounds.size.width) >> 1, (int) (bMode.getHeight() - bounds.size.height) >> 1, (int) bounds.size.width, (int) bounds.size.height);

            GLCapabilities capabilities = new GLCapabilities();
            canvas = GLDrawableFactory.getFactory().createGLCanvas(capabilities);
            canvas.addGLEventListener(this);
            frame.add(canvas);
            frame.show();
      }

hope that helps :slight_smile: