My app uses FBOS to save 3D views to disk as JPEG or TIFF files. I create 2 renderbuffers for my FBO, one for the front color buffer, and one for a depth buffer. Everything works well as long as I don’t try to create an image that’s too big.
I first query OpenGL to make sure FBOs are supported, and get the maximum dimensions for an FBO by calling glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, &maxRenderbufferSize)
I also check each OpenGL call in my setup for errors using glGetError(). None of my OpenGL calls are returning errors.
My development machine, a recent MacBook Pro with an NVIDIA 8600 graphics chip and 256 mb of VRAM, claims to support FBOs of 8192x8192.
However, at a little over 4000x4000 pixels, the resulting image comes out black. If I increase the image size much beyond that, it frequently locks up my machine to the point where I have to do a force shutdown and restart.
I suspect that I am taking up too much VRAM, and leaving the Quartz system unable to handle drawing to the screen.
Sometimes after using my app the system will also get VERY sluggish, as if it is having to page the contents of VRAM back and forth to main memory. I have not had this happen since
How can I preflight my setup to avoid this, and better detect errors when they do happen?
I am a fair newbie when it comes to OpenGL, so it may be that I am missing something obvious.
Here is the code for the key part of my routine to save an image to disk using an FBO:
//Code to set up an NSBitmapImageRep and get a handle to the bitmap data in theRepData not shown
if (use_FBOs || force_fbos)
{
//if use_FBOs
// create an FBO size to to save_width, save_height
// Create a renderbuffer size to save_width, save_height
// bind FDO
// bind FBO to renderbuffer
// perform an openGL copy, with or without selection
// unbind renderbuffer, FBO
// delete FBO
//Set up an FBO with one renderbuffer attachment
GLuint framebuffer = 0;
GLuint renderbuffers[2] = {0,0};
glGenFramebuffersEXT( 1,
&framebuffer); //Generate a new framebuffer id
opengl_errchk("glGenFramebuffersEXT");
if (!framebuffer) return nil;
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT,
framebuffer); //Bind to it
opengl_errchk("glBindFramebufferEXT");
glGenRenderbuffersEXT( 2, renderbuffers); //Generate 2 new renderbuffer ids
//one for the color data, and one for a depth buffer
if (!renderbuffers[0] || !renderbuffers[1]) return nil;
opengl_errchk("glGenRenderbuffersEXT");
glBindRenderbufferEXT( GL_RENDERBUFFER_EXT,
renderbuffers[0]); //Bind the first renderbuffer
opengl_errchk("glBindRenderbufferEXT");
glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT,
GL_RGB,
save_width,
save_height); //Create storage for the renderbuffer object
opengl_errchk("glRenderbufferStorageEXT");
glFramebufferRenderbufferEXT(
GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT,
renderbuffers[0]); //Install the renderbuffer in the framebuffer?
opengl_errchk("glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT...");
//Now bind a depth buffer to the FBO
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffers[1]);
opengl_errchk("glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffers[1])");
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, save_width, save_height);
opengl_errchk("glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, save_width, save_height)");
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, renderbuffers[1]);
opengl_errchk("glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, renderbuffers[1])");
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); //Make sure the framebuffer is "complete" (ready for drawing)
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{ //Error. clean up before returning
//unbind the frame buffer.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glDeleteRenderbuffersEXT(2, renderbuffers);
glDeleteFramebuffersEXT(1, &framebuffer);
NSLog(@"Error. Frame buffer not complete.");
return nil;
}
// code to draw content to the renderbuffer
if (!save_selection)
glViewport (0, 0, openGLRect.size.width, openGLRect.size.height);
else
{// shift and scale our drawing
scale_factor = ((float)save_width) / view_selection_rect.size.width;
glViewport (-view_selection_rect.origin.x * scale_factor,
-view_selection_rect.origin.y * scale_factor,
camera.viewWidth * scale_factor,
camera.viewHeight * scale_factor );
}
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
[theMeshObject drawMesh];
glPixelStorei(GL_PACK_ALIGNMENT, 8);
glReadPixels( openGLRect.origin.x,
openGLRect.origin.y,
openGLRect.size.width,
openGLRect.size.height,
GL_RGB,
GL_UNSIGNED_BYTE,
theRepData); //theRepData points to bitmapData from an NSBitmapImageRep set up with it's rows aligned on 8-byte boundaries
opengl_errchk("glReadPixels");
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); //Now unbind the framebuffer
opengl_errchk("glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)");
glDeleteRenderbuffersEXT(2, renderbuffers); //delete both renderbuffer objects
opengl_errchk("glDeleteRenderbuffersEXT(2, renderbuffers)");
glDeleteFramebuffersEXT(1, &framebuffer);
opengl_errchk("glDeleteFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer)");
glViewport (0, 0, camera.viewWidth, camera.viewHeight); //restore the viewport back to it's original value.
opengl_errchk("glViewport");
}