现代的OpenGL调试方式(翻译)

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

原文地址: http://www.learnopengl.com/#!In-Practice/Debugging

1 Debugging

Graphics programming can be a lot of fun, but it can also be a large source of frustration whenever something isn’t rendering right or perhaps not even rendering at all! Seeing as most of what we do involves manipulating pixels it can be difficult to figure out the cause of error whenever something doesn’t work the way it’s supposed to. Debugging these kind of errors is different than what you’re used to when debugging errors on the CPU. We have no console to output text to, no breakpoints to set on our GLSL code and no way of easily checking the state of GPU execution.

图形编程可以有很多乐趣,但当无法按正确意图渲染出东西来,甚至什么东西都渲染不出来的时候,也会带来很大的挫败感。想找出无法按我们意图操作像素值从而渲染出想要的结果的原因何在,是很困难的。与调试运行在CPU上的代码相比,调试这些运行在GPU的代码更为困难。我们无法在控制台上对这些GPU代码进行打印输出;无法再GLSL代码上下断点调试;无法很容易地检查GPU执行代码时的状态。

In this tutorial we’ll look into several techniques and tricks of debugging your OpenGL program. Debugging in OpenGL is not too difficult to do and getting a grasp of its techniques definitely pays out in the long run.

在本教程中,我们将会学习到一些调试OpenGL程序代码的技巧和方法,之后你也将会看到调试OpenGL代码并不是想象中那么困难。从长远来看,也值得花时间去掌握这些调试技巧。

2 glGetError()

The moment you incorrectly use OpenGL (like configuring a buffer without first binding any) it will take notice and generate one or more user error flags behind the scenes. We can query these error flags using a function named glGetError that simply checks the error flag(s) set and returns an error value if OpenGL indeed got abused.

当你没有正确使用OpenGL的API,比如在配置一个buffer之前没有先正确绑定它时。OpenGL将会在幕后生成一个或者多个“用户错误代码”,我们可以通过使用glGetError函数去查询这些错误代码。glGetError函数将会检查这些错误代码,并且在OpenGL系统确实被乱用的时候,会将这个错误代码返回给用户层。glGetError函数的声明原型如下:

1GLenum glGetError();

The moment glGetError is called it returns either an error flag or no error at all. The error codes that glGetError can return are listed below:

glGetError函数被调用的时候,它将会返回错误代码,或者不返回一个代表没有错误代码的GL_NO_ERROR的代码,返回的错误代码如下表所示:

Flag Code Description
Flag Code Description
GL_NO_ERROR 0 No user error reported since last call to glGetError.
GL_INVALID_ENUM 1280 Set when an enumeration parameter is not legal.
GL_INVALID_VALUE 1281 Set when a value parameter is not legal.
GL_INVALID_OPERATION 1282 Set when the state for a command is not legal for its given parameters.
GL_STACK_OVERFLOW 1283 Set when a stack pushing operation causes a stack overflow.
GL_STACK_UNDERFLOW 1284 Set when a stack popping operation occurs while the stack is at its lowest point.
GL_OUT_OF_MEMORY 1285 Set when a memory allocation operation cannot allocate (enough) memory.
GL_INVALID_FRAMEBUFFER_OPERATION 1286 Set when reading or writing to a framebuffer that is not complete.

Within OpenGL’s function documentation you can always find the error codes a function generates the moment it is incorrectly used. For instance, if you take a look at the documentation of the glBindTexture function you can find all the user error codes it could generate under the Errors section

在OpenGL的函数文档里面,你可以查到该OpenGL函数如果没有正确执行时,马上调用例如glGetError函数返回的错误代码将会返回的结果。例如,glBindTexture函数的描述文档中,你可以找到所有该函数调用发生错误时的错误代码。

The moment an error flag is set, no other error flags will be reported. Furthermore, the moment glGetError is called it clears all error flags (or only one if on a distributed system, see note below). This means that if you call glGetError once at the end of each frame and it returns an error you can’t conclude this was the only error and the source of the error could’ve been anywhere in the frame

在一个错误代码标志被设置的时候,其他的错误代码不会被报告出来。此外,当glGetError函数被调用的时候,该函数将会清空所有的错误标志(在某些发布平台上,或者仅仅清除一个错误标志)。这也意味着:在每一帧的最后调用一次glGetError函数的话。它返回的错误代码并不表示在本帧中其他的OpenGL函数调用没有导致错误代码。

Note that when OpenGL runs distributely like frequently found on X11 systems, other user error codes can still be generated as long as they have different error codes. Calling glGetError then only resets one of the error code flags instead of all of them. Because of this it is recommended to call glGetError inside a loop.

注意在比如像X11这样的发布平台上。当发生了多种错误时,其他的错误代码也会发生。调用一次glGetError函数则仅仅设置其中的一个错误标志位。所以在这种平台上建议在一个循环内多次调用glGetError函数。

1glBindTexture(GL_TEXTURE_2D, tex);
2std::cout << glGetError() << std::endl; // returns 0 (no error) 
3glTexImage2D(GL_TEXTURE_3D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
4std::cout << glGetError() << std::endl; // returns 1280 (invalid enum) 
5glGenTextures(-5, textures);
6std::cout << glGetError() << std::endl; // returns 1281 (invalid value) 
7std::cout << glGetError() << std::endl; // returns 0 (no error)

The great thing about glGetError is that it makes it relatively easy to pinpoint where any error might be and to validate the proper use of OpenGL. Let’s say you get a black screen and you have no idea what’s causing it: is the framebuffer not properly set? Did I forget to bind a texture? By calling glGetError all over your codebase you can quickly catch the first place an OpenGL error starts showing up which means before that call something went wrong.

使用glGetError函数最重要的事情是让“指出何处可能会发生错误,以及验证是否正确地使用OpenGL”变得更为容易些。比如在使用一个texture的时候,是否忘记了要先绑定它。在你所有的代码中调用glGetError的地方你能快速地获取到第一个发生错误的地方。

By default glGetError only prints the error numbers which isn’t easy to understand unless you memorize the error codes. It often makes sense to write a small helper function to easily print out the error strings together with where the error check function was called:

缺省地,glGetError函数只是简单地返回错误代码,而没有文字描述,这让人不容易记住和理解错误代码的含义。所以一般地我们会写一些帮助函数,用来返回一个描述错误代码的文字字符串,如下:

 1GLenum glCheckError_(const char *file, int line)
 2{
 3    GLenum errorCode;    
 4    while ((errorCode = glGetError()) != GL_NO_ERROR) 
 5    {        
 6        std::string error;    
 7        switch (errorCode)
 8        {
 9            case GL_INVALID_ENUM: error = "INVALID_ENUM"; break;
10            case GL_INVALID_VALUE: error = "INVALID_VALUE"; break;
11            case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break;
12            case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break;
13            case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break;
14            case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break;
15            case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break;
16        }
17        std::cout << error << " | " << file << " (" << line << ")" << std::endl;
18    }
19
20    return errorCode;
21}
22
23#define glCheckError() glCheckError_(__FILE__, __LINE__) glBindBuffer(GL_VERTEX_ARRAY, vbo);glCheckError();

This will give us the following output

上述的代码将会得到如下输出

One important thing left to mention is that GLEW has a long-existing bug where calling glewInit() always sets theGL_INVALID_ENUM error flag and thus the first glGetError will always return an error code which can throw you completely off guard. To fix this it’s advised to simply call glGetError after glewInit to clear the flag:

一个非常重要的事情必须被提及。GLEW库有一个长期存在的bug,在调用glewInit函数的地方,一直会把GL_INVALID_ENUM 标志位给设置。因而,在第一次调用glGetError的时候会一直返回这个值。解决这个bug很简单,在glewInit函数调用后,马上调用一次glGetError函数,如下:

1glewInit();
2glGetError();
3glewInit();
4glGetError();

glGetError doesn’t help you too much as the information it returns is rather simple, but it does often help you catch typos or quickly pinpoint where in your code things went wrong; a simple but effective tool in your debugging toolkit.

glGetError函数只能简单地提供错误码。没法提供更多的帮助信息。但利用它可以快速地查询到代码哪里调用OpenGL函数发生了错误。这函数,简单,但是有效。

3 Debug output

A less common, but more useful tool than glCheckError is an OpenGL extension called debug output that became part of core OpenGL since version 4.3. With the debug output extension OpenGL itself will directly send an error or warning message to the user with a lot more details compared to glCheckError. Not only does it provide more information, it can also help you catch errors exactly where they occur by intelligently using a debugger.

一个没那么常用,但也很有效的工具函数是glCheckError。早期这个函数是作为一个OpenGL扩展的形式存在。从4.3版本开始,它变成了OpenGL的内核函数。通过使用这个函数。OpenGL可以直接发送一个带有详细信息的错误或者警告的描述。这个函数不仅仅只提供错误信息。它还可以帮你通过使用调试器,智能地捕获到哪个发生了错误。

Debug output is core since OpenGL version 4.3, which means you’ll find this functionality on any machine that runs OpenGL 4.3 or higher. If they’re not available its functionality can be queried from the ARB_debug_output or AMD_debug_output extension. Note that OS X does not seem to support debug output functionality (as gathered online; I haven’t tested it myself. Let me know if I’m wrong).

从4.3版本开始,Debug output将作为OpenGL的内核存在。也就是说只要你运行OpenGL4.3以上的版本你可以直接使用它。如果运行低版本的OpenGL的话。可以通过查询ARB_debug_output或者AMD_debug_output这两个OpenGL扩展达到这个功能。注意在OS X中貌似不支持这个函数。(从网上得知,未经证实)。

In order to start using debug output we have to request a debug output context from OpenGL at our initialization process. This process varies based on whatever windowing system you use; here we will discuss setting it up on GLFW, but you can find info on other systems in the additional resources at the end.

为了能够使用调试输出,我们需要从OpenGL处给初始化进程请求一个“调试输出上下文”。这将依赖于你所使用的窗口系统的版本是什么。在此我们讨论在GLFW中继续设置。其他的平台的话可以在本文的最后查阅相关资料

4 Debug output in GLFW

Requesting a debug context in GLFW is surprisingly easy as all we have to do is pass a hint to GLFW that we’d like to have a debug output context. We have to do this before we call glfwCreateWindow:

在GLFW中请求一个调试输出上下文非常简单。只需要像GLFW提供一个hint值便能达到我们的目的。在调用glfwCreateWindow函数之前调用如下代码:

1glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);

Once we initialize GLFW we should have a debug context if we’re using OpenGL version 4.3 or higher, or else we have to take our chances and hope the system is still able to request a debug context. Otherwise we have to request debug output using its OpenGL extension(s).Using OpenGL in debug context can be significantly slower compared to a non-debug context so when working on optimizations or releasing your application you want to remove or comment GLFW’s debug request hint.

一旦我们初始化GLFW时决定使用调试输出上下文时我们就必须使用4.3或者更高的OpenGL版本。否则就只能使用OpenGL扩展。使用调试版本的OpenGL将会比非调试版本的要慢。所以当发布程序时最好移除对GLFW的调试输出上下文的支持。

To check if we successfully initialized a debug context we can query OpenGL:

为了检查是否成功初始化调试输出上下文,我们可以执行以下的代码:

1GLint flags; 
2glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
3if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
4{
5    // initialize debug output
6}