Properly minimizing an OpenGL view

Posts: 1,234
Joined: 2002.10
Post: #16
The example I posted at the top of the thread is written assuming an NSOpenGLContext instance variable "ctx" which is set once when the view is created and never changes. But of course you can use any flavor GL interface to do the same thing.
Quote this message in a reply
Posts: 1,234
Joined: 2002.10
Post: #17
Revisiting this after some recent work on PCSX:

One of the inefficiencies in the code at the top of this thread is the repeated pixel swizzling:
* the GL framebuffer is always ARGB format
* glReadPixels swizzles that to RGBA
* NSImage must be created with RGBA
* NSImage swizzles to ARGB (NSCachedImageRep) during draw

That isn't so great. So here's an updated version using CG to eliminate the swizzles:
- (void)copyGLtoQuartz {
    const void *get_byte_pointer(void *bitmap) { return bitmap; }
    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
    unsigned char* bitmap = malloc(rowbytes * size.height);
    [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
    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_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, bitmap);

    [self lockFocus];
    // create a CGImageRef from the memory block
    CGDataProviderDirectAccessCallbacks gProviderCallbacks = { get_byte_pointer, NULL, NULL, NULL };
    CGDataProviderRef provider = CGDataProviderCreateDirectAccess(bitmap, rowbytes * size.height, &gProviderCallbacks);
    CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
    CGImageRef cgImage = CGImageCreate(size.width, size.height, 8, 32, rowbytes, cs,
        kCGImageAlphaNoneSkipFirst, provider, NULL, NO, kCGRenderingIntentDefault);

    // composite the CGImage into the view
    CGContextRef gc = [[NSGraphicsContext currentContext] graphicsPort];
    CGContextDrawImage(gc, CGRectMake(0, 0, size.width, size.height), cgImage);

    // clean up

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

For 32bpp framebuffers, this is about twice as fast as the NSImage version, so the short pause before minimization is reduced. It still works fine for 16bpp framebuffers, but, like the old version, it will have to convert during glReadPixels. (If you know your framebuffer might be 16bpp and you care, you could optimize for that case.)

[Edit: fixed get_byte_pointer type for forward-compatibility.]
Quote this message in a reply
Posts: 304
Joined: 2002.04
Post: #18
I have a weird thing going on with my G4/400 Rage128. When an app calls glReadPixels it freezes my entire system for just over 30 seconds (just timed it). This only happens the first time - even if I relaunch the app it wont happen again. Only if I reboot the machine will a call to glReadPixels trigger the pause again.

I know the Rage128 is pretty old - so its not a big concern. But does anyone here still using a Rage128 see the same thing or is it just me?

Thought Id mention it since it was weird.
Quote this message in a reply
Posts: 1,234
Joined: 2002.10
Post: #19
30 seconds? Are you sure you're not trying to allocate a gig of RAM or something, and swapping? What does Shark say is happening the whole time?

I definitely don't see that happen on the Rage128 iMacs I've tested on, although I haven't looked with 10.3.7 yet.

The pause I see (for a reasonably sized window on the order of 1024x768) is about 0.1 seconds for NSImage and 0.05 seconds for the new CG code.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  OpenGL view first frame flickers garbage noface0711 0 843 Mar 26, 2016 06:16 AM
Last Post: noface0711
  OpenGL view first frame flickers garbage mk12 8 8,949 Sep 4, 2010 06:06 PM
Last Post: mk12
  When to create custom OpenGL view instead of subclass NSOpenGL view Coyote 37 34,506 Oct 20, 2009 08:16 PM
Last Post: Coyote
  opengl view question Leroy 3 4,334 Jul 23, 2007 11:08 PM
Last Post: AnotherJake
  Porting SDL to Cocoa OpenGL view--texture problem smittyz 7 7,278 Jul 21, 2007 07:53 PM
Last Post: smittyz