Hi all. I'm trying to render some graphics using NSOpenGLView and then save the rendered bits as a TIFF image. I'm having trouble getting this working. I already posted this question to the mac- opengl list, and didn't get a solution. I'm posting here now because I'm using NSOpenGLView, and perhaps my difficulties are related to it, so maybe it's really a Cocoa issue in that sense. Anyhow, I'm hoping somebody else in the Cocoa world has figured out this problem. I'm seeing incomplete rendering in the saved TIFF; it looks like the glReadPixels returns the pixels of the framebuffer before all of the drawing that I've requested has completed. Perhaps a picture is worth a thousand words. The way these images ought to look:

http://www.sticksoftware.com/task3_landscape.tiff

 The way they often do look:

http://www.sticksoftware.com/task1_landscape.tiff

So as you can see, the rendering is perfectly fine except that not all the drawing has completed yet. Note these two images were both generated by the same exact code, in the same run of the app; but sometimes the rendering gets completed, and sometimes it doesn't.

Here's my top-level method to write the image; this is a method on a subclass of NSOpenGLView:


- (void)writeToFile:(NSString *)filePath
{
        if (![self window])
        {
// OpenGL doesn't seem to display unless we're in an onscreen window :-<
                NSRect popViewFrame = [self frame];
NSWindow *popViewWindow = [[NSWindow alloc] initWithContentRect:popViewFrame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
                
                [popViewWindow setContentView:self];
                [popViewWindow orderFront:nil];
                [self setNeedsDisplay:YES];
                [self display];
        }
        
        NSImage *snapImage = [self imageFromView];
        NSData *tiffData = [snapImage TIFFRepresentation];
        
        [tiffData writeToFile:filePath atomically:YES];
}


It's a little weird because this method gets called sometimes on a view that is not yet installed in a window, so I just make a window then and there. (This is run in a headless command-line app, I ought to mention.) And yes, I don't bother cleaning up the window and such; right after this image writes out, the task exits, so cleanup doesn't matter. Note that I am sure the rendering is in fact complete; I see the window flash onscreen, and it has everything in it. (I can also run my app in a GUI mode, and all the rendering looks right then.)

The -imageFromView method is pretty much straight from the web (in fact, I think this code has gone over the list before):


// From http://www.ripplon.com/code/Cocoa/NSOpenGLView-imageFromView.m

@implementation NSOpenGLView (AKImageGeneration)

static void memxor(unsigned char *dst, unsigned char *src, unsigned int bytes)
{
        while (bytes--) *dst++ ^= *src++;
}

static void memswap(unsigned char *a, unsigned char *b, unsigned int bytes)
{
        memxor(a, b, bytes);
        memxor(b, a, bytes);
        memxor(a, b, bytes);
}

- (NSImage *)imageFromView
{
        NSRect bounds;
        int height, width, row, bytesPerRow;
        NSBitmapImageRep *imageRep;
        unsigned char *bitmapData;
        NSImage *image;
        
        bounds = [self bounds];
        
        height = bounds.size.height;
        width = bounds.size.width;
        
        imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
                                                                                
                           pixelsWide: width
                                                                                
                           pixelsHigh: height
                                                                                
                        bitsPerSample: 8
                                                                                
                  samplesPerPixel: 4
                                                                                
                                 hasAlpha: YES
                                                                                
                                 isPlanar: NO
                                                                                
                   colorSpaceName: NSCalibratedRGBColorSpace
                                                                                
                          bytesPerRow: 0                                // 
indicates no empty bytes at row end
                                                                                
                         bitsPerPixel: 0];
        
        [[self openGLContext] makeCurrentContext];
        
        glFlush();

        bitmapData = [imageRep bitmapData];
        
        bytesPerRow = [imageRep bytesPerRow];
        
glPixelStorei(GL_PACK_ROW_LENGTH, 8*bytesPerRow/[imageRep bitsPerPixel]);
        
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bitmapData);
        
        glFlush();

// Flip the bitmap vertically to account for OpenGL coordinate system difference
        // from NSImage coordinate system.
        
        for (row = 0; row < height/2; row++)
        {
                unsigned char *a, *b;
                
                a = bitmapData + row * bytesPerRow;
                b = bitmapData + (height - 1 - row) * bytesPerRow;
                
                memswap(a, b, bytesPerRow);
        }
        
        // Create the NSImage from the bitmap
        
image = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
        [image addRepresentation: imageRep];
        
        // Release memory
        
        [imageRep release];
        
// Previously we did not flip the bitmap, and instead did [image setFlipped:YES]; // This does not work properly (i.e., the image remained inverted) when pasting
        // the image to AppleWorks or GraphicConvertor.
        
        return image;
}

@end


I added the two glFlush() calls in an attempt to fix this bug, but they make no difference; the image still sometimes gets written out with partial data. Changing them to glFinish() calls makes no difference. Indeed, adding sleep(1) calls to try to guarantee that drawing has completed before I get the pixels also makes no difference. Finally, I suppose some code from my NSOpenGLView subclass might be relevant:


- (id)initWithFrame:(NSRect)frame
{
        GLuint pixelFormatAttributes[] =
        {
                //NSOpenGLPFANoRecovery,
                NSOpenGLPFAWindow,
                NSOpenGLPFAAccelerated,
                NSOpenGLPFADoubleBuffer,
                NSOpenGLPFAColorSize, 24,
                NSOpenGLPFAAlphaSize, 8,
                //NSOpenGLPFADepthSize, 24,
                //NSOpenGLPFAStencilSize, 8,
                NSOpenGLPFADepthSize, 0,
                NSOpenGLPFAStencilSize, 0,
                NSOpenGLPFAAccumSize, 0,
                0
        };
        
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes: (NSOpenGLPixelFormatAttribute*)pixelFormatAttributes];
        
        if (!pixelFormat)
[NSException raise:NSInternalInconsistencyException format:@"No OpenGL pixel format in %@", NSStringFromSelector(_cmd)];

if (self = [super initWithFrame:frame pixelFormat:[pixelFormat autorelease]])
        {
   }
        
   return self;
}

- (void)drawRect:(NSRect)rect
{
        NSRect bounds = [self bounds];
        
        // Update the viewport
        glViewport(0, 0, bounds.size.width, bounds.size.height);
        
        // Update the projection
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
gluOrtho2D(0.0, bounds.size.width, bounds.size.height, 0.0); // swap bottom and top to create a flipped coordinate system
        glMatrixMode(GL_MODELVIEW);
        
// Clear to white; we used to use glClear(), but that does not work correctly when saving to a TIFF, bizarrely...
        glColor3f(1.0, 1.0, 1.0);
        glRecti(0, 0, bounds.size.width, bounds.size.height);
        
        // Frame our view
        glColor3f(0.25, 0.25, 0.25);
        glRecti(0, 0, 1, bounds.size.height);
        glRecti(1, 0, bounds.size.width - 1, 1);
glRecti(bounds.size.width - 1, 0, bounds.size.width, bounds.size.height); glRecti(1, bounds.size.height - 1, bounds.size.width - 1, bounds.size.height);
        
        // Further drawing
        ...
        
        [[self openGLContext] flushBuffer];
}


How can I get this to work properly? My OpenGL Reference Manual (page 2) says: "...each primitive is drawn completely before any subsequent command takes effect... state-querying commands return data that's consistent with complete execution of all previously issued OpenGL commands." So doesn't that mean that this result should be impossible, according to the spec? I'm very inexperienced with OpenGL, though, so I may have made some rookie mistake in the code above. (Feel free to critique my code in other respects too, I'd like to learn.)

   Thanks!

Ben Haller
McGill University

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to