1-输出调试信息
在编写OpenGL程序时,难免会出错。一些比较明显的错误(如写错函数名等)是容易被发现并纠错的,但还有一些不明显的错误(写错参数等)不容易发现,我们只能看错误的输出结果干瞪眼。还好,OpenGL为我们提供了一些输出调试信息的途径,如glGetError()
和glDebugMessageCallback()
等。
接下来简单看看如何应用它们。
输出调试信息
glGetError()
static void GLClearError()
{
while (glGetError() != GL_NO_ERROR);
}
static bool GLLogCall(const char* functionName, const char* filename, int line)
{
while (GLenum error = glGetError())
{
std::cerr << std::format("[OpenGL ERROR]:code {:#x} at {}, in {}({}).\n", error, functionName, filename, line);
return false;
}
return true;
}
// MSVC编译器触发中断
#define GL_ASSERT(x) if (!(x)) __debugbreak()
#define GLCall(x) GLClearError();\
x;\
GL_ASSERT(GLLogCall(#x, __FILE__, __LINE__))
// 这里#x就是把传进来的x看成字符串,方便获取函数名
如上述代码所示,通过调用GLCall(x)
宏进行调试信息的输出。首先用GLClearError()
清空OpenGL累积的错误,然后尝试执行OpenGL函数x,最后检查x是否出错,出错就触发断点并输出错误信息。
glGetError()
有良好的兼容性,但这样得到的错误状态仍不明确:
- 它向OpenGL询问错误状态,而这个错误的来源具体是哪个函数引发的不清楚。
- 返回的是错误码,需要开发者自己寻找出错的原因。
glDebugMessageCallback()
在OpenGL4.3中添加,它允许我们定义错误产生时进行处理的回调函数,处理起问题更加详细灵活。在使用它之前,得先在OpenGL上下文中配置调试环境:
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
要想检查该OpenGL上下文是否启用DEBUG,可以使用以下代码检查:
int flags;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
// 成功启用DEBUG,配置DebugMessage相关内容...
}
在配置相关内容前,得先自定义回调函数,它的原型如下:
typedef void (APIENTRY *DEBUGPROC)(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
const void *userParam);
其中,调试信息的来源source
如下表:
source | 解释 |
---|---|
GL_DEBUG_SOURCE_API | OpenGL API |
GL_DEBUG_SOURCE_WINDOW_SYSTEM | 窗口系统 |
GL_DEBUG_SOURCE_SHADER_COMPILER | 着色器的编译器 |
GL_DEBUG_SOURCE_THIRD_PARTY | 第三方库 |
GL_DEBUG_SOURCE_APPLICATION | 应用程序 |
GL_DEBUG_SOURCE_OTHER | 其他 |
调试信息的类型type
如下表:
type | 解释 |
---|---|
GL_DEBUG_TYPE_ERROR | 错误(glError) |
GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR | 尝试使用已弃用功能 |
GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR | 未定义行为 |
GL_DEBUG_TYPE_PORTABILITY | 可移植性问题 |
GL_DEBUG_TYPE_PERFORMANCE | 性能问题 |
GL_DEBUG_TYPE_MARKER | 注解 |
GL_DEBUG_TYPE_PUSH_GROUP | 调用了glPushDebugGroup() |
GL_DEBUG_TYPE_POP_GROUP | 调用了glPopDebugGroup() |
GL_DEBUG_TYPE_OTHER | 其他行为 |
调试信息的消息等级severity
如下表:
severity | 描述 |
---|---|
GL_DEBUG_SEVERITY_HIGH | 致命错误,如OpenGL错误,着色器编译失败等 |
GL_DEBUG_SEVERITY_MEDIUM | 警告,如可移植性能警告等 |
GL_DEBUG_SEVERITY_LOW | 一般消息,如冗余的状态切换 |
GL_DEBUG_SEVERITY_NOTIFICATION | 最普通的通知消息 |
另外两项是调试信息和用户自定义参数。
下例是自定义函数的一个可能实现:
// glDebug.h
#ifdef NEW_GL_DEBUG
#define GLCall(x) GLLogCall(#x, __FILE__, __LINE__);\
x;\
glPopDebugGroup()
void GLLogCall(const char* funcName, const char* filename, int line);
// 回调函数
void GLAPIENTRY GLDebugCallback(unsigned source,
unsigned type,
unsigned id,
unsigned severity,
int length,
const char* message,
const void* userParam);
#endif
// glDebug.cpp
#ifdef NEW_GL_DEBUG
void GLLogCall(const char* funcName, const char* filename, int line)
{
std::string simpleFilename(filename);
simpleFilename = simpleFilename.substr(simpleFilename.find_last_of('\\') + 1);
const std::string log = std::format("{}({}): {}", simpleFilename, line, funcName);
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, log.size(), log.c_str());
}
struct debugMsg
{
std::string msg;
unsigned int id;
};
// 回调函数
void GLAPIENTRY GLDebugCallback(unsigned source,
unsigned type,
unsigned id,
unsigned severity,
int length,
const char* message,
const void* userParam)
{
std::string sourceStr;
switch (source)
{
case GL_DEBUG_SOURCE_API: sourceStr = "API"; break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: sourceStr = "Window System"; break;
case GL_DEBUG_SOURCE_SHADER_COMPILER: sourceStr = "Shader Compiler"; break;
case GL_DEBUG_SOURCE_THIRD_PARTY: sourceStr = "Third Party"; break;
case GL_DEBUG_SOURCE_APPLICATION: sourceStr = "Application"; break;
case GL_DEBUG_SOURCE_OTHER: sourceStr = "Other"; break;
default: sourceStr = "Unknown"; break;
}
std::string typeStr;
switch (type)
{
case GL_DEBUG_TYPE_ERROR: typeStr = "Error"; break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeStr = "Deprecated Behavior"; break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeStr = "Undefined Behavior"; break;
case GL_DEBUG_TYPE_PORTABILITY: typeStr = "Portability"; break;
case GL_DEBUG_TYPE_PERFORMANCE: typeStr = "Performance"; break;
case GL_DEBUG_TYPE_MARKER: typeStr = "Marker"; break;
case GL_DEBUG_TYPE_PUSH_GROUP: typeStr = "Push Group"; break;
case GL_DEBUG_TYPE_POP_GROUP: typeStr = "Pop Group"; break;
case GL_DEBUG_TYPE_OTHER: typeStr = "Other"; break;
default: typeStr = "Unknown"; break;
}
static std::vector<debugMsg> msgQueue;
if (type == GL_DEBUG_TYPE_POP_GROUP)
{
if (!msgQueue.empty() && msgQueue.at(msgQueue.size() - 1).id != 1)
{
for (auto& debugMsg : msgQueue)
{
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH: LOG_CRITICAL("{0}", debugMsg.msg); GL_ASSERT(false); break;
case GL_DEBUG_SEVERITY_MEDIUM: LOG_ERROR("{0}", debugMsg.msg); break;
case GL_DEBUG_SEVERITY_LOW: LOG_WARN("{0}", debugMsg.msg); break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
default: LOG_INFO("{0}", debugMsg.msg); break;
}
}
msgQueue.clear();
}
else
{
msgQueue.clear();
}
}
else
{
msgQueue.push_back({
std::format("[GL {}] [{}({})] {}", sourceStr, typeStr, id, message),
id
});
}
}
#endif
这里我为了准确定位调试信息,在GLLogCall()
中将包含函数名、文件、行数等调试信息插入到GL_DEBUG_TYPE_PUSH_GROUP
中。这样子得到调试信息的格式如下:
[OpenGL Application] [Push Group(1)] xxx.cpp(xx): glXXXX()
[OpenGL API] [Other(123456)] 可能出现的调试信息
[OpenGL Application] [Pop Group(1)] xxx.cpp(xx): glXXXX()
大多数情况下控制台只会输出Push/Pop信息,反而显得更繁琐,于是我在下面简单实现了只输出Push信息和调试信息的功能(就是msgQueue那几行),效果如下(这里改用spdlog
库了,很好用!):
可能有更好的解决方案,欢迎讨论~
参考资料
- S10.错误处理-OpenGL-The Cherno
- docs.gl
- OpenGL程序调试输出_opengl开发三维应用程序怎么输出-CSDN博客