2.2 检测OpenGL和GLSL错误
编译和运行GLSL代码的过程与普通代码的不同,GLSL的编译发生在C++运行时。另外一个复杂的点是GLSL代码并没有运行在CPU中(它运行在GPU中),因此操作系统并不总能捕获OpenGL运行时的错误。以上这两点使调试变得很困难,因为常常很难判断着色器的运行是否失败,以及为什么失败。
程序2.3展示了用于捕获和显示GLSL错误的模块。其中GLSL函数glGetShaderiv()和glGetProgramiv()用于提供有关编译过的GLSL着色器和程序的信息。程序2.3还使用了程序2.2中的createShaderProgram()函数,不过加入了错误检测的调用。
程序2.3中实现了如下3个实用功能。
- printShaderLog():当GLSL代码编译失败时,显示OpenGL日志内容。
- printProgramLog():当GLSL链接失败时,显示OpenGL日志内容。
- checkOpenGLError():检查OpenGL错误标志,即是否发生OpenGL错误。
checkOpenGLError()既用于检测GLSL代码编译错误,又用于检测OpenGL运行时的错误,因此我们强烈建议在整个C++/OpenGL应用程序开发过程中使用它。例如,对于程序2.2中glCompileShader()和glLinkProgram()的调用,我们可以很方便地使用程序2.3中的代码进行加强,以确保捕获到所有拼写错误和编译错误,同时知道错误原因。
使用这些工具的另一个很重要的原因是,GLSL错误并不会导致C++程序崩溃。因此,除非程序员能通过步进找到错误发生的点,否则调试会非常困难。
程序2.3 用以捕获GLSL错误的模块
void printShaderLog(GLuint shader) {
int len = 0;
int chWrittn = 0;
char *log;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
log = (char *)malloc(len);
glGetShaderInfoLog(shader, len, &chWrittn, log);
cout << "Shader Info Log: " << log << endl;
free(log);
} }
void printProgramLog(int prog) {
int len = 0;
int chWrittn = 0;
char *log;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
log = (char *)malloc(len);
glGetProgramInfoLog(prog, len, &chWrittn, log);
cout << "Program Info Log: " << log << endl;
free(log);
} }
bool checkOpenGLError() {
bool foundError = false;
int glErr = glGetError();
while (glErr != GL_NO_ERROR) {
cout << "glError: " << glErr << endl;
foundError = true;
glErr = glGetError();
}
return foundError;
}
// 检测OpengGL错误的示例如下
GLuint createShaderProgram() {
GLint vertCompiled;
GLint fragCompiled;
GLint linked;
...
// 捕获编译着色器时的错误
glCompileShader(vShader);
checkOpenGLError();
glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
if (vertCompiled != 1) {
cout << "vertex compilation failed" << endl;
printShaderLog(vShader);
}
glCompileShader(fShader);
checkOpenGLError();
glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
if (fragCompiled != 1) {
cout << "fragment compilation failed" << endl;
printShaderLog(fShader);
}
// 捕获链接着色器时的错误
glAttachShader(vfProgram, vShader);
glAttachShader(vfProgram, fShader);
glLinkProgram(vfProgram);
checkOpenGLError();
glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
if (linked != 1) {
cout << "linking failed" << endl;
printProgramLog(vfProgram);
}
return vfProgram;
}
着色器运行时错误的常见结果是输出屏幕上完全空白,根本没有输出。即使是着色器中的一个小拼写错误也可能导致这种结果,这样就很难断定是在哪个管线阶段发生了错误。在没有任何输出的情况下,找到错误的成因就像大海捞针。
有一些技巧可以推测着色器代码运行错误的原因,其中一种就是暂时将片段着色器换成程序2.2中的片段着色器。程序2.2中,片段着色器仅输出一个特定颜色,例如将其设置成蓝色,那么如果后来的输出中的几何形状正确,但是全显示为蓝色,那么顶点着色器应该是正确的,错误应该发生在片段着色器;如果输出的仍然是空白屏幕,那么错误很可能发生在管线的更早期,例如顶点着色器。
附录C中将展示另一种有用的调试工具Nsight,它适用于配有特定型号NVIDIA显卡的计算机。