NVIDIA OptiX光线追踪引擎是一款专为在GPU上实现最优光线追踪性能而设计的应用框架。使用OptiX的应用程序可能会以难以诊断的方式出现故障,例如无效的API参数、全黑画面,或隐藏在数千个并发线程中的GPU端错误。
NVIDIA OptiX工具包(OTK)提供的调试工具可以帮助解决上述问题。OTK是一个GitHub代码仓库,包含一系列支持GPU光线追踪应用常见工作流程的实用工具,并采用BSD 3条款风格的许可证,允许用户自由复制和修改其中的代码。
本文将重点介绍OTK的两项调试功能:对OptiX和CUDA API错误码的一致性检查,以及有针对性的设备端调试打印。OTK还提供了一个示例程序DemandPbrtScene,展示了设备端调试打印在实际场景中的应用。
OptiX日志背景
在使用OTK提供的辅助功能之前,有必要先了解一些关于OptiX日志的背景知识。创建OptiX设备上下文时,需要提供一个选项结构体,用于配置日志回调和验证模式。当验证模式设置为OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL时,OptiX能够协助验证API函数的输入参数。
OptiX会将验证错误的可读信息写入日志。遇到类似OPTIX_ERROR_INVALID_VALUE这样的API错误时,日志输出应是首先排查的地方。验证功能会在API层面带来一定的额外开销,因此建议在调试和测试版本中启用全部验证,而在正式发布版本中关闭验证。更多详情请参阅OptiX SDK示例。
尽早发现错误
在后续失败掩盖原始问题之前,尽早发现错误至关重要。
OptiX中的大多数函数会返回一个OptixResult错误码,非零值表示发生了错误。CUDA运行时API和CUDA驱动API遵循类似的模式。三套API均支持以下机制:
- 用于错误码的独立枚举类型,例如OptixResult
- 返回错误码符号名称字符串的函数,例如OPTIX_ERROR_INVALID_VALUE
- 返回错误码可读信息的函数,例如"Invalid value"
这三套API中上述函数的签名略有不同,但机制相同。
OTK错误检查宏
在每个API调用处手动处理这些错误码既繁琐又容易出错,最好使用宏或函数调用来统一执行错误处理策略。OTK提供了两种错误检测策略的宏:
- OTK_ERROR_CHECK( expr ):抛出异常
- OTK_ERROR_CHECK_NOTHROW( expr ):向std::cerr打印信息并继续执行
通过复用已有机制并创建适当的宏,也可以轻松实现其他处理策略。
该错误检查机制将宏的使用降至最低,并将实际工作委托给内联函数完成。可以在内联函数定义处设置断点,使调试器在检测到错误时暂停执行。
宏的作用在于提供导致错误的代码的诊断信息:
- expr:传入宏的参数字符串形式,即求值为错误码的表达式
- __FILE__:调用宏所在的源文件名称
- __LINE__:调用宏所在源文件的行号
这些来自宏调用处的信息将被传递给负责实际错误检查的内联函数。
错误检查的内部实现
内联模板函数checkError用于检查状态码是否存在错误,并在检测到失败时生成诊断信息。对于上述三套API,将错误码简单转换为bool类型即可判断是否出错。三套API均以零表示成功,非零表示失败。
错误信息的格式如下:
file(line): expr failed with error nnn (name): message
其中expr为求值后的表达式,nnn为将状态码转换为int后的结果,name为状态码的符号名称,message为可读的错误信息。若name或message为空,函数将自动省略对应部分。
内联模板函数makeErrorString负责构建该信息,它调用内联模板函数getErrorName和getErrorMessage来合并生成完整信息。由于每套API的状态码类型各不相同,可以对模板函数进行特化,以调用相应API获取扩展错误信息。
OTK为每套API提供一个头文件,包含上述模板函数的必要特化实现(见表1)。只需引入所用API对应的头文件,并在所有调用处统一使用OTK_ERROR_CHECK宏即可。以下示例同时使用了三套API:
OTK_ERROR_CHECK( cudaSetDevice( m_deviceIndex ) );
OTK_ERROR_CHECK( cuCtxGetCurrent( &m_cudaContext ) );
OTK_ERROR_CHECK( cuStreamCreate( &m_stream, CU_STREAM_DEFAULT ) );
OTK_ERROR_CHECK( optixInit() );
设备端调试打印
图形应用程序的问题在于,导致黑屏的原因实在太多。要调试OptiX设备端代码的问题,可以采取以下几种方式:
- 在设备代码的调试版本上使用CUDA调试器
- 通过printf在发布版本的设备代码中获取信息
许多应用程序在以调试模式编译时运行速度过慢,影响交互式调试器的正常使用。
printf式调试的主要难点在于GPU上有大量线程并发运行,输出内容极易泛滥成灾。此外,问题可能只在与应用程序进行一定交互后才会出现,问题可视化之前产生的调试输出纯属干扰,反而妨碍找到有效信息。
DebugLocation机制
头文件<OptiXToolkit/ShaderUtil/DebugLocation.h>提供了一种可复用的调试输出机制。DebugLocation结构体用于控制其行为:
struct DebugLocation {
bool enabled;
bool dumpSuppressed;
bool debugIndexSet;
uint3 debugIndex;
};
其中enabled成员用于开关整个机制;dumpSuppressed成员在机制启用时关闭调试输出;debugIndexSet成员表示debugIndex中已存储有效的启动索引。
满足以下条件时,该机制将输出调试信息:
- enabled为true
- dumpSuppressed为false
- debugIndexSet为true,且当前启动索引与debugIndex匹配
在OptiX管线的启动参数中加入DebugLocation结构体实例,即可实现对调试输出的交互式控制。
debugInfoDump函数
模板函数debugInfoDump提供了发出调试信息的接口:
template <typename Callback>
static __forceinline__ __device__ bool debugInfoDump(
const DebugLocation& debug,
const Callback &callback )
Callback模板参数应为符合以下定义的结构体或类:
struct Callback {
void setColor( float red, float green, float blue );
void dump( const uint3& index );
};
setColor方法用于在调试位置周围绘制可视化方框,便于在屏幕上直观定位正在输出信息的像素点,通常做法是为对应当前启动索引的输出像素设置颜色。若不需要可视化标记,该方法可留空。
dump方法用于打印应用程序在指定启动索引处认为相关的任何信息。
调试位置的可视化标记
启用机制后,即使关闭了dump输出,回调结构体的setColor方法也会在屏幕上绘制方框以标示当前调试位置;禁用时方框隐藏。
调试位置的红色像素位于一个一像素宽的黑色方框内,黑色方框外再套一个一像素宽的白色方框,从而形成高对比度的位置指示器。若输出缓冲区不是传统的颜色缓冲区,可以自由地将提供的红、绿、蓝值映射为某种便于可视化的特殊值。
一次性调试输出模式
为避免调试输出泛滥,建议采用一次性输出模式,即根据用户操作仅输出一次调试信息。可按以下顺序使用该模式:
1. 启用DebugLocation机制
2. 按常规发起启动
3. 当用户交互式选择调试位置时,将dumpSuppressed设为true、debugIndexSet设为true,并将debugIndex设为所选位置
4. 后续启动将显示调试位置标记,但不会输出dump信息
5. 用户与应用程序交互,将其调整至适当状态,期间可移动调试位置
6. 当用户希望获取当前位置的调试信息时,将dumpSuppressed设为false
7. 发起启动以获取调试输出
8. 启动完成后将dumpSuppressed重新设为true
DemandPbrtScene示例
OTK中的DemandPbrtScene示例演示了针对pbrt第3版场景的按需加载几何体功能,其中使用了DebugLocation机制,包括一次性输出行为、调试输出的交互式切换以及调试位置的交互式选取,UI框架采用ImGui。如需从OTK运行此示例,需要准备一个pbrt-v3的场景文件。
总结
OptiX工具包为常见的OptiX开发问题提供了可复用的调试与测试工具:一致性API错误检查和有针对性的设备端调试输出。相关代码可通过NVIDIA/optix-toolkit GitHub仓库获取。
如需立即上手,可从GitHub下载OptiX工具包,从在调试版本中启用OptiX验证开始,使用OTK_ERROR_CHECK封装CUDA和OptiX调用,并借助DebugLocation在开发早期隔离GPU端错误。OTK采用宽松的BSD 3条款风格许可证,可直接将这些工具复制、改编并集成到自己的OptiX应用程序中。
Q&A
Q1:NVIDIA OptiX工具包(OTK)是什么?有什么用?
A:OTK是NVIDIA提供的一个开源GitHub代码仓库,包含一系列用于GPU光线追踪应用开发的实用工具。它主要提供两大调试能力:对OptiX和CUDA API错误码的一致性检查,以及有针对性的设备端调试打印功能,帮助开发者更高效地定位和修复光线追踪应用中的问题。OTK采用BSD 3条款许可证,可自由复制和修改。
Q2:OTK_ERROR_CHECK宏怎么用?和直接判断返回值有什么区别?
A:OTK_ERROR_CHECK是一个封装宏,只需将API调用包裹其中,例如OTK_ERROR_CHECK( optixInit() ),它会在检测到非零错误码时自动抛出异常,并附带出错文件名、行号和错误详情。相比手动判断每个调用的返回值,这种方式更简洁、更不容易遗漏,也更便于调试器设置断点。OTK还提供了不抛异常、仅打印信息的OTK_ERROR_CHECK_NOTHROW版本。
Q3:DebugLocation机制如何避免GPU调试时输出内容过多?
A:DebugLocation通过只在特定启动索引(即屏幕上特定像素位置)处输出调试信息来解决这一问题,避免了数千个并发线程同时打印造成的信息泛滥。配合一次性输出模式,可以先让应用程序运行到目标状态,再由用户手动触发单次dump输出,之后自动关闭,从而精准获取所需调试信息,不受无关输出干扰。
