Properly minimizing an OpenGL view

Sage
Posts: 1,232
Joined: 2002.10
Post: #1
(this is something to put in the FAQ)

Did you ever notice that when you minimize the window of an OpenGL game, sometimes the window turns all white while it is genie-ing into the dock? Or, the dock icon is all white?

Nearly all of the uDevGame entries had this problem, so here's the answer for Cocoa people. You need to read the GL framebuffer and draw it into the underlying Quartz view:

Code:
// window delegate methods
- (void)windowWillMiniaturize:(NSNotification *)notification {
    [self copyGLtoQuartz];    
    [[self window] setOpaque:NO];        // required to make the Quartz underlay and the window shadow appear correctly
}


- (void)windowDidMiniaturize:(NSNotification *)notification {
    [[self window] setOpaque:YES];
}

// NSOpenGLView subclass methods
- (BOOL)isFlipped {
    return YES;                            // required for proper minimization, must sync ortho view
}


- (void)copyGLtoQuartz {
    NSSize  size = [self frame].size;
    GLfloat zero = 0.0f;
    long    rowbytes = size.width * 4;
    rowbytes = (rowbytes + 3)& ~3;            // ctx rowbytes is always multiple of 4, per glGrab
    NSBitmapImageRep *minicon = [[NSBitmapImageRep alloc]
        initWithBitmapDataPlanes:nil
        pixelsWide:size.width
        pixelsHigh:size.height
        bitsPerSample:8
        samplesPerPixel:3
        hasAlpha:NO
        isPlanar:NO
        colorSpaceName:NSDeviceRGBColorSpace
        bytesPerRow:rowbytes
        bitsPerPixel:32];
    
    [ctx makeCurrentContext];
    glFinish();                                // finish any pending OpenGL commands
    glPushAttrib(GL_ALL_ATTRIB_BITS);        // reset all properties that affect glReadPixels, in case app was using them
    glDisable(GL_COLOR_TABLE);
    glDisable(GL_CONVOLUTION_1D);
    glDisable(GL_CONVOLUTION_2D);
    glDisable(GL_HISTOGRAM);
    glDisable(GL_MINMAX);
    glDisable(GL_POST_COLOR_MATRIX_COLOR_TABLE);
    glDisable(GL_POST_CONVOLUTION_COLOR_TABLE);
    glDisable(GL_SEPARABLE_2D);
    
    glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 1, &zero);
    glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 1, &zero);
    glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 1, &zero);
    glPixelMapfv(GL_PIXEL_MAP_A_TO_A, 1, &zero);
    
    glPixelStorei(GL_PACK_SWAP_BYTES, 0);
    glPixelStorei(GL_PACK_LSB_FIRST, 0);
    glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0);
    glPixelStorei(GL_PACK_ALIGNMENT, 4);    // force 4-byte alignment from RGBA framebuffer
    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
    glPixelStorei(GL_PACK_SKIP_ROWS, 0);
    glPixelStorei(GL_PACK_SKIP_IMAGES, 0);
    
    glPixelTransferi(GL_MAP_COLOR, 0);
    glPixelTransferf(GL_RED_SCALE, 1.0f);
    glPixelTransferf(GL_RED_BIAS, 0.0f);
    glPixelTransferf(GL_GREEN_SCALE, 1.0f);
    glPixelTransferf(GL_GREEN_BIAS, 0.0f);
    glPixelTransferf(GL_BLUE_SCALE, 1.0f);
    glPixelTransferf(GL_BLUE_BIAS, 0.0f);
    glPixelTransferf(GL_ALPHA_SCALE, 1.0f);
    glPixelTransferf(GL_ALPHA_BIAS, 0.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_RED_SCALE, 1.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_RED_BIAS, 0.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_GREEN_SCALE, 1.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_GREEN_BIAS, 0.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_BLUE_SCALE, 1.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_BLUE_BIAS, 0.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_ALPHA_SCALE, 1.0f);
    glPixelTransferf(GL_POST_COLOR_MATRIX_ALPHA_BIAS, 0.0f);
    glReadPixels(0, 0, size.width, size.height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, [minicon bitmapData]);
    glPopAttrib();

    [self lockFocus];
    [minicon drawInRect:frame];
    [minicon release];

    [self unlockFocus];
    [[self window] flushWindow];
}

Note 1: This setup requires the viewport coordinate system to have the origin in the upper left. For example in an orthographic projection:

Code:
glViewport(0, 0, frame.size.width, frame.size.height);
    gluOrtho2D(0, frame.size.width, frame.size.height, 0);        // must be flipped to match the NSView, origin at TOP left

If your origin is in the lower left, like in Quartz, then you have to flip the NSImage upside down in CopyGLtoQuartz.

Note 2: This doesn't handle a GL view which has a translucent background. This is only an issue if you are overlaying GL content on Quartz content like in the UnderlaySurface example. See the GLUT source code if you need to do this, it involves fixing up the alpha component of each pixel.

Note 3: If your app uses GLUT, then all of this is already done for you.

Note 4: You'll also have to do this if you want to print your NSOpenGLView.

(Edit: fixed typos induced by lack of sleep)
Quote this message in a reply
Member
Posts: 304
Joined: 2002.04
Post: #2
THANK YOU. this drove me crazy.
Quote this message in a reply
Member
Posts: 304
Joined: 2002.04
Post: #3
where is ctx defined/initialized?

thanks, Codemattic
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #4
Code:
// NSOpenGLView subclass header
NSOpenGLContext *ctx;

Depending how you construct your view, you either make this yourself, or you can get it from the system with [NSOpenGLContext currentContext];

Don't forget, if you have multiple contexts or use threads, you must set the current context before ANY gl calls (resize drawRect, dealloc, etc);
Quote this message in a reply
Member
Posts: 269
Joined: 2005.04
Post: #5
Ah thank you. I keep forgetting to add code like this to Gaichu. Mental note...
Quote this message in a reply
Member
Posts: 201
Joined: 2002.06
Post: #6
Somebody should add this to the source code section of the site. It would be a good addition.
Quote this message in a reply
DoG
Moderator
Posts: 869
Joined: 2003.01
Post: #7
If we only had it in Carbon, too. Though I am sure there are some Apple examples on this, somebody should extract the relevant code.
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #8
It's pretty easy in Carbon, too -- Make a GWorld as if loading a texture via QuickTime, ReadPixels into the GWorld, then CopyBits the GWorld to the window.
Quote this message in a reply
Programmer
Unregistered
 
Post: #9
Strange -- I dropped this into my (non-game) app to try it and it doesn't even call those methods upon miniturization. Does this have to be a root view, or will it work with a pair of NSOpenGLView panes in a larger window?
Quote this message in a reply
Member
Posts: 72
Joined: 2006.10
Post: #10
Make sure that your glView (or whatever object you put the code in) is the window's delegate, then the code should be called when the window is minimised.

- Sohta
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #11
You'll have to do a little more work if you have multiple GLviews, or overlapping views, or etc. This simplified code is geared towards games where the window is one big GL context.

See the GLUT source for a complete recursive hierarchical copy.
Quote this message in a reply
Member
Posts: 72
Joined: 2006.10
Post: #12
I've finally added that sweet piece of code to my project (works great)

For those who don't want to change their coordinate system (like me). You can also simply flip the image as you copy it to the window. Here's a simple way to do it:

Code:
NSAffineTransform *mirror = [NSAffineTransform transform];
  [mirror  scaleXBy:1 yBy:-1];
  [mirror translateXBy:0 yBy: -size.height];
  [mirror concat];
  [minicon drawInRect:[glWindow frame]];
  [mirror invert];
  [mirror concat];

Thanks a lot!

- Sohta
Quote this message in a reply
Member
Posts: 233
Joined: 2003.05
Post: #13
Anybody ever try to get this to work with an SDL app? I'm assuming it would go into SDLMain.m, but I'm shaky on Cocoa and I could be wrong. Also, I'm not sure how to get the ctx from SDL. Any help on this would be great!

"Pay no attention to that man behind the curtain." - Wizard of Oz
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #14
I believe SDL is built on top of CGL, not NSGL. So you can use CGLGetCurrentContext() at any time after the context is created and active. Substitute the proper SDLImage stuff for NSBitmapImageRep stuff too.
Quote this message in a reply
DoG
Moderator
Posts: 869
Joined: 2003.01
Post: #15
Since NSGL (and AGL, for that matter)is a layer above CGL, you can probably use CGL calls no matter how the context was created.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  OpenGL view first frame flickers garbage mk12 8 6,648 Sep 4, 2010 06:06 PM
Last Post: mk12
  When to create custom OpenGL view instead of subclass NSOpenGL view Coyote 37 21,909 Oct 20, 2009 08:16 PM
Last Post: Coyote
  opengl view question Leroy 3 3,251 Jul 23, 2007 11:08 PM
Last Post: AnotherJake
  Porting SDL to Cocoa OpenGL view--texture problem smittyz 7 5,076 Jul 21, 2007 07:53 PM
Last Post: smittyz
  Printing OpenGL View in Window skyhawk 3 3,694 Mar 21, 2007 02:05 PM
Last Post: unknown