一、简介
上一节中我们着重介绍了glGetError()函数,包括它的注意事项、用法以及返回值介绍等,本节中我们将继续对OpenGL调试进行更为深入的介绍。
二、OpenGL调试
(Debug Output)的OpenGL拓展十一个非常有用的工具,它在4.3版本之后变为了核心OpenGL的一部分。通过使用调试输出拓展,OpenGL自身会直接发送一个比起更为完善的错误或警告信息给用户。它不仅提供了更多的信息,也能够帮助你使用一个调试器(Debugger)捕捉错误源头。要想开始使用调试输出,我们先要在初始化进程中从OpenGL请求一个调试输出上下文。这个进程根据你的窗口系统会有所不同,这里我们只会讨论在GLFW中配置,但你可以在教程最后的附加资源处找到其它系统的相关资料。
GLFW中的调试输出:
在GLFW中请求一个调试输出非常简单,我们只需要传递一个提醒到GLFW中,告诉它我们需要一个调试输出上下文即可。我们需要在调用glfwCreateWindow之前完成这一请求。
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
一旦GLFW初始化完成,如果我们使用的OpenGL 版本为4.3或以上的话我们就有一个调试上下文了,如果不是的话则祈祷系统仍然能够请求一个调试上下文吧。如果还是不行的话我们必须使用它的OpenGL拓展来请求调试输出。
要检查我们是否成功地初始化了调试上下文,我们可以对OpenGL进行查询:
GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
// 初始化调试输出
}
调试输出工作的方式是这样的,我们首先将一个错误记录函数的回调(类似于GLFW输入的回调)传递给OpenGL,在这个回调函数中我们可以自由地处理OpenGL错误数据,在这里我们将输出一些有用的错误数据到控制台中。下面是这个就是OpenGL对调试输出所期待的回调函数的原型:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar message, void userParam);
注意在OpenGL的某些实现中最后一个参数为const void而不是void。
有了这一大堆的数据,我们可以创建一个非常有用的错误打印工具:
void APIENTRY glDebugOutput(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
void *userParam)
{
// 忽略一些不重要的错误/警告代码
if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return;
std::cout << “---------------” << std::endl;
std::cout << “Debug message (” << id << "): " << message << std::endl;
switch (source)
{
case GL_DEBUG_SOURCE_API: std::cout << “Source: API”; break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: std::cout << “Source: Window System”; break;
case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << “Source: Shader Compiler”; break;
case GL_DEBUG_SOURCE_THIRD_PARTY: std::cout << “Source: Third Party”; break;
case GL_DEBUG_SOURCE_APPLICATION: std::cout << “Source: Application”; break;
case GL_DEBUG_SOURCE_OTHER: std::cout << “Source: Other”; break;
} std::cout << std::endl;
switch (type)
{
case GL_DEBUG_TYPE_ERROR: std::cout << “Type: Error”; break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << “Type: Deprecated Behaviour”; break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: std::cout << “Type: Undefined Behaviour”; break;
case GL_DEBUG_TYPE_PORTABILITY: std::cout << “Type: Portability”; break;
case GL_DEBUG_TYPE_PERFORMANCE: std::cout << “Type: Performance”; break;
case GL_DEBUG_TYPE_MARKER: std::cout << “Type: Marker”; break;
case GL_DEBUG_TYPE_PUSH_GROUP: std::cout << “Type: Push Group”; break;
case GL_DEBUG_TYPE_POP_GROUP: std::cout << “Type: Pop Group”; break;
case GL_DEBUG_TYPE_OTHER: std::cout << “Type: Other”; break;
} std::cout << std::endl;
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH: std::cout << “Severity: high”; break;
case GL_DEBUG_SEVERITY_MEDIUM: std::cout << “Severity: medium”; break;
case GL_DEBUG_SEVERITY_LOW: std::cout << “Severity: low”; break;
case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << “Severity: notification”; break;
} std::cout << std::endl;
std::cout << std::endl;
}
当调试输出检测到了一个OpenGL错误,它会调用这个回调函数,我们将可以打印出非常多的OpenGL错误信息。注意我们忽略掉了一些错误代码,这些错误代码一般不能给我们任何有用的信息(比如NVidia驱动中的131185仅告诉我们缓冲成功创建了)。
调试着色器输出:
对于GLSL来说,我们不能访问像是glGetError这样的函数,也不能通过步进的方式运行着色器代码。如果你得到一个黑屏或者完全错误的视觉效果,通常想要知道着色器代码是否有误会非常困难。是的,我们是有编译错误报告来报告语法错误,但是捕捉语义错误又是一大难题。
一个经常使用的技巧就是将着色器程序中所有相关的变量直接发送到片段着色器的输出通道,以评估它们。通过直接输出着色器变量到输出颜色通道,我们通常可以通过观察视觉结果来获取有用的信息。比如说,如果我们想要检查一个模型的法向量是否正确,我们可以把它们(可以是变换过的也可以是没有变换过的)从顶点着色器传递到片段着色器中,在片段着色器中我们会用以下这种方式输出法向量:
#version 330 core
out vec4 FragColor;
in vec3 Normal;
[…]
void main()
{
[…]
FragColor.rgb = Normal;
FragColor.a = 1.0f;
}
通过输出一个(非颜色)变量到这样子的输出颜色通道中,我们可以快速审查变量是否显示着正确的值。