我们今天来讲调试信息,这个东西讲起来会比较无聊,因为都是一些函数调用,没啥可讲的,函数就是那样用的,不过其效果挺好玩的,同时在程序设计中也是很必要的,所以还是来写一下,不过,就是因为知识比较固定且简单,所以我们 一篇就覆盖三节的内容吧:
1. 获取当前活动的顶点属性和对应索引
2.获取当前活动的uniform量和对应索引
2.Debug
Getting Ready
我们以上一节的旋转三角形的例子来讲这一篇。
当我们的shader program编译连接完成之后,所有的量都已经分配了自己的索引,这时候我们可以查看其信息。
我们默认连接好的shader program的句柄为programHandle
Getting a list of active vertex input attributes and locations
我们需要获取所有顶点属性信息,首先要知道有多少个顶点属性
glGetProgramInterfaceiv用来获取program中的一些状态信息。
它有四个参数:
第一个参数为:program 句柄,确定是哪一个程序中的状态信息
第二个参数为:我们要查询的是程序的哪一类数据资源
第三个参数为:我们要查询的量
第四个参数为:存储获取的信息
glGetProgramInterfaceiv(programHandle, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &numAttribs);
即为,我们要获取programHandle这个着色器程序中有关程序输入(即为顶点属性)的数据信息,将当前处于活动状态的上述资源的数量( GL_ACTIVE_RESOURCES)存储到numAttribs中
接下来,我们需要循环获取顶点属性(数据资源,resource)信息
假如我们的numAttribs的值为6,我们对于每一都如此做:
glGetProgramResourceiv获取单个数据资源的状态信息
它一共有8个参数:
para1:shader program 句柄用于指定处理的是哪一个程序
para2:同上面那个函数的第二个参数,数据资源的类型
para3:查询的是第几个数据资源(0 ~ numAttribs-1)
para5:要查询数据资源的哪些信息,参数类型为枚举数组地址,用于记录要查询的所有查询信息
para4:para5中所传入的数组的size
para6:para8的数组中一个数据资源包含多少个信息元素
para7:第七个参数是一个指向整数的指针,该整数将接收被写入的数据组有多少组,如果para8为二维信息,即有很多组,每组数据为一维,那么需要该参数返回para8中有多少组,以便解析para8中的数据,而我们 这里的para8指定接收资源数据的一组信息,所以,此参数无效,我们可用nullptr来跳过该参数。
para8:用于接收系统返回的数据信息
glGetProgramResourceiv(programHandle, GL_PROGRAM_INPUT, i, 3, properties, 3, nullptr, results);
前两个就不用说了,我们要获取第 i 个数据资源(顶点属性量)的信息,其中一组信息有三种,它们存储在properties数组中:
GLenum properties[]{ GL_NAME_LENGTH, GL_TYPE, GL_LOCATION }; //数据资源的名字的长度,数据资源的数据类型,数据资源的索引值
我们需要将系统传出的数据每3个一组存入results中,返回。由于我们的results只记录一组返回信息,所以数据无需解析分组,所以para7为nullptr,如果为多组的二维信息,我们需要指定一个整型来接受返回数据。
如:glGetProgramResourceiv(programHandle, GL_PROGRAM_INPUT, i, 3, properties, 3, num, results);
num是一个GLint,那么意思是,results中存储着num组数据,每组数据有3个,如同二维数组传递参数一样,低维度的参数必须给定,也就是num前面的3,对吧~
最后,我们通过glGetProgramResourceiv获取数据资源的名字。
我们已经通过上面那个函数获取到了名字的长度,所以,我们创建一个缓冲区来接受字符串。
GLint nameBufSize = results[0] + 1;
GLchar* name = new char[nameBufSize];
glGetProgramResourceName(programHandle, GL_PROGRAM_INPUT, i, nameBufSize, nullptr, name);
至此,我们获取到了顶点属性量的名字
我们的名字以及索引都得到了,还需要将GL_TYPE获取到的类型枚举值换成字符串
const GLchar* getTypeString(GLenum type)
{
// There are many more types than are covered here, but
// these are the most common in these examples.
switch (type) {
case GL_FLOAT:
return "float";
case GL_FLOAT_VEC2:
return "vec2";
case GL_FLOAT_VEC3:
return "vec3";
case GL_FLOAT_VEC4:
return "vec4";
case GL_DOUBLE:
return "double";
case GL_INT:
return "int";
case GL_UNSIGNED_INT:
return "unsigned int";
case GL_BOOL:
return "bool";
case GL_FLOAT_MAT2:
return "mat2";
case GL_FLOAT_MAT3:
return "mat3";
case GL_FLOAT_MAT4:
return "mat4";
default:
return "?";
}
}
代码如下:
void getVertexAttribList(GLint programHandle)
{
GLint numAttribs;
glGetProgramInterfaceiv(programHandle, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &numAttribs);
GLenum properties[]{ GL_NAME_LENGTH,GL_TYPE,GL_LOCATION };
cout << "Active attributes:" << endl;
for (int i = 0; i < numAttribs; ++i)
{
GLint results[3];
glGetProgramResourceiv(programHandle, GL_PROGRAM_INPUT, i, 3, properties, 3, nullptr, results);
GLint nameBufSize = results[0] + 1;
GLchar* name = new char[nameBufSize];
glGetProgramResourceName(programHandle, GL_PROGRAM_INPUT, i, nameBufSize, nullptr, name);
cout << "location:" << results[2] << " " << name << "(" << getTypeString(results[1]) << ")" << endl;
delete[] name;
}
}
在main主程序中的着色器程序连接完成之后调用此函数即可得到如下测试结果:
没问题,嘻嘻
Getting a list of active uniform variables
void getUniformList(GLint programHandle) { GLint numUniforms = 0; glGetProgramInterfaceiv(programHandle, GL_UNIFORM, GL_ACTIVE_RESOURCES, &numUniforms); GLenum properties[] = { GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_BLOCK_INDEX }; printf("Active uniforms: "); for (int i = 0; i < numUniforms; ++i) { GLint results[4]; glGetProgramResourceiv(programHandle, GL_UNIFORM, i, 4, properties, 4, NULL, results); if (results[3] != -1) continue; // Skip uniforms in blocks GLint nameBufSize = results[0] + 1; char * name = new char[nameBufSize]; glGetProgramResourceName(programHandle, GL_UNIFORM, i, nameBufSize, NULL, name); cout << "location:" << results[2] << " " << name << "(" << getTypeString(results[1]) << ")" << endl; delete[] name; } }
唯一不同的一点是properties的第四个枚举量
它记录了当前查询的uniform量所在的uniform block,如果该uniform量不属于任何block,那么该元素对应的值为-1
这里,我们只查询单独的uniform量,所以当其GL_BLOCK_INDEX对应的返回值为-1的时候是我们所要的,反之,我们跳过。
Debug
在之前,我们获取调试信息的传统方法是调用glGetError。但是这是一种非常繁琐的方法,其繁琐程度就不再赘述了。
从OpenGL 4.3开始,我们现在支持更现代的调试方法。我们可以整一个调试回调函数,该函数将在发生错误或者生成其他警告提示性消息时执行。
不仅如此,我们还可以发送自己的自定义消息,由同一个回调处理,我们可以使用各种条件过滤消息。
注:context有翻译为上下文,有翻译为环境,个人比较倾向于环境一说,下文不做翻译
使用debug context 创建OpenGL程序。 虽然获取debug context 并非绝对必要,但我们可能无法获得与使用debug context 时相同的消息。
要在启用调试的情况下使用GLFW创建OpenGL context,在创建窗口之前要使用以下函数调用。
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
默认情况下,OpenGL debug context 将启用调试消息。但是,如果需要显式启用调试消息,请使用以下调用。
glEnable(GL_DEBUG_OUTPUT);
我们采用如下步骤进行:
1.创建回调函数以接收调试消息。
该函数必须符合OpenGL文档中描述的特定原型。 我们采用如下方式:
void debugCallback(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length,
const GLchar * message, void * param) {
// Convert GLenum parameters to strings
printf("%s:%s[%s](%d): %s
", sourceStr, typeStr,
severityStr, id, message);
}
2.使用glDebugMessageCallback向OpenGL注册我们的回调:
glDebugMessageCallback(debugCallback,NULL);
3.启用所有消息,所有来源,所有级别和所有ID:
glDebugMessageControl(GL_DONT_CARE,GL_DONT_CARE,
GL_DONT_CARE,0,NULL,GL_TRUE);
我们来解释一下上述的一些内容:
回调函数debugCallback有几个参数,其中最重要的是调试消息本身(第六个参数,message)。
对于此示例,我们只是将消息打印到标准输出,但我们可以将其发送到日志文件或其他目的地.
debugCallback的前四个参数描述消息的来源,类型,ID号和严重性。
id号是特定于消息的无符号整数。 下表中描述了source,type和Severity参数的可能值。
Source |
Generated By |
GL_DEBUG_SOURCE_API |
Calls to the OpenGL API |
GL_DEBUG_SOURCE_WINDOW_SYSTEM |
Calls to a window system API |
GL_DEBUG_SOURCE_THIRD_PARTY |
An application associated with OpenGL |
GL_DEBUG_SOURCE_APPLICATION |
This application itself. |
GL_DEBUG_SOURCE_OTHER |
Some other source |
Type |
Description |
GL_DEBUG_TYPE_ERROR |
An error from the OpenGL API. |
GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR |
Behavior that has been deprecated |
GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR |
Undefined behaviour |
GL_DEBUG_TYPE_PORTABILITIY |
Some functionality is not portable. |
GL_DEBUG_TYPE_PERFORMANCE |
Possible performance issues |
GL_DEBUG_TYPE_MARKER |
An annotation |
GL_DEBUG_TYPE_PUSH_GROUP |
Messages related to debug group push. |
GL_DEBUG_TYPE_POP_GROUP |
Messages related to debug group pop. |
GL_DEBUG_TYPE_OTHER |
Other messages |
Severity | Meaning |
GL_DEBUG_SEVERITY_HIGH |
Errors or dangerous behaviour |
GL_DEBUG_SEVERITY_MEDIUM |
Major performance warnings, other warnings or use of deprecated functionality. |
GL_DEBUG_SEVERITY_LOW |
Redundant state changes, unimportant undefined behaviour. |
GL_DEBUG_SEVERITY_NOTIFICATION |
A notification, not an error or performance issue. |
length参数是消息字符串的长度,不包括空终止符。 最后一个参数param是用户定义的指针。 我们可以使用它指向一些可能对回调函数有帮助的自定义对象。 例如,如果我们将消息记录到文件中,则可能指向包含文件I / O功能的对象。可以使用glDebugMessageCallback的第二个参数设置此参数。
在debugCallback中,我们将每个GLenum参数转换为字符串,此篇第一节中,我们写过getTypeString这个函数。
然后,我们将所有信息打印到标准输出。
对glDebugMessageCallback的调用使用OpenGL调试系统注册我们的回调函数。
第一个参数是指向我们的回调函数的指针,第二个参数(本例中为NULL)可以是指向我们想要传递给回调的任何对象的指针。
每次调用debugCallback时,此指针都作为最后一个参数传递。
最后,对glDebugMessageControl的调用确定了我们的消息过滤器。
此功能可用于有选择地打开或关闭消息源,类型,ID或严重性的任何组合。 在这个例子中,我们打开了一切。
一些扩展内容,可能不会用到,可跳过
OpenGL还为命名调试组的堆栈提供支持。 基本上这意味着我们可以记住堆栈上的所有调试消息过滤器设置,并在稍后进行一些更改后返回它们。 这可能很有用,例如,如果有一些代码段我们需要过滤某些类型的消息,而其他段我们需要一组不同的消息。涉及的函数是glPushDebugGroup和glPopDebugGroup。 对glPushDebugGroup的调用会生成类型为GL_DEBUG_TYPE_PUSH_GROUP的调试消息,并在堆栈上保留调试过滤器的当前状态。 然后我们可以使用glDebugMessageControl更改我们的过滤器,然后使用glPopDebugGroup返回到原始状态。 类似地,函数glPopDebugGroup生成类型为GL_DEBUG_TYPE_POP_GROUP的调试消息。
第三部分主要是用于我们下一篇的类所用的,暂时没有相关的测试
感觉像是在写说明书,没有办法,这种章节的特点就是这样,但又比较重要~
感谢您的阅读,生活愉快~