计算机图形学编程(使用OpenGL和C++)(第2版)
上QQ阅读APP看书,第一时间看更新

编译和运行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显卡的计算机。