Debugging Ray Tracing Applications Using NVIDIA OptiX Toolkit NVIDIA OptiX Toolkit (OTK) provides debugging facilities for GPU ray tracing applications, including consistent checking of OptiX and CUDA API error codes and targeted device-side debug printing. OTK, a GitHub repository with a BSD 3-clause license, offers macros like OTK_ERROR_CHECK and OTK_ERROR_CHECK_NOTHROW to enforce error handling policies, while OptiX's validation mode and log callback help diagnose API errors early. NVIDIA OptiX https://developer.nvidia.com/rtx/ray-tracing/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 https://github.com/NVIDIA/optix-toolkit 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 https://github.com/NVIDIA/optix-toolkit/tree/3b9d3c6d08c884372ffc85270c987d1f5273a985/examples/DemandLoading/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 https://github.com/NVIDIA/optix-sdk/tree/main/SDK . 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 https://docs.nvidia.com/cuda/cuda-runtime-api/index.html and the CUDA driver API https://docs.nvidia.com/cuda/cuda-driver-api/index.html 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: : Throw an exception OTK ERROR CHECK expr : Print a message to OTK ERROR CHECK NOTHROW expr 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 . Header | API |