Incomplete rendering returned from glReadPixels
Incomplete rendering returned from glReadPixels
- Subject: Incomplete rendering returned from glReadPixels
- From: Ben Haller <email@hidden>
- Date: Tue, 22 Jun 2010 09:25:44 -0400
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 (email@hidden)
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:
This email sent to email@hidden