Getting debug messages
Prior to recent versions of OpenGL, the traditional way to get debug information was to call glGetError
. Unfortunately, that is an exceedingly tedious method for debugging a program. The glGetError
function returns an error code if an error has occurred at some point previous to the time the function was called. This means that if we're chasing down a bug, we essentially need to call glGetError
after every function call to an OpenGL function, or do a binary search-like process where we call it before and after a block of code, and then move the two calls closer to each other until we determine the source of the error. What a pain!
Thankfully, as of OpenGL 4.3, we now have support for a more modern method for debugging. Now we can register a debug callback function that will be executed whenever an error occurs, or other informational message is generated. Not only that, but we can send our own custom messages to be handled by the same callback, and we can filter the messages using a variety of criteria.
Getting ready
Create an OpenGL program with a debug context. While it is not strictly necessary to acquire a debug context, we might not get messages that are as informative as when we are using a debug context. To create an OpenGL context using GLFW with debugging enabled, use the following function call prior to creating the window.
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
An OpenGL debug context will have debug messages enabled by default. If, however, you need to enable debug messages explicitly, use the following call.
glEnable(GL_DEBUG_OUTPUT);
How to do it...
Use the following steps:
- Create a callback function to receive the debug messages. The function must conform to a specific prototype described in the OpenGL documentation. For this example, we'll use the following one:
void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar * message, void * param) { // Convert GLenum parameters to strings printf("%s:%s[%s](%d): %s\n", sourceStr, typeStr, severityStr, id, message); }
- Register our callback with OpenGL using
glDebugMessageCallback
:glDebugMessageCallback( debugCallback, NULL );
- Enable all messages, all sources, all levels, and all IDs:
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
How it works...
The callback function debugCallback
has several parameters, the most important of which is the debug message itself (the sixth parameter, message
). For this example, we simply print the message to standard output, but we could send it to a log file or some other destination.
The first four parameters to debugCallback
describe the source, type, id number, and severity of the message. The id number is an unsigned integer specific to the message. The possible values for the source, type and severity parameters are described in the following tables.
The source parameter can have any of the following values:
Source |
Generated By |
---|---|
|
Calls to the OpenGL API |
|
Calls to a window system API |
|
An application associated with OpenGL |
|
This application itself. |
|
Some other source |
The type parameter can have any of the following values:
Type |
Description |
---|---|
|
An error from the OpenGL API. |
|
Behavior that has been deprecated |
|
Undefined behaviour |
|
Some functionality is not portable. |
|
Possible performance issues |
|
An annotation |
|
Messages related to debug group push. |
|
Messages related to debug group pop. |
|
Other messages |
The severity parameter can have the following values:
Severity |
Meaning |
---|---|
|
Errors or dangerous behaviour |
|
Major performance warnings, other warnings or use of deprecated functionality. |
|
Redundant state changes, unimportant undefined behaviour. |
|
A notification, not an error or performance issue. |
The length
parameter is the length of the message string, excluding the null terminator. The last parameter param
is a user-defined pointer. We can use this to point to some custom object that might be helpful to the callback function. For example, if we were logging the messages to a file, this could point to an object containing file I/O capabilities. This parameter can be set using the second parameter to glDebugMessageCallback
(more on that in the following content).
Within debugCallback
we convert each GLenum
parameter into a string. Due to space constraints, I don't show all of that code here, but it can be found in the example code for this book. We then print all of the information to standard output.
The call to glDebugMessageCallback
registers our callback function with the OpenGL debug system. The first parameter is a pointer to our callback function, and the second parameter (NULL
in this example) can be a pointer to any object that we would like to pass into the callback. This pointer is passed as the last parameter with every call to debugCallback
.
Finally, the call to glDebugMessageControl
determines our message filters. This function can be used to selectively turn on or off any combination of message source, type, id, or severity. In this example, we turn everything on.
There's more...
OpenGL also provides support for stacks of named debug groups. Essentially what this means is that we can remember all of our debug message filter settings on a stack and return to them later after some changes have been made. This might be useful, for example, if there are sections of code where we have needs for filtering some kinds of messages and other sections where we want a different set of messages.
The functions involved are glPushDebugGroup
and glPopDebugGroup
. A call to glPushDebugGroup
generates a debug message with type GL_DEBUG_TYPE_PUSH_GROUP
, and retains the current state of our debug filters on a stack. We can then change our filters using glDebugMessageControl
, and later return to the original state using glPopDebugGroup
. Similarly, the function glPopDebugGroup
generates a debug message with type GL_DEBUG_TYPE_POP_GROUP
.