This post was updated on April 17, 2024.
For developers working on ray tracing applications for both DirectX 12 and Vulkan, ray tracing validation is here to help you improve performance, find hard-to-debug issues, and root-cause crashes.
Unlike existing debug solutions, ray tracing validation performs checks at the driver level, which enables it to identify potential problems that cannot be caught by tools such as the D3D12 Debug Layer and Vulkan Validation layer. Warnings and errors are delivered straight from the driver to the application with a callback, where they can be processed through existing application-side debugging or logging systems.
I highly recommend enabling ray tracing validation during any feature development and functional testing wherever possible.
Requirements
Using the Ray Tracing Validation Layer with DirectX 12 requires an NVIDIA driver from version 551.61 or later, and a version of NVIDIA API (NvAPI) 545 or later. For use in Vulkan, all that’s required is an NVIDIA driver from version 560.70 for Windows or version 560.28.03 for Linux.
Enabling ray tracing validation
DirectX 12
To enable ray tracing validation, set the NV_ALLOW_RAYTRACING_VALIDATION=1
environment variable on your development machine. Note this will require a restart of your IDE, but if the NVAPI_ACCESS_DENIED
error persists a machine restart may be required.
Create a D3D12 device and then initialize NVAPI.
Enable ray tracing validation on this device with the NvAPI_D3D12_EnableRaytracingValidation
function. Ray tracing validation must be enabled before using any ray tracing functions on the device (including capability queries), otherwise the function will fail with the NVAPI_INVALID_CALL
error.
Customize the NVAPI_D3D12_RAYTRACING_VALIDATION_MESSAGE_CALLBACK
function to output to your debugger or logging system of choice and then register it with NvAPI_D3D12_RegisterRaytracingValidationMessageCallback
.
Flush validation messages to the registered callback after fence signals or in your device-removal handler with NvAPI_D3D12_FlushRaytracingValidationMessages
.
NvAPI_D3D12_FlushRaytracingValidationMessages
reports any validation messages to the registered callback for work that has been completed on the GPU at the time of the call. This enables you to control the granularity of the messages reported. Crucially, explicit flushing also enables the processing of validation messages even after a device removal error.
DirectX 12 application example
// Validation callback
static void __stdcall myValidationMessageCallback(void* pUserData, NVAPI_D3D12_RAYTRACING_VALIDATION_MESSAGE_SEVERITY severity, const char* messageCode, const char* message, const char* messageDetails)
{
const char* severityString = "unknown";
switch (severity)
{
case NVAPI_D3D12_RAYTRACING_VALIDATION_MESSAGE_SEVERITY_ERROR: severityString = "error"; break;
case NVAPI_D3D12_RAYTRACING_VALIDATION_MESSAGE_SEVERITY_WARNING: severityString = "warning"; break;
}
fprintf(stderr, "Ray Tracing Validation message: %s: [%s] %s\n%s", severityString, messageCode, message, messageDetails);
fflush(stderr);
}
// Enable Ray Tracing Validation
void onCreate()
{
ID3D12Device* device = MyCreateD3DDevice();
NvAPI_Initialize();
if (validationMode) {
NvAPI_D3D12_EnableRaytracingValidation(device, NVAPI_D3D12_RAYTRACING_VALIDATION_FLAG_NONE);
NvAPI_D3D12_RegisterRaytracingValidationMessageCallback(device, &myValidationMessageCallback, (void*)&myCallbackData, &nvapiValidationCallbackHandle);
}
}
// Flush the validation message after a fence signal
void waitForGPU(UINT64 fenceValue)
{
fence->SetEventOnCompletion(fenceValue, fenceEvent);
WaitForSingleObjectEx(fenceEvent, INFINITE, FALSE);
if (validationMode)
NvAPI_D3D12_FlushRaytracingValidationMessages(device);
}
// We highly recommend checking for DXGI_ERROR_DEVICE_REMOVED and flushing the validation message if this occurs
void onDeviceRemoved()
{
// flushing after DXGI_ERROR_DEVICE_REMOVED is OK
if (validationMode)
NvAPI_D3D12_FlushRaytracingValidationMessages(device);
}
Vulkan
To enable ray tracing validation, set the NV_ALLOW_RAYTRACING_VALIDATION=1
environment variable on your development machine. Note this will require a restart of your IDE and that without this environment variable set, the extension won’t be reported by the Vulkan Caps Viewer tool.
Vulkan offers ray tracing validation support through the VK_NV_ray_tracing_validation
extension. This extension should be enabled and its feature bit rayTracingValidation
to true.
The ray tracing validation warnings and errors are reported through the VK_EXT_debug_utils
extension message callbacks. This callback should be registered with the application’s Vulkan instance. There is no need to manually flush the validation messages on Vulkan, these are flushed by the driver automatically at device idle and device lost.
Vulkan application example
The following code example shows how to enable and use ray tracing validation in a Vulkan application.
VkDebugUtilsMessengerEXT debugUtilsMessenger = VK_NULL_HANDLE;
// Callback function
VkBool32 __stdcall debugUtilsMessengerCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
std::string prefix("");
if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
prefix = "WARNING: ";
}
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
prefix = "ERROR: ";
}
// Display message to default output (console/logcat)
std::stringstream debugMessage;
debugMessage << prefix << "[" << pCallbackData->messageIdNumber << "][" << pCallbackData->pMessageIdName << "] : " << pCallbackData->pMessage;
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
std::cout << debugMessage.str() << "\n";
} else {
std::cout << debugMessage.str() << "\n";
}
fflush(stdout);
return VK_FALSE;
}
// Check for ray tracing validation support and enable the feature
VkPhysicalDeviceRayTracingValidationFeaturesNV validationFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_VALIDATION_FEATURES_NV};
VkPhysicalDeviceFeatures2 features = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
features.pNext = &validationFeatures;
vkGetPhysicalDeviceFeatures2(m_physicalDevice, &features);
// Confirm that the ray tracing validation feature is supported
assert(validationFeatures.rayTracingValidation == true);
// If ray tracing validation is supported enable it by passing the validationFeature structure to the pNext chain of VkDeviceCreateInfo
deviceCreateInfo.pNext = &validationFeatures;
VkResult result = vkCreateDevice(m_physicalDevice, &deviceCreateInfo, nullptr, &m_device);
// Register the callback with the Vulkan instance
void onCreate()
{
VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCI{};
debugUtilsMessengerCI.sType =
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
debugUtilsMessengerCI.messageSeverity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debugUtilsMessengerCI.messageType =
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
debugUtilsMessengerCI.pfnUserCallback = debugUtilsMessengerCallback;
VkResult result = vkCreateDebugUtilsMessengerEXT(vkInstance,
&debugUtilsMessengerCI, nullptr, &debugUtilsMessenger);
assert(result == VK_SUCCESS);
}
// Destroy the messenger as part of cleanup
void onShutdown()
{
vkDestroyDebugUtilsMessengerEXT(vkInstance, debugUtilsMessenger, nullptr);
}
What is being validated?
Ray tracing validation reports on certain error conditions that can lead to hard-to-debug faults or corruptions with ray tracing and build acceleration structure calls. For DirectX 12, the validation occurs within DispatchRays
and BuildRaytracingAccelerationStructure
calls. For Vulkan, the validation occurs within vkCmdTraceRaysKHR
and vkCmdBuildAccelerationStructuresKHR
calls. The validation for all these commands run at execution time on the GPU.
For example, an application might build a new ray tracing pipeline for the next frame and update the shader binding table (SBT) that is still in use for the current frame. This could lead to a potential fault on missing entries in the SBT in the current frame. Ray tracing validation reports an UNKNOWN_ENTRY_FUNCTION
error along with error-specific details, guiding you to the root cause of the fault.
Validation checks for DispatchRays
and and vkCmdTraceRaysKHR
include the following:
- Unexpected SBT record shader type, like encountering a miss shader entry when expecting a hit group
- SBT references a shader that is not part of the pipeline
- Out-of-bounds SBT entry
- Shader payload type mismatch, when the invoked shader expects a type different than that passed to
TraceRay
- Maximum trace depth exceeded
- Stack overflow
Validation checks for BuildRaytracingAccelerationStructure
and and vkCmdBuildAccelerationStructuresKHR
include the following:
- Performance warnings for inefficient acceleration structures.
- Excessive degenerated triangle use in re-fittable, bottom-level ASs. This may lead to poor performance.
- Bad vertex data, such as NaNs and large numbers.
- Ill-conditioned geometry or instance transforms.
- Incomplete source acceleration structures used for refitting, copying or TLAS builds. Likely an app-side issue with proper syncing between AS operations.
- Vertex, OMM, or DMM input index out-of-bounds check.
- Altered flags between AS build and refit.
Example output
At the time of publication, here are a few examples of the driver’s validation output. The errors that are caught by the driver will evolve and grow over time.
error: [UNKNOWN_ENTRY_FUNCTION] attempted to execute a shader that is not part of the pipeline
launch index: [451, 309, 0]
additional occurrences: 12
SBT byte offset (if applicable): 1088
SBT range: likely hitgroup/raygen/callable
error: [UNEXPECTED_SHADER_TYPE] encountered a shader-binding-table record with unexpected shader type
launch index: [0, 0, 0]
additional occurrences: 10737
type: hitgroup
expected: miss
SBT byte offset: 64
SBT GPUVA: 0xb2841c0
error: [HIT_SBT_OUT_OF_BOUNDS] encountered an out-of-bounds access in the shader binding table when accessing a hitgroup record
launch index: [826, 122, 0]
additional occurrences: 1067
SBT byte offset: 7040
SBT byte size: 6528
SBT index: 110
instance SBT base index: 108
warning: [EXCESSIVE_DEGENERATE_PRIMITIVES] Acceleration structure has a significant portion of degenerated primitives, which can lead to poor performance.
Performance
Enabling ray tracing validation comes at a performance cost. However, this cost is typically quite low, especially when no errors are detected. The performance impact should be acceptable for day-to-day feature development and debugging. As already mentioned, applications should not ship with validation mode enabled.
In the most recent tests against an extensive collection of benchmarks, we observed a performance impact of roughly 3% frame time on average for cases that did not trigger any errors. With benchmarks that did trigger errors, performance overhead ranged from 3% to 40%.
Ray tracing validation has no effect on ray query–based ray tracing from compute or pixel shaders.
Conclusion
Ray tracing validation is a powerful tool for debugging troublesome crashes and improving ray tracing performance on both DirectX 12 and Vulkan. The changes required to enable it are small and simple.
If you are developing a ray tracing application, consider adding it to your development ecosystem today. For more information, see the ray tracing forum.