图形编程想要调试并不是一件容易的事,有的时候渲染出全黑的结果基本上只能凭经验来查错,特别是对于着色器,断点日志都是无效的,因此想办法掌握一些调试方法还是有必要的,不然找错误的源头可能真的会非常困难

一、glGetError()

可以在程序的任意地方调用glGetError方法,它会返回之前所有的错误标记,常见的错误如下:

标记 代码 描述
GL_NO_ERROR 0 自上次调用glGetError以来没有错误
GL_INVALID_ENUM 1280 枚举参数不合法
GL_INVALID_VALUE 1281 值参数不合法
GL_INVALID_OPERATION 1282 一个指令的状态对指令的参数不合法
GL_STACK_OVERFLOW 1283 压栈操作造成栈上溢(Overflow)
GL_STACK_UNDERFLOW 1284 弹栈操作时栈在最低点(译注:即栈下溢(Underflow))
GL_OUT_OF_MEMORY 1285 内存调用操作无法调用(足够的)内存
GL_INVALID_FRAMEBUFFER_OPERATION 1286 读取或写入一个不完整的帧缓冲

不过需要注意一下几点:

  • 每次调用glGetError方法,都会输出并清空当前所有的错误标记
  • 输出的错误可能是在任意地方产生的,你并不会知道产生这个错误的具体方法和行号
  • 调用glewInit()会自动设置一个GL_INVALID_ENUM的错误标记,所以需要在调用glewInit之后立即调用glGetError以消除这个标记,这应该是一个glew自带的bug

当然,glGetError()方法返回的并不是字符串,而是一个错误码(数字)也就是上表中的代码那一列,因此每次拿到这些错误码都需要去查一次错误码对应的实际错误描述,这个比较麻烦

一个一劳永逸的方法就是这样:

void glCheckError_(const char* file, int line)
{GLenum errorCode;while ((errorCode = glGetError()) != GL_NO_ERROR){string error;switch (errorCode){case GL_INVALID_ENUM:                  error = "INVALID_ENUM"; break;case GL_INVALID_VALUE:                 error = "INVALID_VALUE"; break;case GL_INVALID_OPERATION:             error = "INVALID_OPERATION"; break;case GL_STACK_OVERFLOW:                error = "STACK_OVERFLOW"; break;case GL_STACK_UNDERFLOW:               error = "STACK_UNDERFLOW"; break;case GL_OUT_OF_MEMORY:                 error = "OUT_OF_MEMORY"; break;case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break;}cout << error << " | " << file << " (" << line << ")" << endl;}
}

也就是自己写一个枚举来自动转化,并且同时输出当前glGetError()的行号

二、调试输出

还有个比glGetError更好的办法,那就是使用openGL自带的调试输出扩展工具,可惜的是只有4.3以上的openGL版本支持,它可以给出更详细的错误报告,并且你可以通过断点来得到产生错误的堆栈

glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{cout << "成功启用调试功能" << endl;
}

常规操作,使用glfwWindowHint开启,并且检查是否成功地初始化了调试上下文的功能,注意一下,若是发布正式的版本,记得不要开启调试,它必然会影响程序效率

其次就是开启调试输出,设置输出回调和日志筛选:

  • glDebugMessageCallback:提供一个调试回调函数来接收所产生的调试信息,非常重要
  • glDebugMessageControl:过滤输出的调试信息/日志,例如你可以选择无视一些无关紧要的报告。前三个参数分别表示消息来源、消息类型、和消息等级,如果设置为GL_DONT_CARE则默认全部接收,第4个参数表示消息标志符的数目,第5个参数为消息标志符的数组,这两个不用管,传0和空就好,最后一个参数为是否启用,为GL_FALSE时表示丢弃对应消息
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{cout << "DEBUG_CONTEXT_RUNING_SUCCESS" << endl;glEnable(GL_DEBUG_OUTPUT);glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);glDebugMessageCallback(glDebugOutput, NULL);glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE);
}

回调函数直接复制粘贴下面的代码吧,看了就明白了,不需要多解释

需要注意的是:这个接口的参数是不可变更的,必须要对的上,不然就会出现严重错误

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;cout << "---------------" << endl;cout << "Debug message (" << id << "): " << message << endl;//生成消息的对象switch (source){case GL_DEBUG_SOURCE_API:             cout << "Source: API"; break;                 //GLcase GL_DEBUG_SOURCE_WINDOW_SYSTEM:   cout << "Source: Window System"; break;       //GLSL编译器或者其他着色语言的编译器case GL_DEBUG_SOURCE_SHADER_COMPILER: cout << "Source: Shader Compiler"; break;     //窗口系统,比如WGL或者GLXcase GL_DEBUG_SOURCE_THIRD_PARTY:     cout << "Source: Third Party"; break;         //外部调试器或者第三方中间库case GL_DEBUG_SOURCE_APPLICATION:     cout << "Source: Application"; break;         //应用程序case GL_DEBUG_SOURCE_OTHER:           cout << "Source: Other"; break;               //与前面列出都不相符的源}cout << "\n";//产生消息的情况switch (type){case GL_DEBUG_TYPE_ERROR:               cout << "Type: Error"; break;                       //生成一个错误的事件case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: cout << "Type: Deprecated Behaviour"; break;        //被标记为弃用的行为case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  cout << "Type: Undefined Behaviour"; break;         //在规范中未定义的行为case GL_DEBUG_TYPE_PORTABILITY:         cout << "Type: Portability"; break;         //依赖于实现的性能警告case GL_DEBUG_TYPE_PERFORMANCE:         cout << "Type: Performance"; break;         //使用特定供应商提供的扩展或者着色器case GL_DEBUG_TYPE_MARKER:              cout << "Type: Marker"; break;              //命令流的注解case GL_DEBUG_TYPE_PUSH_GROUP:          cout << "Type: Push Group"; break;          //进入一个调试组case GL_DEBUG_TYPE_POP_GROUP:           cout << "Type: Pop Group"; break;           //离开一个调试组case GL_DEBUG_TYPE_OTHER:               cout << "Type: Other"; break;               //与前面列出都不相符的类型}cout << "\n";//显示消息的例子switch (severity){case GL_DEBUG_SEVERITY_HIGH:         cout << "Severity: high"; break;       //任何GL错误;危险的未定义行为;着色器编译错误和链接错误case GL_DEBUG_SEVERITY_MEDIUM:       cout << "Severity: medium"; break;     //严重的性能警告;着色器编译链接警告;使用弃用的行为case GL_DEBUG_SEVERITY_LOW:          cout << "Severity: low"; break;        //冗余状态改变所产生的性能警告;微不足道的弃用行为case GL_DEBUG_SEVERITY_NOTIFICATION: cout << "Severity: notification"; break;   //没有任何错误或者性能警告}cout << "\n";cout << endl;
}

好了,可以利用glDebugMessageInsert方法测试一下:

glDebugMessageInsert:自定义调试消息传送给调试环境,第124个参数分别表示表示消息来源、消息类型、和消息等级,第3个参数为消息标志,第5个参数为消息长度,第6个参数为内容,第3个参数和第5个参数可以暂时不用管

glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 10000, GL_DEBUG_SEVERITY_HIGH, -1, "这是一个测试");

三、着色器调试

直接将对应的属性/变量传入片段着色器中输出是一个不错的选择,或者合理的利用几何着色器

如果得到了一个全黑的结果,最好检查一下所有的uniform变量是否合理的赋值,其次检查观察矩阵投影矩阵等几个决定屏幕位置的重要矩阵值是否正确,很有可能结果是被渲染到了很远的屏幕外面

这篇文章列举了几乎所有可能黑屏的原因

将法线设为颜色输出的测试结果

幸运的是,只要能渲染出结果,那么调试就会变得容易不少

GLSL参考编译器

每一个驱动对于着色器的规范都不太一样,NVIDIA相对于更加宽容一些,但是AMD却会严格执行OpenGL规范,这样就会导致同样的着色器在一台机器上能正常工作,另一台机器就不可以了

想要保证着色器代码在所有的机器上都能运行,可以直接对着官方的标准使用OpenGL的GLSL参考编译器(Reference Compiler)来检查。从这里下载所谓的GLSL语言校验器(GLSL Lang Validator)的可执行版本,或者从这里找到完整的源码

有了GLSL语言校验器,就可以很方便的检查你的着色器代码,只需要把着色器文件作为程序的第一格参数即可:

  • .vert:顶点着色器(Vertex Shader)
  • .frag:片段着色器(Fragment Shader)
  • .geom:几何着色器(Geometry Shader)
  • .tesc:细分控制着色器(Tessellation Control Shader)
  • .tese:细分评估着色器(Tessellation Evaluation Shader)
  • .comp:计算着色器(Compute Shader)

运行GLSL参考编译器非常简单,如果没有检测到错误的话就没有输出

glsllangvalidator shaderFile.vert

这本质上是一个规范检查工具,因此并不是万能的,你的着色器仍然有可能有BUG

四、专业调试软件

在专业调试软件下,上面的测试方法就显得不太需要了

这些第三方应用的运作原理是:注入到OpenGL驱动中拦截各种OpenGL调用,给你大量有用的数据,能提供的帮助包括但不限于:对OpenGL函数使用进行性能测试,寻找瓶颈,检查缓冲内存,显示纹理和帧缓冲附件等,若要写(大规模)生产代码,这类的工具在开发过程中是非常有用的

当然具体用哪一个应用,就去专门了解对应的就好了

gDebugger:http://www.gremedy.com/

RenderDoc:https://github.com/baldurk/renderdoc

CodeXL:https://developer.amd.com/tools-and-sdks/opencl-zone/codexl/

参考:

  • https://www.cnblogs.com/vertexshader/articles/3563883.html
  • https://learnopengl.com/#!In-Practice/Debugging

OpenGL基础56:OpenGL简单调试相关推荐

  1. 【OpenGL基础】|| OpenGL渲染过程介绍

    文章目录 1. 介绍 2. 顶点输入 3. 顶点着色器 4. 编译着色器 5. 片元着色器 6. 着色器程序 7. 链接顶点属性 8. 顶点数组对象 9. 索引缓冲对象 1. 介绍 在OpenGL中, ...

  2. android opengl版本,Android OpenGL ES(一)开发入门

    早就听过大名鼎鼎的 OpenGL,却迟迟没有实践学习,有些惭愧.今天开始通过实践+博文方式学习掌握 OpenGL.此文对于 OpenGL 的学习分为以下部分: OpenGL 基础概念 OpenGL 坐 ...

  3. OpenGL基础35:帧缓冲(下)之简单图像处理

    在之前的章节,所有的物体都是中规中矩的显示的,只考虑了光照对物体的影响,那假设想要显示特殊的效果该怎么操作呢?例如马赛克风.将所有的物体都显示为黑白色,就像上世纪80年代的灰白电视一样,又或者说将整个 ...

  4. OpenGL基础25:多光源(附简单GLSL配置)

    到这里,光照基础就已经接近尾声了,当然对于光照渲染的学习,这可能只是百步中的一步,尽管如此,至少还是做到了从 0 到 1 的一个过程,就像之前刚学会"HelloWorld"一样,一 ...

  5. 【OpenGL】计算机图形学实验一:OpenGL基础实验(实验环境的熟悉、简单图形的绘制和输出)

    实验一:OpenGL基础实验 (实验环境的熟悉.简单图形的绘制和输出) 1.实验目的和要求 学习基本的OpenGL图形绘制和输出函数,掌握使用基于C++  OpenGL开发图形程序的流程. 2.实验设 ...

  6. OpenGL基础知识介绍和简单使用

    OpenGL基础知识介绍 OpenGL简介 OpenGL 专业词解析 1.OpenGL上下文[context] 2.渲染 3.顶点数组和顶点缓冲区 4.着色器程序Shader 5.顶点着色器(Vert ...

  7. OpenGL: 基础篇

    本人水平有限,如有问题请以文章形式提出,大家可以讨论吗... [OPENGL怎么用] OPENGL编程类似C编程,实际接口就是C,所以熟悉C是必要的 一般编程可用到的函数库包括: OPENGL实用库: ...

  8. opengl基础学习

    转自:http://www.cnblogs.com/crazyxiaom/articles/2073586.html 说起编程作图,大概还有很多人想起TC的#include <graphics. ...

  9. opengl基础学习专题 (二) 点直线和多边形

    题外话 随着学习的增长,越来越觉得自己很水.关于上一篇博文中推荐用一个 学习opengl的 基于VS2015的 simplec框架.存在 一些问题. 1.这个框架基于VS 的Debug 模式下,没有考 ...

最新文章

  1. 微信小程序云开发 | 云函数安装依赖
  2. leetcode-344-反转字符串
  3. 我是如何学习写一个操作系统(三):操作系统的启动之保护模式
  4. 在Windows笔记本上调试运行在iOS设备上的前端应用
  5. 不用Linux也可以的强大文本处理方法
  6. mysql sharding 读取_MySQL读写分离(一)——sharding-jdbc
  7. LNMP(linux+nginx+mysql+php)服务器环境配置
  8. vc++之剪贴板通信实例
  9. Linux根据端口号或者关键字查询进程,重启Tomcat服务脚本优缺点说明
  10. CVPR2022 Oral | CosFace、ArcFace的大统一升级,AdaFace解决低质量图像人脸识
  11. Java设计模式——装饰器模式
  12. Win10命令提示符在哪里 怎么打开命令提示符窗口
  13. 数据库原理与应用——课程介绍及数据库系统概论(上)
  14. 计算机找不到ac97前面板怎么办,电脑Win7系统前面板耳机插孔没有声音解决方法(音频设置+前面板插线方法)...
  15. TQ2440之内核3.15.6移植
  16. linux iso镜像怎么用u盘装,如何使用U盘安装ISO系统镜像?
  17. FFplay文档解读-31-视频过滤器六
  18. 数据结构编程实践(七)创建哈夫曼树、生成哈夫曼编码、完成图片的压缩与解压缩
  19. mysql优化工具 MySQL Tuner
  20. java之getResource方法

热门文章

  1. python自动化办公模块有哪些-Python 自动化办公|Word文档
  2. python工程师工资多少-最新 | 2019年Python工程师的平均薪资是多少?
  3. 免费python课程排行榜-Python爬虫学习排行榜
  4. python是什么类型的语言-Python是什么?简单了解pythonp-入门
  5. python是什么 自学-python自学需要什么软件?
  6. 学python可以从事什么工作-学Python可以找什么工作或者做什么兼职?
  7. python编程入门书籍-编程小白的第一本 Python 入门书
  8. 关于计算机实验的英语作文,关于实验的英语作文
  9. JS的Document类型
  10. CImage 对话框初始化时候显示透明 PNG