|
OpenGL generate all sorts of error reports and something goes wrong. Unfortunately, it is easy to miss them because OpenGL function calls do not return an error code. You have to call a special function "glGetError" to find out if an error has been generated or not. However, it is not enough to call glGetError only after the functions you suspect to generate and error. OpenGL errors are retains until glGetError is called. So if a function has generated long before you call glGetError, that first error will be reported even if your most recent OpenGL function call didn't generate and error.
// error: rectangle texture do not accept GL_REPEAT as wrapping mode. glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_REPEAT); ... ... GLint id; // This function call is correct glGenFramebuffersEXT(1, &id); if(glGetError() == GL_NO_ERROR) { // error: we get here because we failed to call glGetError after glTexParameteri. ... }
In the previous code sample, the call to glGenFramebuffersEXT does not cause an error. However, because glTexParameteri was called with an invalid value and we failed to call glGetError, we end up getting that error when we call glGetError right after we glTexParameteri. It can be very difficult to track down which OpenGL call caused the error. You have to test glGetError after every OpenGL function. And when you finally find the error you realize that it might be a good idea to leave all the glGetError calls you have added to your code just in case you need them again. And that is the right things to do.
But then you might wonder about performance. All these call to glGetError and add up very quickly and as your code become error free (OpenGL call errors that is), glGetError tends to do unnecessary work each time. You need glGetError only when there is something wrong.
So it is a good idea to call glGetError in debug mode and remove it when you release your program. And you can do that with C++ macros. Here they are:
#define CHECKGL( GLcall ) \ { \ GLcall; \ if(1) \ CheckGLError( #GLcall, __FILE__, __LINE__ ); \ } #define CHECKGL_MSG( msg ) \ { \ if(1) \ CheckGLError( #msg, __FILE__, __LINE__ ); \ }
Lets go over these macros. The first macro takes an OpenGL function call as parameter. The call is executed and right after, the function CheckGLError is called. More on CheckGLError later.
CHECKGL( glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_WRAP) );
We don't have to call glGetError. This is taken care by CheckGLError.
Even when the OpenGL call returns a value like you can still encapsulate the call inside CHECKGL, provided you don't initialize the variable that receives the returned value inside the macro.
void * ptr0; CHECKGL( ptr0 = glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB) ); ... CHECKGL( void* ptr1 = glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB) ); // Be careful here! ptr1 isn't defined anymore. ptr1 is out of scope.
The second macro is special. You can make an OpenGL call that is not encapsulated inside CheckGLError . But right after, you may use the macros CHECKGL_MSG and pass it the name of the previous OpenGL call. CHECKGL_MSG checks if and error as been triggered and reports the name of the OpenGL function that was passed as parameter.
void *ptr0 = glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB); CHECKGL_MSG( "glMapBufferARB" );
Now, onto CheckGLError! The purpose of this function is to call glGetError. If an error is detected, CheckGLError reports the name of the OpenGL call that created the error and the file and line number of the call.
int CheckGLError(const char *GLcall, const char *file, int line) { GLenum glErr; int retCode = 0; while ( (glErr=glGetError()) != GL_NO_ERROR) { switch(glErr) { case GL_INVALID_ENUM: printf("GL_INVALID_ENUM error in File %s at line: %d", file, line); break; case GL_INVALID_VALUE: printf("GL_INVALID_VALUE error in File %s at line: %d", file, line); break; case GL_INVALID_OPERATION: printf("GL_INVALID_OPERATION error in File %s at line: %d", file, line); break; case GL_STACK_OVERFLOW: printf("GL_STACK_OVERFLOW error in File %s at line: %d", file, line); break; case GL_STACK_UNDERFLOW: printf("GL_STACK_UNDERFLOW error in File %s at line: %d", file, line); break; case GL_OUT_OF_MEMORY: printf("GL_OUT_OF_MEMORY error in File %s at line: %d", file, line); break; default: printf("UNKNOWN ERROR in File %s at line: %d", file, line); } } return retCode; }
You can adapt the macros and CheckGLError any way you like. But calling glGetError consistently after every OpenGL call is the only way to detect potential errors before it is too late. For your release build, you can simply have the macros do nothing and avoid a performance hit.
There are limitations though. You cannot call glGetError between glBegin and glEnd. Therefore, you can't use the macros between glBegin and glEnd. Here you are on your own; you have to make sure that everything you do between these calls correct.
However, if you plan on moving to OpenGL 3.0, you are aware that glBegin, glEnd and many other functions that are called in between are being deprecated. You will have to use vertex buffer object and you will be able to use the macros to check for errors.
At first it can be tedious to go through your code and encapsulate your gl calls with the macros. But trust me, you won't regret it!
|