Content Creation / Rendering

Ray Tracing Validation for DirectX 12 and Vulkan

Decorative image of code block with green lightbeams shining on a figure walking on a computer chip between monitors.

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 Vulkan Beta driver from version 552.04 for Windows or version 550.40.59 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.

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 not succeed.

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 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);


assert(validationFeatures.rayTracingValidation == true);


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.

Discuss (3)

Tags