Article summary — original

Debugging Ray Tracing Applications with NVIDIA OptiX Toolkit The open-source NVIDIA OptiX Toolkit provides utilities to diagnose ray tracing issues on the GPU. It focuses on two core debugging facilities: consistent cross-API error checking for OptiX and CUDA, and targeted device-side debug printing to filter output from thousands of concurrent threads. OptiX Toolkit (OTK) is a BSD 3-clause licensed GitHub repository offering shared utilities for GPU ray tracing development. OTK unifies error checking across the OptiX API, CUDA runtime API, and CUDA driver API using macros like OTK_ERROR_CHECK and inline template functions. Targeted device-side printing uses the DebugLocation structure and debugInfoDump template to restrict output to a specific launch index, avoiding thread output flooding. A visual high-contrast bounding box and a one-shot dumping mode let developers interactively inspect specific pixels using UI frameworks like ImGui in the DemandPbrtScene example. API Error Checking and Validation OptiX provides built-in validation via OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL, which outputs human-readable errors to a log callback during debug and test builds. Because OptiX, the CUDA runtime API, and the CUDA driver API all use zero for success and non-zero for errors, OTK introduces unified inline template functions and macros (OTK_ERROR_CHECK to throw exceptions and OTK_ERROR_CHECK_NOTHROW to log to std::cerr). These macros pass file, line, and expression details to inline functions, generating formatted messages with status codes and symbolic error names without heavy macro overhead. Targeted Device-Side Debug Printing Traditional GPU debugging via interactive debuggers can be prohibitively slow, while release-mode printf statements often overwhelm developers due to massive thread concurrency. OTK addresses this through <OptiXToolkit/ShaderUtil/DebugLocation.h>, which filters output using a DebugLocation configuration. Debug printing triggers only when the system is enabled, dump suppression is false, an index is set, and the current launch index matches the target debugIndex. DebugLocation Visuals and One-Shot Workflow The debugInfoDump device template works with a callback interface containing dump and setColor methods. The setColor method visually outlines the target debug location on-screen with a red pixel nested inside nested black and white boxes. Developers can run in a one-shot mode: suppressing dumps while interactively positioning the target pixel and manipulating the application state, unsuppressing output for a single launch to collect logs, and immediately re-suppressing dumps afterward, as illustrated in OTK's DemandPbrtScene sample.

Article summary — zh

利用 NVIDIA OptiX Toolkit 调试 GPU 光线追踪应用 基于 GPU 的 NVIDIA OptiX 光线追踪应用常面临 API 参数错误、黑屏或并发线程掩盖 GPU 端 Bug 等排查难题。开源工具库 NVIDIA OptiX Toolkit(OTK,基于 BSD 3-Clause 许可证)提供了统一的 API 错误检查机制与目标化设备端调试打印功能,以降低调试难度。 OptiX 内置验证模式(OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL)可在调试阶段捕获 API 错误并输出日志,但具有一定开销,建议在发布版本中关闭。 OTK 提供了宏(如 OTK_ERROR_CHECK 和 OTK_ERROR_CHECK_NOTHROW)与内联模板函数,统一对 OptiX、CUDA Runtime 及 CUDA Driver API 的错误代码进行一致性拦截与详细信息格式化。 针对海量 GPU 线程导致的 printf 淹没问题,OTK 通过 DebugLocation 结构和 debugInfoDump 机制实现了针对特定 launch index 的精准打印与视觉定位框标示。 OTK 中的 DemandPbrtScene 示例结合 ImGui 界面展示了单次触发(one-shot)模式,方便用户交互式选择调试位置并仅在需要时输出数据。 统一的 API 错误检查机制 OptiX 与 CUDA API 普遍采用返回非零状态码表示失败的模式。OTK 通过最小化宏与内联模板函数(如 checkError 和 makeErrorString),将调用位置的文件名、行号、表达式、错误代码及其文字描述组合为统一格式的诊断消息。开发者可通过引入对应头文件,直接使用 OTK_ERROR_CHECK 宏统一包裹各类 API 调用,并可选择在发现错误时抛出异常或输出到终端。 目标化设备端调试与单次打印模式 常规 GPU 设备端调试易受交互式调试器性能下降或大规模并行 printf 日志刷屏的阻碍。OTK 引入 DebugLocation 结构与 debugInfoDump 模板函数,允许开发者将调试限定在特定线程(debugIndex)。通过配套的回调接口,系统能在目标像素周围绘制高对比度的黑白嵌套方框以可视化定位,并支持单次触发(one-shot)模式,仅在用户交互到特定状态时输出一次日志,有效抑制干扰信息。

Saved original article

NVIDIA OptiX ray tracing engine is an application framework for achieving optimal ray tracing performance on the GPU. Applications using OptiX can fail in ways that are difficult to diagnose: an invalid API argument, a black frame, or a GPU-side bug buried under thousands of concurrent threads. Debugging facilities in the NVIDIA OptiX Toolkit (OTK) can help. OTK is a GitHub repository containing a set of utilities supporting workflows common in GPU ray tracing applications. OTK is licensed with a BSD 3-clause style license, so you are free to copy and modify any of the code. This post covers two OTK debugging facilities: consistent checking of OptiX and CUDA API error codes and targeted device-side debug printing. OTK includes an example program, DemandPbrtScene , that shows device-side debug printing used in context. How do OptiX logging and validation work? Before getting to the assistance provided by OTK, some background on the OptiX log would be useful. When you create an OptiX device context, you supply an options structure that allows you to configure a log callback and a validation mode. OptiX can assist in validating inputs to API functions when the validation mode is set to OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL . OptiX writes human-readable messages for validation errors to the log. The log output should be the first place you look for API errors like OPTIX_ERROR_INVALID_VALUE .  Validation does impose some additional cost in the API. It’s recommended to enable all validation in debug and testing builds and omitting validation for release builds. For more details, see the OptiX SDK samples . How to consistently check return codes for errors It’s best to detect errors as early as possible as detailed in this section, before subsequent failures mask the original problem. API mechanisms Most functions in OptiX return an OptixResult error code that, when non-zero, indicates an error. The CUDA runtime API and the CUDA driver API follow a similar pattern. All three APIs support the following: A distinct enumerated type for the error code; for example OptixResult A function to return a symbolic name for an error code as a string; for example OPTIX_ERROR_INVALID_VALUE A function to return a human-readable error message for an error code; for example, Invalid value The signatures of these functions are slightly different for the three APIs, but the mechanisms are the same. Error checking policy Handling these error codes manually at every API call site is tedious and error-prone. It is better to use macros or function calls to enforce a consistent policy for handling errors. OTK provides macros that implement two policies when an error is detected: OTK_ERROR_CHECK( expr ) : Throw an exception OTK_ERROR_CHECK_NOTHROW( expr ) : Print a message to std::cerr and continue Other policies are easily implemented by reusing some of the provided machinery and creating an appropriate macro. Minimal use of macro machinery This error checking mechanism uses macros to a minimal extent and delegates to inline functions to do the actual work. You can set breakpoints in the inline function definitions to have the debugger stop execution when the function detects an error. The macros exist to provide diagnostic information about the code that caused the error: expr : A string form of the supplied argument to the macro. This is the expression that evaluates to the error code. __FILE__ : The name of the source file where the macro was invoked. __LINE__ : The line number within the source file where the macro was invoked. This information from the macro call site is passed to the inline function that does the actual error checking. Unified error checking across APIs The inline template function checkError checks the status code for an error and creates a diagnostic message if it detects a failure. In the case of these three APIs, a simple cast of the error code to bool suffices to indicate an error. All three APIs use a status code of zero to indicate success and non-zero to indicate failure. Error messages are formatted as follows: file(line): expr failed with error nnn (name): message Where expr is the evaluated expression, nnn is the result of casting the status code to an int , name is the symbolic name of the status code, and message is the human-readable error message. If either the name or message are empty, the function omits them. The inline template function makeErrorString is responsible for building this message. It calls the inline template functions getErrorName and getErrorMessage to build the combined message. Each API has a distinct type for API status codes, so you can specialize the template functions to perform the appropriate API call to get the extended error information. Usage OTK provides one header per API that provides the necessary specializations of the template functions described (Table 1). Table 1.  Error-checking headers Simply include the headers for the APIs you are using and use the single macro OTK_ERROR_CHECK around all call sites. The following example uses all three APIs. 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() ); How to perform targeted device-side debug printing The problem with graphics applications is that there are too many ways to code up a black screen. To debug problems in your OptiX device code, you can take several approaches. A couple of obvious choices spring to mind: Use the CUDA debugger on a debug build of the device code Get information from a release build of the device code with printf Many applications run too slowly when compiled in debug mode, impairing the use of interactive debuggers. The main difficulty with printf -style debugging is that there are so many threads running concurrently on the GPU that you can end up drowning in a firehose of output. In addition, it could be that the issue only arises after a certain amount of interaction with the application.  Debug output prior to the problem manifesting visually is just noise that gets in the way of finding the desired information. DebugLocation The header <OptiXToolkit/ShaderUtil/DebugLocation.h> provides a reusable mechanism for debug output. The structure DebugLocation controls the behavior: struct DebugLocation { bool enabled; bool dumpSuppressed; bool debugIndexSet; uint3 debugIndex; }; The enabled member turns the entire mechanism on or off. The dumpSuppressed member turns off the debug output even if the mechanism is enabled. The debugIndexSet member indicates that a valid launch index has been stored in debugIndex . The mechanism will output debug information when the following conditions are true: enabled is true dumpSuppressed is false debugIndexSet is true, and The current launch index matches debugIndex Include an instance of the DebugLocation structure in the launch parameters of the OptiX pipeline for interactive control of debug output. The debugInfoDump function The template function debugInfoDump provides the interface for emitting debug information: template <typename Callback> static __forceinline__ __device__ bool debugInfoDump( const DebugLocation& debug, const Callback &callback ) The Callback template parameter should be a struct or class matching the following: struct Callback { void setColor( float red, float green, float blue ); void dump( const uint3& index ); }; The setColor method is used to draw a visual box around the debug location for easy identification of the point on the screen for which information is dumped. The typical usage is to set the color for the output pixel corresponding to the current launch index. If no visual indication of the debug location is desired, the method can simply be empty. The dump method is used to print whatever information the application considers relevant at the supplied launch index. Displaying the debug location When enabled, the setColor method on the callback structure draws a box on screen to indicate the current debug location, even when dump output is suppressed. The box is hidden when disabled. A red pixel at the debug location sits inside a one-pixel-wide black box, itself inside a one-pixel-wide white box. This provides a high-contrast indicator of the location where dump messages are occurring. If the output buffer is not a traditional color buffer, you are free to map the supplied red, green, and blue values to some distinctive value for visualization. One-shot mode To avoid drowning in debug output, it’s useful to have the debug output in a one-shot mode,  where the output is dumped once in response to user control. You can sequence this mode as follows: Enable the DebugLocation mechanism Launch as usual When the user interactively selects the debug location, set dumpSuppressed to true , debugIndexSet to true , and debugIndex to the selected location Subsequent launches display the debug location, but the mechanism provides no dumps The user interacts with the application to manipulate it into the appropriate state, possibly moving the debug location along the way When the user indicates that they want debug information for the current location, set dumpSuppressed to false Launch to get the debug output Set dumpSuppressed back to true after launch DemandPbrtScene example The DemandPbrtScene example in OTK demonstrates demand loaded geometry for pbrt version 3 scenes . It uses the DebugLocation mechanism, including one-shot behavior and interactive toggling of debug output and interactive selection of the debug position. It uses ImGui as the UI framework. Figure 1. DemandPbrtScene debug controls with highlighted debug pixel To run this example from OTK, you will need a scene file for pbrt-v3 . Get started debugging with NVIDIA OptiX Toolkit The OptiX Toolkit provides reusable debugging and testing utilities for common OptiX development problems: consistent API error checking and targeted device-side debug output. The code is available through the NVIDIA/optix-toolkit GitHub repo. Ready to get started? Download the OptiX Toolkit from GitHub and start by enabling OptiX validation in your debug builds, wrapping your CUDA and OptiX calls with OTK_ERROR_CHECK , and using DebugLocation to isolate GPU-side bugs earlier in development. OTK is available under a permissive BSD 3-clause-style license, so you can copy, adapt, and integrate these utilities directly into your own OptiX applications.