« Fragment Program Utilities | Main | Running out of VRAM on OS X »

June 12, 2006

Debugging OpenGL on OS X

It's tough to debug OpenGL problems, especially with pixel shaders. I'll cover some tips for any platform, and then some of the OS X built-in tools that can help.

Error Checking

First, the fundamental rule of debugging OpenGL: call glGetError() early and often. This returns a non-zero value if any of the previous GL calls ran into a problem. I try to call this at the end of every big chunk of GL processing, for example at the end of a model drawing routine, to keep down the clutter and avoid the performance impact of having it in an inner loop.

Now, you only get a number out of the function, so you need to call gluErrorString() to get an understandable description of what went wrong. I usually wrap the checking up into a macro, so I can easily add it, and automatically remove it in release builds. Here's what I use:

#ifdef ENABLE_GL_ERRORS
void Effect_ShowGLErrors(void)
{
GLenum error;
const GLubyte* errStr;
if ((error = glGetError()) != GL_NO_ERROR)
{
errStr = gluErrorString(error);
fprintf(stderr, "OpenGL Error: %s\n", errStr);
}
}
#else // ENABLE_GL_ERRORS
#define EFFECT_SHOW_GL_ERRORS() (void)(0)
#endif // ENABLE_GL_ERRORS

Sometimes it's not obvious which call caused the error, and then you either need to scatter more checks through your GL code to narrow it down, or use a great feature of Apple's OpenGL Profiler, Break on Error.

Start up the profiler, in /Developer/Applications/Graphics Tools/, and either launch your program from it or attach to a copy that's already running. Choose "Views->Breakpoints" from the main menu, and click "Break on Error" on the bottom left of the window. Now, when an error occurs, the program will display a callstack, arguments, and the exact function that caused it. If you've attached to a program you ran from the debugger, you can hit pause in there and examine everything as normal.

Black Screen Blues

One of the hardest things to debug is a black screen in GL. Is the window or pbuffer setup messed up? Is one of the textures you're using bogus? Is your camera pointing at the floor? Is a fragment program having a problem? Is the geometry you're drawing broken, or translated off to infinity? Is your lighting setup busted?

My first test is the programming equivalent of checking pupil dilation with a flashlight; glClear(). If calling a colored clear at the end of rendering doesn't work, then the problem's outside of the drawing code, and I know I have to fix something in my pbuffer or window setup instead.

Clearing the screen is the only way of getting something to appear on screen that it's impossible to screw up by having dodgy gl state. It doesn't matter what your viewport or matrices are, what textures or programs are bound, if GL is working, calling glClear() will have an effect. It's especially useful if you have a chain of rendering running through a series of pbuffers, it can be tough to figure out where a problem's occuring if the end result is blank, using clear lets you work backwards through the chain.

glClearColor(1,1,0,1);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0,0,0,0);

As a bonus, you can change the color (I've got yellow above) if you want to indicate different calls.

If this does give you a yellow screen when you call it after all your rendering, then move it to the start of your rendering code, so anything else will be drawn over it. If you then see a black screen, or a silhouette of your model, you know your geometry is getting rendered, but the texturing, lighting or pixel shaders you're using don't work. If you still see a clear yellow screen, then it's the geometry that's the problem.

Posted by petewarden at June 12, 2006 12:32 PM

Comments

Actually it is possible for state settings to mess up even glClear :)

glClear honors glColorMask, glDepthMask, and glStencilMask functions.

Worth noting you do simple anaglyph stereo glasses rendering (red/cyan glasses for instance) using glColorMask to mask which color components you want to render into, not ideal but a fun trick :)

Posted by: LordHavoc at October 20, 2006 06:42 AM

That's an interesting point, thanks. Funnily enough, I ran into the stencil mask problem with glClear, but the other way around. I _wanted_ it to respect the stencil mask, and it ignored it and cleared the whole screen. I thought it also ignored the z buffer. I'm not sure on the spec on these two though, so it may have just been a driver oddity on OS X.

glColorMask() being ignored makes sense, I ended up using it to implement a color matrix operation on the fixed function pipeline a while ago (you can set up an all channels = red * redFactor + green*greenFactor + blue*blueFactor equation, and then do three passes with all but one channel masked and the appropriate factors from the matrix).

Posted by: Pete Warden at October 20, 2006 08:18 AM

Post a comment




Remember Me?