Debug output
GL_ARB_debug_output allows the GL to notify applications when various events occur that may be useful during application development and debugging. These events are represented in the form of enumerable messages with a human-readable string representation.
Step 1. First, let’s define support for GL_ARB_debug_output extension.
// --- ogl.g - global space ---------------------------------------
#ifndef GL_ARB_debug_output
#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242
#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143
#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144
#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145
#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243
#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244
#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245
#define GL_DEBUG_SOURCE_API_ARB 0x8246
#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247
#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248
#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249
#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A
#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B
#define GL_DEBUG_TYPE_ERROR_ARB 0x824C
#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D
#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E
#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F
#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250
#define GL_DEBUG_TYPE_OTHER_ARB 0x8251
#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146
#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147
#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148
typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC) (unsigned int source, unsigned int type,
unsigned int severity, int count, const unsigned int* ids, bool enabled);
typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC) (unsigned int source, unsigned int type,
unsigned int id, unsigned int severity, int length, const char* buf);
typedef void (APIENTRY *GLDEBUGPROCARB)(unsigned int source, unsigned int type, unsigned int id,
unsigned int severity, int length, const char* message, void* userParam);
typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC) (GLDEBUGPROCARB callback,
void* userParam);
typedef unsigned int (APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC) (unsigned int count, int bufsize,
unsigned int* sources,unsigned int* types, unsigned int* ids,
unsigned int* severities, int* lengths, char* messageLog);
#endif
extern PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB;
extern PFNGLDEBUGMESSAGEINSERTARBPROC glDebugMessageInsertARB;
extern PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB;
extern PFNGLGETDEBUGMESSAGELOGARBPROC glGetDebugMessageLogARB;
|
// --- ogl.cpp - global space -------------------------------------
PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB = NULL;
PFNGLDEBUGMESSAGEINSERTARBPROC glDebugMessageInsertARB = NULL;
PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB = NULL;
PFNGLGETDEBUGMESSAGELOGARBPROC glGetDebugMessageLogARB = NULL;
|
// --- GLRenderer.cpp - CGLRenderer::InitAPI() -------------------------------------
glDebugMessageControlARB = (PFNGLDEBUGMESSAGECONTROLARBPROC)
wglGetProcAddress("glDebugMessageControlARB");
glDebugMessageInsertARB = (PFNGLDEBUGMESSAGEINSERTARBPROC)
wglGetProcAddress("glDebugMessageInsertARB");
glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC)
wglGetProcAddress("glDebugMessageCallbackARB");
glGetDebugMessageLogARB = (PFNGLGETDEBUGMESSAGELOGARBPROC)
wglGetProcAddress("glGetDebugMessageLogARB"); |
Step 2. In order to enable this extension, we have to create GL context with WGL_CONTEXT_DEBUG_BIT_ARB set. WGL_CONTEXT_DEBUG_BIT_ARB is a least significant bit in the WGL_CONTEXT_FLAGS_ARB attribute.
bool CGLRenderer::CreateGLContext(CDC* pDC)
{
PIXELFORMATDESCRIPTOR pfd ;
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.iLayerType = PFD_MAIN_PLANE;
int nPixelFormat = ChoosePixelFormat(pDC->m_hDC, &pfd);
if (nPixelFormat == 0) return false;
BOOL bResult = SetPixelFormat (pDC->m_hDC, nPixelFormat, &pfd);
if (!bResult) return false;
// --- OpenGL 3.x-4.x ---
HGLRC tempContext = wglCreateContext(pDC->m_hDC);
if(!tempContext) return false;
wglMakeCurrent(pDC->m_hDC,tempContext);
int major, minor;
GetGLVersion(&major, &minor);
int attribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, major,
WGL_CONTEXT_MINOR_VERSION_ARB, minor,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB |
WGL_CONTEXT_DEBUG_BIT_ARB,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)
wglGetProcAddress("wglCreateContextAttribsARB");
if(wglCreateContextAttribsARB != NULL)
m_hrc = wglCreateContextAttribsARB(pDC->m_hDC,0, attribs);
if (!m_hrc)
m_hrc = tempContext;
else
{
wglMakeCurrent(pDC->m_hDC,m_hrc);
wglDeleteContext(tempContext);
}
InitAPI();
wglMakeCurrent(NULL,NULL);
return true;
}
|
Step 3. Check debug log. This extension can work in two modes:
- immediately call your callback function, when the exception happens, or
- collect all exception into debug log.
First we will examine the second mode, because it does not require a definition of callback function and hence it is a little bit easier for the implementation.
To retrieve a debug log we should call function glGetDebugMessageLogARB. The first parameter count is the number of entries we want to read. bufsize is the size of the buffer where message will be loaded, and the pointer to the buffer is sent as the last parameter - messageLog. sources, types, ids, severities and lengths are arrays with at least count elements where sources, types, IDs and lengths of the messages will be stored, respectively. The messageLog consists of separate entries divided by null-characters (which are counted into the length of each message). After reading, entries are automatically deleted from the debug log. To demonstrate reading debug log, we will write function CheckDebugLog() which will read maximum 10 entries from the log. In the real situation it is not a great number, but, frankly, I don’t want to make so many errors just to demonstrate how this extension works. :o) You are free to modify this function to fulfill your needs.
void CGLRenderer::CheckDebugLog()
{
unsigned int count = 10; // max. num. of messages that will be read from the log
int bufsize = 2048;
unsigned int* sources = new unsigned int[count];
unsigned int* types = new unsigned int[count];
unsigned int* ids = new unsigned int[count];
unsigned int* severities = new unsigned int[count];
int* lengths = new int[count];
char* messageLog = new char[bufsize];
unsigned int retVal = glGetDebugMessageLogARB(count, bufsize, sources, types, ids,
severities, lengths, messageLog);
if(retVal > 0)
{
unsigned int pos = 0;
for(unsigned int i=0; i<retVal; i++)
{
DebugOutputToFile(sources[i], types[i], ids[i], severities[i],
&messageLog[pos]);
pos += lengths[i];
}
}
delete [] sources;
delete [] types;
delete [] ids;
delete [] severities;
delete [] lengths;
delete [] messageLog;
} |
Now we got all errors and warnings, but what we should do with all of them? For now we will write them into a file. The function DebugOutputToFile() will do the stuff, and furthermore it will translate enumerates to human readable format.
void CGLRenderer::DebugOutputToFile(unsigned int source, unsigned int type, unsigned int id,
unsigned int severity, const char* message)
{
FILE* f;
f = fopen("Debug.txt","a");
if(f)
{
char debSource[16], debType[20], debSev[5];
if(source == GL_DEBUG_SOURCE_API_ARB)
strcpy(debSource, "OpenGL");
else if(source == GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB)
strcpy(debSource, "Windows");
else if(source == GL_DEBUG_SOURCE_SHADER_COMPILER_ARB)
strcpy(debSource, "Shader Compiler");
else if(source == GL_DEBUG_SOURCE_THIRD_PARTY_ARB)
strcpy(debSource, "Third Party");
else if(source == GL_DEBUG_SOURCE_APPLICATION_ARB)
strcpy(debSource, "Application");
else if(source == GL_DEBUG_SOURCE_OTHER_ARB)
strcpy(debSource, "Other");
if(type == GL_DEBUG_TYPE_ERROR_ARB)
strcpy(debType, "Error");
else if(type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB)
strcpy(debType, "Deprecated behavior");
else if(type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB)
strcpy(debType, "Undefined behavior");
else if(type == GL_DEBUG_TYPE_PORTABILITY_ARB)
strcpy(debType, "Portability");
else if(type == GL_DEBUG_TYPE_PERFORMANCE_ARB)
strcpy(debType, "Performance");
else if(type == GL_DEBUG_TYPE_OTHER_ARB)
strcpy(debType, "Other");
if(severity == GL_DEBUG_SEVERITY_HIGH_ARB)
strcpy(debSev, "High");
else if(severity == GL_DEBUG_SEVERITY_MEDIUM_ARB)
strcpy(debSev, "Medium");
else if(severity == GL_DEBUG_SEVERITY_LOW_ARB)
strcpy(debSev, "Low");
fprintf(f,"Source:%s\tType:%s\tID:%d\tSeverity:%s\tMessage:%s\n",
debSource,debType,id,debSev,message);
fclose(f);
}
} |
To find out what we have made wrong in our code, we will put CheckDebugLog() call into DestoyScene(), and trigger it by closing application.
void CGLRenderer::DestroyScene(CDC *pDC)
{
wglMakeCurrent(pDC->m_hDC, m_hrc);
//--------------------------------
// ... Deallocation ...
CheckDebugLog();
wglMakeCurrent(NULL,NULL);
//--------------------------------
if(m_hrc)
{
wglDeleteContext(m_hrc);
m_hrc = NULL;
}
} |
If we didn’t do anything wrong, there will be no Debug.txt file. So, let’s be destructive and start garbling the code. ;o)
The easiest way is just to change VAO ID to something meaningless. For example 5.
void CGLRenderer::DestroyScene(CDC *pDC)
{
// ...
//glBindVertexArray(m_vaoID);
glBindVertexArray(5); // wrong ID for VAO
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
// ...
} |
Now we have a nice log like this:
Source:OpenGL Type:Error ID:1282 Severity:High Message:GL_INVALID_OPERATION error generated.
Function DebugOutputToFile() lists all sources, types, and severities of the messages. Details can be found in the spec.
We did it! Now we can claim that debug output works for us. And if you satisfied with this, you are free to skip the rest of the tutorial.
Step 4. Defining a callback function. Don’t be afraid. This is also a child play. Just define a callback function and call. I don’t like global functions, so I’ll define a static member of CGLRenderer class. The signature (mean the parameters list) must be as GL requires.
class CGLRenderer
{
public:
static void CALLBACK DebugCallback(unsigned int source, unsigned int type,
unsigned int id, unsigned int severity,
int length, const char* message, void* userParam);
|
The implementation is not significant now, so we will just call DebugOutputToFile()
void CGLRenderer::DebugCallback(unsigned int source, unsigned int type, unsigned int id,
unsigned int severity, int length,
const char* message, void* userParam)
{
DebugOutputToFile(source, type, id, severity, message);
} |
and register this function by calling glDebugMessageCallbackARB. The proper position is, for example, in the PrepareScene() before other GL function calls.
void CGLRenderer::PrepareScene(CDC *pDC)
{
wglMakeCurrent(pDC->m_hDC, m_hrc);
//...
glDebugMessageCallbackARB(&CGLRenderer::DebugCallback, NULL);
//...
}
|
The second parameter of glDebugMessageCallbackARB is an argument that will be proceeded to your callback function. This is a fairly simple example, and we do not need additional parameters to distinguish the context in which the error occurred.
If the callback function is used, each error/warning triggers it and no messages are written into debug log. If we want to unregister callback function, just call
glDebugMessageCallbackARB(NULL, NULL); |
and everything will be like before (messages will be written into debug log). But be careful. Debug log has its limits. If it is full, no new messages will be written. Oh, well, it is not such a big thing. In any case we should resolve all problems. But the problem is how to locate where in the code the error is. This extension does not give the answer to this question.
Step 5. Controlling the volume of debug output. If we want to disable some of the error/warning outputs, we should call function glDebugMessageControlARB(enum source, enum type, enum severity, sizei count, const uint* ids, boolean enabled). The last parameter defies if those outputs are enabled (if TRUE) or disabled (if FALSE). By default all messages are enabled.
Step 6. Defining own messages. The last level of using debug output extension is defining messages that will be emitted by your application or (third-party) library. To accomplish this, an application should call function glDebugMessageInsertARB (enum source, enum type, uint id, enum severity, int length, const char* buf). Let’s define our messaging function.
void CGLRenderer::EmitGLError(unsigned int id, char* message, int length)
{
glDebugMessageInsertARB(GL_DEBUG_SOURCE_APPLICATION_ARB, GL_DEBUG_TYPE_ERROR_ARB, id,
GL_DEBUG_SEVERITY_HIGH_ARB, length, message);
} |
If we call it wit the proper string (somewhere in the DestroyScene())
EmitGLError(1U, "The end is near!", 17); |
we will get the following record:
Source:Application Type:Error ID:1 Severity:High Message:The end is near!
Of course, the message was just for announcing the end of the tutorial, not the year of 2012. ;o)
Step 7. Synchronous debug output. This is the greatest thing that this extension has brought to us! Everything previously mentioned just enables us to know that there is an error, but where it is we don't know. But if we enable synchronous debug output by
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
and set a breakpoint in our callback function, whenever the debug output report is emitted our application will be stopped (by calling a callback function) and we will know exactly what made the trouble (because this call is activated before the problematic function finishes).

|
|