Debug Output

From OpenGL Wiki
Revision as of 22:47, 1 February 2015 by Alfonse (talk | contribs) (→‎Object names: Moving section to its own page.)
Jump to navigation Jump to search
Debug Output
Core in version 4.6
Core since version 4.3
Core ARB extension KHR_debug
ARB extension ARB_debug_output
Vendor extension AMD_debug_output

Debug Output is an OpenGL feature that makes Error Checking from functions easier.

Note that this feature, which is core in 4.3, has functionality that is generally exposed by two different extensions. The KHR extension is the core feature, which is always advertised when available. The other two are generally only advertised when the OpenGL Context was created with the WGL/GLX_DEBUG_CONTEXT_BIT.

This page describes the KHR/core functionality. The ARB/AMD version contains only the message log part, without the object names or the scoped messages. They also have some slight differences in the callback function parameters and the types of messages they work with.

Message

The heart of debug output is a message. Messages are generated when certain things happen in the OpenGL implementation; the user can even generate message of their own.

In order to have messages generated at all, debug output must first be enabled. If the context is a debug context (ie: GL_CONTEXT_FLAG_DEBUG_BIT is part of the GL_CONTEXT_FLAGS state), then debug output is already enabled. Otherwise it starts disabled and must be enabled with an explicit glEnable(GL_DEBUG_OUTPUT).

Messages can come from a variety of sources, but they each contain the following data:

  • The source that produced the message, by GLenum.
  • The type of message, by GLenum.
  • The message severity (how important it is), by GLenum.
  • An ID, as a GLuint.
  • A null-terminated string describing the message.

Implementations may create messages at their discretion. In debug contexts, the implementation is required to generate messages for OpenGL Errors and GLSL compilation/linking failures. Beyond these, whether additional warnings and so forth are generated is a matter of the implementation's discretion and quality.

If the context isn't a debug context, then the implementation is free to provide fewer messages than debug contexts, or none at all. It may even swallow up user-generated messages. So messages are only guaranteed when using a debug context.

Message Sources
Source enum Generated by
DEBUG_SOURCE_API Calls to the OpenGL API
DEBUG_SOURCE_WINDOW_SYSTEM Calls to a window-system API
DEBUG_SOURCE_SHADER_COMPILER A compiler for a shading language
DEBUG_SOURCE_THIRD_PARTY An application associated with OpenGL
DEBUG_SOURCE_APPLICATION Generated by the user of this application
DEBUG_SOURCE_OTHER Some source that isn't one of these
Message Types
Type enum Meaning
DEBUG_TYPE_ERROR An error, typically from the API
DEBUG_TYPE_DEPRECATED_BEHAVIOR Some behavior marked deprecated has been used
DEBUG_TYPE_UNDEFINED_BEHAVIOR Something has invoked undefined behavior
DEBUG_TYPE_PORTABILITY Some functionality the user relies upon is not portable
DEBUG_TYPE_PERFORMANCE Code has triggered possible performance issues
DEBUG_TYPE_MARKER Command stream annotation
DEBUG_TYPE_PUSH_GROUP Group pushing
DEBUG_TYPE_POP_GROUP foo
DEBUG_TYPE_OTHER Some type that isn't one of these
Message Severity
Severity enum Meaning
DEBUG_SEVERITY_HIGH All OpenGL Errors, shader compilation/linking errors, or highly-dangerous undefined behavior
DEBUG_SEVERITY_MEDIUM Major performance warnings, shader compilation/linking warnings, or the use of deprecated functionality
DEBUG_SEVERITY_LOW Redundant state change performance warning, or unimportant undefined behavior
DEBUG_SEVERITY_NOTIFICATION Anything that isn't an error or performance issue.

User messages

While most debug output messages are generated by the OpenGL implementation, it is possible to generate messages from outside of the implementation. These messages are generated by the following command:

glDebugMessageInsert(GLenum source​, GLenum type​, GLuint id​, GLenum severity​, GLsizei length​, const char *message​)

The source​ represents who is sending the message. However, in order to be able to differentiate between user-created messages and implementation-generated ones, the source​ must be either GL_DEBUG_SOURCE_APPLICATION or GL_DEBUG_SOURCE_THIRD_PARTY. These sources will never be used for implementation-generated messages. The GL_DEBUG_SOURCE_APPLICATION represents a message generated by the application, while GL_DEBUG_SOURCE_THIRD_PARTY generally represents some code that is interposing itself between the implementation and the application.

The type​ can be any of the message types listed above, and the severity​ can be any of those severities. The id​ is purely for the benefit of the user and may be any unsigned 32-bit integer value. The message​ and length​ represent the text string to be stored. length​ may be negative, which means that message​ must be NULL-terminated. The length of the message (either null-terminated or length-specified) must be less than GL_MAX_DEBUG_MESSAGE_LENGTH.

Getting messages

Messages, once generated, can either be stored in a log or passed directly to the application via a callback function. If a callback is registered, then the messages are not stored in a log.

Messages can be routed to a callback via this function:

void glDebugMessageCallback(DEBUGPROC callback​, void* userParam​);

The type of callback​ is a function of the form:

typedef void APIENTRY funcname(GLenum source​, GLenum type​, GLuint id​,
   GLenum severity​, GLsizei length​, const GLchar* message​, const void* userParam​);

source​, type​, and severity​ are the message's source, type, and severity enumerators. id​ is the message's identifier integer. length​ is the length of the message​ string, excluding the null-terminator.

The userParam​ that is registered with glDebugMessageCallback will be passed to the given callback function with each message. This is useful for storing arbitrary data (like a C++ object) along with the function pointer.

The callback can be called synchronously or asynchronously. This is controlled by the glEnable flag GL_DEBUG_OUTPUT_SYNCHRONOUS. If this flag is enabled, then OpenGL guarantees that your callback will be called:

  • In the same thread as the context.
  • In the scope of the OpenGL function call that fired the message.

If the flag is disabled, then none of these are guaranteed. So if you want asynchronous debug output (for performance reasons), you must take into account the following possibilities:

  • The thread the callback is called on is not the context's thread. Indeed, an OpenGL context may not even be current in the callback's thread, which means that you cannot call any OpenGL function without ensuring the status of the thread's current context.
  • Multiple instances of the same callback function may be called at the same time, obviously from different threads.
  • Messages may be issued out of the order of their occurrence.

Logging

If no callback is registered, then messages are stored in a log. This log has a fixed, implementation defined length of GL_MAX_DEBUG_LOGGED_MESSAGES message entries. If the log is full and more messages are generated, then the new messages will be discarded.

Warning: GL_MAX_DEBUG_LOGGED_MESSAGES is allowed to be as few as one, so it would be unwise to rely on logging without at least verifying that the message log is of reasonable length. So it's more reliable to build your own log.

Each context maintains its own message log queue for commands executed in that context. Logging follows the same synchronization rules as for the callback. So if you need immediate results in the log, or if the order of messages needs to be guaranteed, you must use synchronous behavior.

Messages from the context's log can be fetched with this function:

GLuint glGetDebugMessageLog(GLuint count​, GLsizei bufSize​,
   GLenum *sources​, GLenum *types​, GLuint *ids​, GLenum *severities​,
   GLsizei *lengths​, GLchar *messageLog​);

count​ is the number of messages that you want to try to fetch. The return value is the number of messages actually fetched. Any messages successfully fetched will be removed from the message log.

sources​, types​, ids​, severities​, lengths​ are arrays that must be at least count​ in size. For each successfully extracted message, an entry in these arrays will be filled in with the appropriate message data. The lengths​ are the lengths of the string for that index's corresponding messages.

The behavior of the character data is more difficult to deal with. messageLog​ is a single array of characters, of at least bufSize​ in size. The function will copy into this string the message strings for each message extracted, separated by null characters (and terminated by one). However, if it is unable to copy a string due to lack of space remaining in bufSize​, then this message, and all subsequent messages, will remain in the log.

Therefore, if you do not provide enough space in messageLog​, you cannot get any messages from the log. You can query the length of the string in the first message of the log with GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH. This includes the null-terminator, so you can use that directly to get at least the first message. If you want to be able to query any number of messages, you can use the implementation-defined GL_MAX_DEBUG_MESSAGE_LENGTH value, which is the maximum length (including null-terminator) of message strings.

Here is an example of code that can get the first N messages:

V · E

This example code shows how to get the first X messages from the debug output log.

void GetFirstNMessages(GLuint numMsgs)
{
	GLint maxMsgLen = 0;
	glGetIntegerv(GL_MAX_DEBUG_MESSAGE_LENGTH, &maxMsgLen);

	std::vector<GLchar> msgData(numMsgs * maxMsgLen);
	std::vector<GLenum> sources(numMsgs);
	std::vector<GLenum> types(numMsgs);
	std::vector<GLenum> severities(numMsgs);
	std::vector<GLuint> ids(numMsgs);
	std::vector<GLsizei> lengths(numMsgs);

	GLuint numFound = glGetDebugMessageLog(numMsgs, msgs.size(), &sources[0], &types[0], &ids[0], &severities[0], &lengths[0], &msgData[0]);

	sources.resize(numFound);
	types.resize(numFound);
	severities.resize(numFound);
	ids.resize(numFound);
	lengths.resize(numFound);

	std::vector<std::string> messages;
	messages.reserve(numFound);

	std::vector<GLchar>::iterator currPos = msgData.begin();
	for(size_t msg = 0; msg < lengths.size(); ++msg)
	{
		messages.push_back(std::string(currPos, currPos + lengths[msg] - 1));
		currPos = currPos + lengths[msg];
	}
}


Message filtering

Implementations can emit a lot of messages. As such, it is often useful to be able to filter out certain messages. This can be done using this function:

void glDebugMessageControl(GLenum source​, GLenum type​, GLenum severity​, GLsizei count​, const GLuint *ids​, GLboolean enabled​);

This function has two modes of use: It can be used to filter out a combination of source​/type​/severity​, or it can be used to filter out a specific set of messages using their ids​.

To use it in the first mode, set source​/type​/severity​ to the combination whose filtering options are to be modified. GL_DONT_CARE can be used as any of the three arguments as a wildcard. Depending on whether enabled​ is GL_TRUE or GL_FALSE, the messages matching the filter will be emitted or ignored (respectively.) If the function is being used for this purpose, then count​ should be zero (and ids​ will thus be ignored).

To use it in the second mode (where specific message IDs are blocked), source​ and type​ may not be set to GL_DONT_CARE, but severity​ must be GL_DONT_CARE. The specific IDs to be affected are to be passed in ids​, which should point to an array of at least count​ ID numbers. Since messages are uniquely identified by their type​, severity​, and id​, this mode of use affects only the specific set of messages specified.

Scoping messages

It often requires multiple OpenGL commands in order to achieve a certain desired effect. Therefore, it is frequently useful to designate that a sequence of commands pertains to some particular activity. This would cause any messages generated by those commands to be annotated with that user-defined activity.

OpenGL provides a relatively simple scoping mechanism to define such activity. It is basically a customized mechanism for user-specified messages, except that it is tied into a stack that ensures that, for every opening message, there is a corresponding closing one. The user-provided string will be augmented with information about starting/ending a group. Also, the severity of the message will always be GL_DEBUG_SEVERITY_NOTIFICATION.

The one piece of functionality that a debug group provides beyond this is message filtering. The filtering function operates on the current group, and child groups inherit the parent group's filter state.

The scoping functions are:

void glPushDebugGroup(GLenum source​, GLuint id​, GLsizei length​, const char * message​);

void glPopDebugGroup(void);

The source​, as for any user-defined message, must be GL_DEBUG_SOURCE_APPLICATION or GL_DEBUG_SOURCE_THIRD_PARTY. The id​ can be any user-defined number. The length​ is the length of the message​ string. The actual text of the message generated by the push command will be based on message​, but the system may augment it with other information (like "Pushed Group:" or something).

The pop command emits a message as well. However, it emits a message that has the same source and id as the corresponding pushed command, and its message string will likewise be based on message​, likely augmented with other text (like "Popped Group:").

There is a maximum depth for the stack of groups, defined by GL_MAX_DEBUG_GROUP_STACK_DEPTH, which must be at least 64.