Introduction to OpenGL 4.1 - Tutorial 05

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).
 

Free Hit Counters

Comments