Sections

Error checking in OpenGL

When programming OpenGL, you will run into bugs from time to time. Nonsensical geometry, stuff rendering black, stuff not rendering at all, etc... In these situations, you've most likely done something you shouldn't. For example, tried to buffer more data than the allocated memory can hold, forgotten to upload data, forgotten to set correct uniforms, and so on.

Now OpenGL can't help you with all of these - but if some buffer fails to allocate, or you pass an invalid enum, it can tell you about it. All you need to do is listen.

Whenever you do something bad in OpenGL, an error flag is raised. You can figure out what functions raise which error flags by reading the man pages, which are actually quite pleasant to read.

The following is a list of all the possible errors:

  • GL_NO_ERROR
  • GL_INVALID_ENUM
  • GL_INVALID_VALUE
  • GL_INVALID_OPERATION
  • GL_INVALID_FRAMEBUFFER_OPERATION
  • GL_STACK_UNDERFLOW
  • GL_STACK_OVERFLOW
  • GL_OUT_OF_MEMORY

If any error flag, except for the last one, is raised, the command you were trying to execute is ignored; leaving the state of OpenGL unchanged. If the last one occurs, the state is undefined, which is really scary and you don't want that.

We can find out if an error occurred by calling glGetError(), which returns any of the above codes, as an enum. If multiple errors occurred and you want to see them all, you will need to call this function repeatedly until it returns GL_NO_ERROR flag.

But this sort of global variable might be a source for confusion when debugging. For example: consider the function,

void foo() {
 glBindBuffer(GL_ARRAY_BUFFER, &someBuffer);
 glBufferSubData(GL_ARRAY_BUFFER, offset, size, data)
 glBindBuffer(GL_ARRAY_BUFFER, 0);
}
Let's say the we try to send more data than the buffer has allocated memory for. The error flag will be set to GL_INVALID_VALUE, and our call to glBufferSubData() will do nothing. However, we do not check for errors in this function. Consider another function:
void bar() {
 glBindBuffer(GL_ARRAY_BUFFER, &completelyDifferentBuffer);
 glBufferData(GL_ARRAY_BUFFER, size, NULL, usage);
 glBindBuffer(GL_ARRAY_BUFFER, 0);

 GLenum error = glGetError();
 ...
}

This function does check for errors. Now here's the kicker: if this is the first place we do an error-check after the error flag was raised, we will discover the error here! The reason is that the error flag is only reset to GL_NO_ERROR when we call glGetError(). But this function is completely unrelated to the other!

It would seem as if the error occurred when we allocate data using the other buffer, when in fact it didn't.

So what can we learn from this? For one, we realize that we can't partially check for errors, as it actually makes everything more confusing, which is the opposite of what we want. Our options are then:

  • Check for errors in every function where we interact with OpenGL
  • Check for errors only in the main rendering loop.
The first seems like it will cause quite a bit of overhead, and lead to some bloated code. So we naturally lean towards the second option. Our problem now, is that we no longer know exactly what function call caused the error, only that an error occurred somewhere.

There might be some clever programmable solution to this, like wrapping OpenGL calls to modify some global variable that keeps track of the last called function, and its parameters. But if you need such detailed debugging, I suggest you use gDEBugger, which is an awesome OpenGL debugger/profiler that lets you see everything going on.

The suggested solution is then that we only check for errors in the main rendering loop, and if an error occurs, we either guess what caused it, or pull out the debugger.

2 comments:

  1. Although it would be very nice to use glGetError() in checking your rendering in real-time inside your main loop, in practice, calling that function slows down your rendering quite a bit. The reason why is mainly because calling that function generally does incur a GPU flush, which causes your internal command buffer to halt until it resolves the function, therefore dramatically decreasing the speed.

    The best approach is to use their callback, which is only called when there is an error. Reference: http://www.opengl.org/registry/specs/ARB/debug_output.txt

    ReplyDelete
  2. Great post, much appreciate the time you took to write this.

    ReplyDelete