Debug Output is an amazing OpenGL feature that makes error checking way easier and much more convenient!

Note that this feature has only been a core feature since OpenGL 4.3! Though even if you're developing and supporting prior versions, then you should definitely use version 4.3 just for debugging if possible.

Compared to glGetError, debug output not only tells you about the errors but also sends a message explaining the cause of the error (or simply the cause of the debug message). The severity level is also send, so that you can see how serious the generated debug message should be taken and the source of it!

Here's the GL_ARB_debug_output specs!

The above output was generated by erroneously calling glBindBuffer(GL_ARRAY_BUFFER, -1).


OS X

On Reddit /u/sockstream pointed out that debug output isn't supported on OS X yet! Though Apple have made their own OpenGL Profiler which could be the next best thing!


Context Creation

You need to specify a flag/hint before you create the OpenGL context. To tell OpenGL that you will be using Debug Output. When you release the software remember to disable this by default, as this can impact performance a bit!

WGL (Windows)

Creating an OpenGL Context on Windows, which supports debug output. Requires you to set the WGL_CONTEXT_DEBUG_BIT_ARB flag in WGL_CONTEXT_FLAGS_ARB before calling wglCreateContextAttribs.

WGL_ARB_create_context specs!

GLX (Linux)

Creating an OpenGL Context using GLX, which supports debug output. Requires you to set the GLX_CONTEXT_DEBUG_BIT_ARB flag in GLX_CONTEXT_FLAGS_ARB before calling glXCreateContextAttribsARB.

GLX_ARB_create_context specs!

GLFW

With GLFW you must call the following before calling glfwCreateWindow.

glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
SDL

As with SDL you must specify the following attribute, before calling SDL_SetVideoMode.

SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
QT

With QT you need to specify you want a debug context in the QSurfaceFormat object, by doing:

QSurfaceFormat format;
...
format.setProfile(QSurfaceFormat::CoreProfile);
format.setOption(QSurfaceFormat::DebugContext);
GLUT & FreeGLUT

You need to do the following, before calling glutMainLoop.

glutInitContextProfile(GLUT_CORE_PROFILE);
glutInitContextFlags(GLUT_DEBUG);
WebGL

If you're using WebGL then this is already the end of the line for you! This is just too different from the rest, so I'm going to let Khronos explain how that is done. As they've already written a great page on how to setup a debug context with WebGL!

Android (OpenGL ES)

On Android you can't directly create a debug context. But you can enable a few debug flags when creating the GLSurfaceView object.

GLSurfaceView surface_view;
...
surface_view.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR | GLSurfaceView.DEBUG_LOG_GL_CALLS);

GLSurfaceView reference!

Checking Context

When the context is created you can check if the context actually supports debug output. You do that by doing the following:

GLint flags;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);

if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
    // Debug Output available
}

Registering OpenGL Debug Callback

When the context is created and we know it supports debug messages. Then we can enable and initialize it by doing the following:

glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(debugMessage, NULL);

glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);

GL_DEBUG_OUTPUT - If enabled, debug messages are produced by a debug context. When disabled, the debug message log is silenced. Note that in a non-debug context, very few, if any messages might be produced, even when GL_DEBUG_OUTPUT is enabled.

GL_DEBUG_OUTPUT_SYNCHRONOUS? - If enabled, debug messages are produced synchronously by a debug context. If disabled, debug messages may be produced asynchronously. In particular, they may be delayed relative to the execution of GL commands, and the debug callback function may be called from a thread other than that in which the commands are executed. See glDebugMessageCallback.

This means that enabling GL_DEBUG_OUTPUT_SYNCHRONOUS ensures us that the generated debug messages won't be thrown concurrently!

When [GL_]DEBUG_OUTPUT_SYNCHRONOUS is enabled, the driver guarantees synchronous calls to the callback routine by the context. When synchronous callbacks are enabled, all calls to the callback routine will be made by the thread that owns the current context; all such calls will be made serially by the current context; and each call will be made before the GL command that generated the debug message is allowed to return.

source (5.5.7 - Asynchronous and Synchronous Debug Output)

Callback Function

The message with id 131185 is an annoying Nvidia driver message. Which is generated when glBufferData was successful!

void APIENTRY debugMessage(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
{
    // Some debug messages are just annoying informational messages
    switch (id)
    {
    case 131185: // glBufferData
        return;
    }

    printf("Message: %s\n", message);
    printf("Source: ");

    switch (source)
    {
    case GL_DEBUG_SOURCE_API:
        printf("API");
        break;
    case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
        printf("Window System");
        break;
    case GL_DEBUG_SOURCE_SHADER_COMPILER:
        printf("Shader Compiler");
        break;
    case GL_DEBUG_SOURCE_THIRD_PARTY:
        printf("Third Party");
        break;
    case GL_DEBUG_SOURCE_APPLICATION:
        printf("Application");
        break;
    case GL_DEBUG_SOURCE_OTHER:
        printf("Other");
        break;
    }

    printf("\n");
    printf("Type: ");

    switch (type)
    {
    case GL_DEBUG_TYPE_ERROR:
        printf("Error");
        break;
    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
        printf("Deprecated Behavior");
        break;
    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
        printf("Undefined Behavior");
        break;
    case GL_DEBUG_TYPE_PORTABILITY:
        printf("Portability");
        break;
    case GL_DEBUG_TYPE_PERFORMANCE:
        printf("Performance");
        break;
    case GL_DEBUG_TYPE_MARKER:
        printf("Marker");
        break;
    case GL_DEBUG_TYPE_PUSH_GROUP:
        printf("Push Group");
        break;
    case GL_DEBUG_TYPE_POP_GROUP:
        printf("Pop Group");
        break;
    case GL_DEBUG_TYPE_OTHER:
        printf("Other");
        break;
    }

    printf("\n");
    printf("ID: %d\n", id);
    printf("Severity: ");

    switch (severity)
    {
    case GL_DEBUG_SEVERITY_HIGH:
        printf("High");
        break;
    case GL_DEBUG_SEVERITY_MEDIUM:
        printf("Medium");
        break;
    case GL_DEBUG_SEVERITY_LOW:
        printf("Low");
        break;
    case GL_DEBUG_SEVERITY_NOTIFICATION:
        printf("Notification");
        break;
    }

    printf("\n\n");
}
Insert Debug Messages

You can also push your own debug messages using glDebugMessageInsert.

glDebugMessageInsert(
        GL_DEBUG_SOURCE_APPLICATION,
        GL_DEBUG_TYPE_ERROR,
        0,
        GL_DEBUG_SEVERITY_NOTIFICATION,
        -1,
        "Very dangerous error");

length contains a count of the characters in the character array whose address is given in message. If length is negative then message is treated as a null-terminated string. The length of the message, whether specified explicitly or implicitly, must be less than or equal to the implementation defined constant GL_MAX_DEBUG_MESSAGE_LENGTH.


Edit

Thanks to Daniel in the comments for pointing this out!

Apparently there was a bug in the OpenGL headers which meant that const void *userParam previously was defined as void *userParam. So if you're getting any errors defining the function try to add or remove the const keyword from the parameter.

(v17, 2013-07-08, Jon Leech)
    - Change type of userParam parameter to DebugMessageCallbackARB and
      GLDEBUGPROCARB from 'void *' to 'const void *' to match GL core
      and KHR_debug (Bug 10083).

source (Revision History)