调试输出 Debug Output
OpenGL为应用程序开发者提供了调试输出功能,以便在执行GL命令时获取错误、未定义行为、性能警告等重要信息。开发人员可以通过回调函数或查询消息日志来接收这些调试消息,并且可以控制哪些消息被显示或隐藏,甚至插入自定义的应用程序生成的消息。
调试消息的生成受上下文创建方式的影响。如果创建的是一个带有CONTEXT_FLAG_DEBUG_BIT标志的调试上下文,则系统会自动启用DEBUG_OUTPUT功能并确保生成完整的调试输出;若非调试上下文,默认情况下可能不会生成任何调试消息,即使后续手动启用DEBUG_OUTPUT,其具体输出级别也将由OpenGL实现决定,可能会完全没有调试信息。
因此,要确保获得全面的调试支持,应使用设置了调试标志的OpenGL上下文。
Debug Output Message Source | Messages Generated by |
---|---|
DEBUG_SOURCE_API | The GL |
DEBUG_SOURCE_SHADER_COMPILER | The GLSL shader compiler or compilers for other extension-provided languages |
DEBUG_SOURCE_WINDOW_SYSTEM | The window system, such as WGL or GLX |
DEBUG_SOURCE_THIRD_PARTY | External debuggers or third-party middleware libraries |
DEBUG_SOURCE_APPLICATION | The application |
DEBUG_SOURCE_OTHER | Sources that do not fit to any of the ones listed above |
Table 20.1 : Sources
of debug output messages
Debug Output Message Type | Informs about |
---|---|
DEBUG_TYPE_ERROR | Events that generated an error |
DEBUG_TYPE_DEPRECATED_BEHAVIOR | Behavior that has been marked for deprecation |
DEBUG_TYPE_UNDEFINED_BEHAVIOR | Behavior that is undefined according to the specification |
DEBUG_TYPE_PERFORMANCE | Implementation-dependent performance warnings |
DEBUG_TYPE_PORTABILITY | Use of extensions or shaders in a way that is highly vendor-specific |
DEBUG_TYPE_MARKER | Annotation of the command stream |
DEBUG_TYPE_PUSH_GROUP | Entering a debug group |
DEBUG_TYPE_POP_GROUP | Leaving a debug group |
DEBUG_TYPE_OTHER | Types of events that do not fit any of the ones listed above |
Table 20.2: Types
of debug output messages
Severity Level Token | Suggested examples of messages |
---|---|
DEBUG_SEVERITY_HIGH | Any GL error; dangerous undefined behavior; any shader compiler and linker errors; |
DEBUG_SEVERITY_MEDIUM | Severe performance warnings; GLSL or other shader compiler and linker warnings; use of currently deprecated behavior |
DEBUG_SEVERITY_LOW | Performance warnings from redundant state changes; trivial undefined behavior |
DEBUG_SEVERITY_NOTIFICATION | Any message which is not an error or performance concern |
Table 20.3: Severity levels of messages
调试消息 Debug Messages
每条调试消息都具有以下独特标识属性:
- 消息源:从预定义的符号常量列表中选取,表示消息产生的具体模块或阶段。
- 消息类型:同样由一组符号常量定义,代表不同类别或类型的事件,如错误、警告等。
- ID:在每个消息源和类型对所构成的独立命名空间内分配的一个无符号整数,用于唯一标识该命名空间内的特定消息。
由于不同的消息源和类型可能有重叠的ID范围,因此要完全区分两条消息,必须同时考虑其消息源、类型和ID三者组合。
此外,每条消息还具备:
- 严重级别:一个全局统一的轴上的级别分类,用以大致描述消息的重要性。开发者可以根据严重级别来过滤并控制输出的数量。
- 字符串描述:每条消息都有一个终止符为null的文本说明,虽然格式随实现而异,但应包含触发消息的具体事件描述,并且即使同一类消息的不同实例,其字符串内容也应有足够的差异以便区分。
最后,所有消息(包括终止符)的长度不超过实现相关的最大长度常量MAX_DEBUG_MESSAGE_LENGTH,并且每条消息可以启用或禁用,初始状态下除了严重级别为DEBUG_SEVERITY_LOW的消息外,其他消息默认启用。DebugMessageControl
命令可用于更改单个消息的状态(启用或禁用)。
调试消息回调 Debug Message Callback
应用程序可以通过调用以下命令来设置接收调试消息的回调函数:
void glDebugMessageCallback(DEBUGPROC callback, const void *userParam);
其中,callback
存储回调函数的地址。回调函数原型必须为:
-
void callback(enum source, enum type, uint id, enum severity, sizei length, const char *message, const void *userParam);
source
:消息源,表示消息来自哪个部分,例如OpenGL库、第三方库等。type
:消息类型,表示消息的类型,如错误、警告、性能信息等。id
:消息ID,用于标识特定的消息。severity
:消息的严重程度,表示消息的重要性。length
:消息字符串的长度。message
:包含消息内容的字符串。userParam
:用户参数,可以是任意用户指定的数据,会在每次回调时传递给回调函数。
在GL调用回调函数执行代码时,应用程序需注意一些特殊情况,无论调试来源如何:
message
指向的内存由GL拥有和管理,在回调函数调用期间才有效。- 从回调函数内部调用任何GL或窗口系统函数的行为未定义,可能会导致程序终止。
- 在多线程GL实现中,对于异步调试输出的安全调试回调也需特别小心处理。第20.8节对此进行了更详细的描述。
如果DEBUG_OUTPUT
被禁用,则GL不会调用回调函数。
调试消息日志 Debug Message Log
当OpenGL的DEBUG_CALLBACK_FUNCTION
设置为NULL
时,调试消息会被存储在每个上下文内部的一个有限容量的消息日志中,其大小上限由MAX_DEBUG_LOGGED_MESSAGES
定义。如果日志已满,则新产生的消息会直接丢弃,不再添加到日志。应用程序可通过查询DEBUG_LOGGED_MESSAGES
和DEBUG_NEXT_LOGGED_MESSAGE_LENGTH
来获取当前日志的状态,并使用GetDebugMessageLog
函数从日志提取调试信息。
若DEBUG_CALLBACK_FUNCTION
被设置为非NULL
值并且DEBUG_OUTPUT
启用,调试消息将不会保存至日志,而是立即传递给回调函数进行实时处理,绕过日志机制。
控制调试消息 Controlling Debug Messages
void glDebugMessageControl( enum source, enum type, enum severity, sizei count, const uint *ids, boolean enabled );
控制活动调试组(参见第20.6节)的调试输出量。如果enabled
参数为TRUE,则引用的消息子集将被启用;若为FALSE,则这些消息将被禁用。
该命令可通过以下方式引用不同消息子集:
- 如果
source
、type
或severity
参数为DONT_CARE
,则分别引用来自所有源、所有类型或所有严重级别的消息。 - 当指定了非
DONT_CARE
值时,匹配指定source
、type
或severity
的所有消息将被引用。 - 若
count
大于零,则ids
是一个包含source
和type
组合下指定数量消息ID的数组。在这种情况下,source
和type
不能为DONT_CARE,而severity
必须是DONT_CARE
。在ids
中未识别的消息ID将被忽略。如果count
为零,则忽略ids
的值。
尽管消息按照其来源和类型构成了一个隐含的层级结构,但并没有针对每种来源、每种类型或每种严重级别设置显式的启用状态。相反,每个消息都有独立存储的启用状态。一次性禁用某个来源的所有消息与使用类型和ID逐个禁用该来源的所有消息,在效果上并无区别。
如果DEBUG_OUTPUT被禁用,则等同于禁用了所有来源、类型或严重级别的消息。
外部生成的消息 Externally Generated Messages
为了支持应用程序和第三方库生成自己的消息,例如包含时间戳信息或有关特定渲染系统事件的信号的消息,可以调用以下函数
void glDebugMessageInsert( enum source, enum type, uint id, enum severity, int length, const char *buf );
id 的值指定消息的 ID,severity 指示调用方定义的严重性级别。 字符串 buf 包含消息的字符串表示形式。 参数length包含buf中的字符数。 如果 length 为负数,则暗示 buf 包含一个以 null 结尾的字符串。
调试组 Debug Groups
调试组提供了一种使用描述性文本对命令流进行注释,将一组离散的命令分组的方法。调试输出消息可以是由实现生成的,也可以是由应用程序使用 DebugMessageInsert 插入的,这些消息将被写入活动调试组(调试组堆栈的顶部)。调试组是严格分层的。它们的序列可以嵌套在其他调试组中,但不能重叠。如果应用程序没有推送任何调试组,则活动调试组为默认调试组。
-
void glPushDebugGroup( enum source, uint id, sizei length, const char *message );
- 用于向命令流中推送一个调试组,该调试组由指定的消息描述。以下是参数及其工作方式的详细说明:
source
:指定调试消息的来源,表示消息的原始来源。id
:指定调试组内生成的消息的ID。这对于在调试输出中识别特定消息很有用。length
:指示消息字符串中的字符数。如果长度为负数,则表示消息字符串以空字符结尾。message
:指向包含调试组描述文本的字符串的指针。
-
void glPopDebugGroup( void );
调用 PushDebugGroup
时,它会使用提供的消息字符串、来源和ID创建一个新的调试组。调试组的类型是 DEBUG_TYPE_PUSH_GROUP
,其严重性是 DEBUG_SEVERITY_NOTIFICATION
。
OpenGL 实现维护着一个调试组堆栈。当推送新的调试组时,它成为活动调试组,并继承来自之前位于堆栈顶部的调试组的调试输出音量控制。调试输出音量的任何控制设置都适用于活动调试组及其上推的任何调试组。
由于调试组是分层的,调试输出音量的控制范围限于活动调试组及其子级。这种分层结构允许根据渲染命令的上下文对调试输出设置进行细粒度控制。
调试标签 Debug Labels
调试标签提供了一种用描述性文本标签注释任何对象(纹理、缓冲区、着色器等)的方法。然后,这些标签可以被调试输出(参见第20章)或外部工具(如调试器或分析器)使用,以描述标记的对象。
-
void glObjectLabel( enum identifier, uint name, sizei length, const char *label );
identifier
是一个枚举值,指示对象的类型,必须是下表中的一个标记。它表示了要附加标签的对象类型。name
是要标记的对象的名称或标识符。length
表示标签字符串的长度。如果是负数,那么标签字符串应当以空字符结尾。label
是一个指向描述性标签字符串的指针。如果 label 为 NULL,则意味着从对象中移除任何调试标签。
Identifier | Object Type |
---|---|
BUFFER | buffer |
FRAMEBUFFER | framebuffer |
PROGRAM_PIPELINE | program pipeline |
PROGRAM | program |
QUERY | query |
RENDERBUFFER | renderbuffer |
SAMPLER | sampler |
SHADER | shader |
TEXTURE | texture |
TRANSFORM_FEEDBACK | transform feedback |
VERTEX_ARRAY | vertex array |
Table 20.4: Object namespace identifiers and the corresponding object types.
void glObjectPtrLabel( void *ptr, sizei length, const char *label );
标记由ptr
标识的同步对象。length
和label
匹配ObjectLabel
的相应参数。
标签是与之相关联的对象状态的一部分。对象标签的初始状态是空字符串。标签不需要唯一。
异步和同步调试输出 Asynchronous and Synchronous Debug Output
DEBUG_OUTPUT_SYNCHRONOUS
是一个状态,它影响 OpenGL 驱动程序生成调试消息的方式和时机。它有两种状态:启用和禁用。
-
当
DEBUG_OUTPUT_SYNCHRONOUS
被禁用时:- 驱动程序允许异步调用调试回调函数,可能来自多个线程,甚至是当前上下文未绑定到生成消息的线程。
- 调试消息可能在生成消息的 GL 命令返回后异步地调用回调函数。
- 应用程序需要确保调试回调线程安全,可以使用 userParam 帮助识别命令的来源。
-
当
DEBUG_OUTPUT_SYNCHRONOUS
被启用时:- 驱动程序保证回调函数由当前上下文同步调用。
- 所有对回调函数的调用都由拥有当前上下文的线程执行,并且在生成调试消息的 GL 命令返回之前执行。
- 启用同步调试输出简化了应用程序的线程安全性责任,但可能会导致驱动程序性能下降。
此外,启用 DEBUG_OUTPUT_SYNCHRONOUS
只保证了由当前上下文生成的消息的回调的上下文内同步,不保证跨多个上下文的同步。因此,如果应用程序使用多个上下文,则需要确保每个上下文的回调函数都是线程安全的,即使所有上下文都启用了 DEBUG_OUTPUT_SYNCHRONOUS
。
调试输出查询 Debug Output Queries
用调试输出命令设置的指针可以用通用的glGetPointerv
命令查询。pnames
DEBUG_CALLBACK_FUNCTION
和DEBUG_CALLBACK_USER_PARAM
分别查询当前回调函数和glDebugMessageCallback
函数集的用户参数。
当没有设置调试回调时,调试消息存储在调试消息日志中,如第20.3节所述。可以通过调用从日志中查询消息
int glGetDebugMessageLog( uint count, sizei bufSize, enum *sources, enum *types, uint *ids, enum *severities, sizei *lengths, char *messageLog );
GetDebugMessageLog
函数从消息日志中获取最多 count
条消息,并返回成功获取的消息数量。
-
消息将按照从最旧到最新的顺序进行获取,并从日志中移除已获取的消息。
-
获取的消息的来源、类型、严重性、ID 和字符串长度将分别存储在由应用程序提供的数组
sources
、types
、severities
、ids
和lengths
中。应用程序需要为每个数组分配足够的空间,以容纳最多count
个元素。 -
所有获取的消息的字符串表示将存储在
messageLog
数组中。如果获取了多条消息,则它们的字符串将连接到同一个messageLog
数组中,并用单个空终止符进行分隔。数组中的最后一个字符串也将以空终止符结尾。 -
messageLog
的最大大小(包括所有空终止符使用的空间)由bufSize
给出。 -
如果消息的字符串(包括空终止符)无法完全适应
messageLog
数组中剩余的空间,则该消息及后续消息将不会被获取,并将保留在日志中。数组lengths
中存储的字符串长度包括每个字符串的空终止符的空间。 -
当
sources
、types
、ids
、severities
、lengths
和messageLog
数组中的任何一个或全部是 NULL 指针时,将丢弃这些数组的属性,但是这些消息仍将从日志中删除。因此,如果应用程序只想从消息日志中删除最多count
条消息而忽略它们的属性,则可以将所有属性数组的指针传递给GetDebugMessageLog
的参数,并将它们设为 NULL。 -
如果上下文不是调试上下文,则 OpenGL 可能选择永远不向消息日志中添加消息,因此
GetDebugMessageLog
将始终返回零。 -
void glGetObjectLabel( enum identifier, uint name, sizei bufSize, sizei *length, char *label );
- 命令用于获取对象的标签字符串。
identifier
和name
参数指定了对象的命名空间和名称,它们与ObjectLabel
函数的相应参数相匹配。label
参数中将返回对象的标签字符串。- 标签字符串将以空终止符结尾,并存储在
label
中。 - 返回的标签字符串的实际字符数(不包括空终止符)将存储在
length
参数中。如果length
是 NULL,则不返回长度信息。 - 可以通过
bufSize
参数指定label
可以容纳的最大字符数(包括空终止符)。 - 如果对象没有指定调试标签,则
label
将包含一个空的、以空终止符结尾的字符串,并且length
中返回 0。 - 如果
label
是 NULL,但length
不是 NULL,则不会返回字符串,但字符串的长度将返回到length
中。
-
void glGetObjectPtrLabel( void *ptr, sizei bufSize, size *length, char *label );
- 在label中返回标记由ptr标识的同步对象的字符串。bufSize、length和label与GetObjectLabel的相应参数相匹配。