NVIDIA OptiX 光线追踪引擎是一种应用框架,可在 GPU 上实现出色的光线追踪性能。使用 OptiX 的应用程序可能会以难以诊断的方式失败:无效的 API 参数、黑帧或隐藏在数千个并发线程下的 GPU 侧错误。
NVIDIA OptiX 工具包 (OTK) 中的调试工具可以提供帮助。OTK 是一个 GitHub 资源库,其中包含一组实用程序,支持 GPU 光线追踪应用中常见的工作流。OTK 具有 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 示例。
如何始终如一地检查退货代码是否有错误
在后续故障隐藏原始问题之前,最好尽可能早地检测到本节中详述的错误。
API 机制
OptiX 中的大多数函数都会返回 OptixResult 错误代码,该代码若为零,则表示错误。CUDA 运行时 API 和 CUDA 驱动程序 API 遵循类似的模式。这三个 API 均支持以下内容:
- 错误代码的独特枚举类型;例如
OptixResult - 将错误代码的符号名称返回为字符串的函数;例如
OPTIX_ERROR_INVALID_VALUE - 一个函数,用于为错误代码返回人类可读错误消息;例如,
Invalid value
三个 API 的函数特征略有不同,但机制相同。
错误检查策略
在每个 API 调用站点手动处理这些错误代码既繁琐又容易出错。最好使用宏或函数调用来执行一致的策略来处理错误。OTK 提供了在检测到错误时实现两种策略的宏:
OTK_ERROR_CHECK( expr ):引发异常OTK_ERROR_CHECK_NOTHROW( expr ):将消息打印到std::cerr并继续
通过重复使用提供的一些机制并创建适当的宏,可以轻松实现其他策略。
尽可能减少使用宏观机械
这种错误检查机制尽可能减少使用宏,并将其委托给内联函数来完成实际工作。您可以在内联函数定义中设置断点,以便在函数检测到错误时停止执行调试器。
宏可提供有关导致错误的代码的诊断信息:
expr:为宏提供的参数的字符串形式。这是评估错误代码的表达式。__FILE__:调用宏的源文件的名称。__LINE__:调用宏的源文件中的行号。
来自宏调用站点的此信息将传递给执行实际错误检查的内联函数。
跨 API 进行统一错误检查
内联模板函数 checkError 检查状态代码是否存在错误,并在检测到故障时创建诊断消息。在这三个 API 中,简单地将错误代码转换为 bool 就足以指示错误。这三个 API 的状态代码均为零表示成功,非零表示失败。
错误消息的格式如下:
file(line): expr failed with error nnn (name): message
其中 expr 是评估表达式,nnn 是将状态代码投射到 int 的结果,name 是状态代码的符号名称,message 是人类可读错误消息。如果名称或消息为空,函数会将其省略。
内联模板函数 makeErrorString 负责构建此消息。它会调用内联模板函数 getErrorName 和 getErrorMessage 来构建组合消息。
每个 API 都有不同类型的 API 状态代码,因此您可以对模板函数进行专门化,以执行适当的 API 调用,从而获取扩展的错误信息。
用法
OTK 为每个 API 提供一个报文头,为所述模板函数提供必要的专门化 (表 1) 。
| 标题 | API |
<OptiXToolkit/Error/cuErrorCheck.h> |
CUDA 驱动 API |
<OptiXToolkit/Error/cudaErrorCheck.h> |
CUDA 运行时 API |
<OptiXToolkit/Error/optixErrorCheck.h> |
OptiX API |
只需包含您正在使用的 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为 truedumpSuppressed为 falsedebugIndexSet为 true- 当前启动索引与
debugIndex匹配
在 OptiX 工作流的启动参数中包含 DebugLocation 结构实例,以便对调试输出进行交互式控制。
debugInfoDump 函数
模板函数 debugInfoDump 提供用于发送调试信息的接口:
template <typename Callback>
static __forceinline__ __device__
bool debugInfoDump( const DebugLocation& debug,
const Callback &callback )
Callback 模板参数应为匹配以下内容的 struct 或 class:
struct Callback
{
void setColor( float red, float green, float blue );
void dump( const uint3& index );
};
setColor 方法用于在调试位置周围绘制视觉框,以便轻松识别屏幕上要为其丢弃信息的点。典型用法是设置与当前启动索引对应的输出像素的颜色。如果不希望直观指示调试位置,则方法可以直接为空。
dump 方法用于打印应用程序在所提供的启动索引上认为相关的任何信息。
显示调试位置
启用后,回调结构上的 setColor 方法会在屏幕上绘制一个方框,指示当前的调试位置,即使在禁止转储输出时也是如此。禁用时,此框处于隐藏状态。
调试位置的红色像素位于单像素宽的黑子内,而该黑子本身位于单像素宽的白子内。这提供了一个用于显示转储消息发生位置的高对比度指示器。如果输出缓冲区不是传统的颜色缓冲区,您可以自由地将提供的红色、绿色和蓝色值映射到一些独特的可视化值。
单步模式
为避免淹没调试输出,将调试输出设置为单次模式非常有用,在这种模式下,输出会被丢弃一次以响应用户控制。您可以按如下所示对此模式进行排序:
- 启用
DebugLocation机制 - 照常启动
- 当用户以交互方式选择调试位置时,将
dumpSuppressed设置为true,将debugIndexSet设置为true,并将debugIndex设置为所选位置 - 后续启动会显示调试位置,但该机制不会提供转储文件
- 用户与应用程序交互,将其操作为适当状态,可能会在此过程中移动调试位置
- 当用户表示需要当前位置的调试信息时,请将
dumpSuppressed设置为false - 启动以获取调试输出
- 启动后,将
dumpSuppressed设置回true
DemandPbrtScene 示例
OTK 中的 DemandPbrtScene 示例演示了 pbrt 版本 3 场景的按需加载几何图形。它使用 DebugLocation 机制,包括调试输出的单步行为和交互式切换,以及调试位置的交互式选择。它使用 ImGui 作为 UI 框架。

要在 OTK 中运行此示例,您需要 pbrt-v3 的场景文件。
开始使用 NVIDIA OptiX 工具套件进行调试
OptiX 工具包为常见的 OptiX 开发问题提供可重复使用的调试和测试实用程序:一致的 API 错误检查和有针对性的设备端调试输出。您可通过 NVIDIA/optix-toolkit GitHub 资源库获取该代码。
准备好开始了吗?从 GitHub 下载 OptiX 工具包,首先在调试构建中启用 OptiX 验证,使用 OTK_ERROR_CHECK 包装 CUDA 和 OptiX 调用,并在开发初期使用 DebugLocation 隔离 GPU 端错误。OTK 在宽松的 BSD 3 条款式许可证下提供,因此您可以直接复制、调整这些实用程序并将其集成到您自己的 OptiX 应用中。