Vulkan NV Ray Tracing with HLSL

At GDC 2018, Nvidia and Microsoft announced DirectX ray tracing (DXR), along with some cool demos like our PICA PICA work at EA SEED.

Our ray tracing support was implemented in the DirectX 12 backend of our high-performance research engine Halcyon, without equivalent functionality in our other low level backends. Since that GDC, Nvidia has released their own vendor-specific Vulkan extension for ray tracing. Here are some relevant links:

This post is not a tutorial for using the VK_NV_ray_tracing extension, there are a number of good tutorials already available such as this one.

Instead, I wanted to show how to use HLSL as the only source language, and target both DXR and VK_NV_ray_tracing, made possible by a recent pull request by Nvidia to Microsoft’s DirectX shader compiler (DXC).

Early versions of this pull request required more options to generate the SPIR-V compared to regular DXR, but the merged version is very simple; just pass -spirv to DXC and use the standard DXR options like -T lib_6_3.

For starters, we need to have some HLSL we want to compile. To trivialize porting between GLSL and HLSL, we will start from simple GLSL shaders, which can be used to produce the following image:


GLSL Ray Gen:

#version 460
#extension GL_NV_ray_tracing : require

layout(set = 0, binding = 0) uniform accelerationStructureNV topLevelAS;
layout(set = 0, binding = 1, rgba8) uniform image2D image;

layout(location = 0) rayPayloadNV vec3 hitValue;

void main()
{
    const vec2 pixelCenter = vec2(gl_LaunchIDNV.xy) + vec2(0.5);
    const vec2 inUV = pixelCenter/vec2(gl_LaunchSizeNV.xy);

    vec2 d = inUV * 2.0 - 1.0;
    float aspectRatio = float(gl_LaunchSizeNV.x) / float(gl_LaunchSizeNV.y);

    vec3 origin = vec3(0, 0, -2.0);
    vec3 direction = normalize(vec3(d.x * aspectRatio, -d.y, 1));
    uint rayFlags = gl_RayFlagsOpaqueNV;
    uint cullMask = 0xff;
    float tmin = 0.001;
    float tmax = 100.0;
    traceNV(topLevelAS, rayFlags, cullMask, 0 /*sbtRecordOffset*/, 0 /*sbtRecordStride*/, 0 /*missIndex*/, origin, tmin, direction, tmax, 0 /*payload*/);

    imageStore(image, ivec2(gl_LaunchIDNV.xy), vec4(hitValue, 0.0));
}

GLSL Closest Hit:

#version 460
#extension GL_NV_ray_tracing : require

layout(location = 0) rayPayloadInNV vec3 hitValue;
hitAttributeNV vec3 attribs;

void main()
{
    const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y);
    hitValue = barycentrics;
}

GLSL Miss:

#version 460
#extension GL_NV_ray_tracing : require

layout(location = 0) rayPayloadInNV vec3 hitValue;

void main()
{
    hitValue = vec3(0.0, 0.1, 0.3);
}

These shaders can be rewritten to HLSL (DXR) as follows:

HLSL Ray Gen:

struct Payload
{
    float3 hitValue;
};

struct Attribute
{
    float2 bary;
};

RaytracingAccelerationStructure g_topLevel : register(t0, space0);
RWTexture2D<float4> g_output : register(u1, space0);

[shader("raygeneration")]
void main()
{
    uint2 launchIndex = DispatchRaysIndex().xy;
    float2 dims = DispatchRaysDimensions().xy;

    float2 pixelCenter = launchIndex + 0.5;
    float2 uv = pixelCenter / dims.xy;

    float2 d = uv * 2.0 - 1.0;
    float aspectRatio = float(dims.x) / float(dims.y);

    RayDesc ray;
    ray.Origin = float3(0.0, 0.0, -2.0);
    ray.Direction = normalize(float3(d.x * aspectRatio, -d.y, 1.0));
    ray.TMin = 0.001;
    ray.TMax = 1000.0;

    Payload payload;
    payload.hitValue = float3(0.0, 0.0, 0.0);

    TraceRay(g_topLevel, RAY_FLAG_FORCE_OPAQUE, 0xff, 0, 0, 0, ray, payload);

    g_output[launchIndex] = float4(payload.hitValue, 1.0f);
}

HLSL Closest Hit:

struct Payload
{
    float3 hitValue;
};

struct Attribute
{
    float2 bary;
};

[shader("closesthit")]
void main(inout Payload payload : SV_RayPayload, in Attribute attribs : SV_IntersectionAttributes)
{
    const float3 barycentrics = float3(1.0 - attribs.bary.x - attribs.bary.y, attribs.bary.x, attribs.bary.y);
    payload.hitValue = barycentrics;
}

HLSL Miss:

struct Payload
{
    float3 hitValue;
};

[shader("miss")]
void main(inout Payload payload : SV_RayPayload)
{
    payload.hitValue = float3(0.0, 0.1, 0.3);
}

NOTE: The application’s Vulkan bindings did not require modification - only the shader source was changed.

SPIR-V Disassembly

The disassembled results show very comparable SPIR-V between the GLSL and HLSL versions.

GLSL Ray Gen SPIR-V:

% spirv-dis triangle.glsl_rgen.spv

; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 99
; Schema: 0
               OpCapability RayTracingNV
               OpExtension "SPV_NV_ray_tracing"
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint RayGenerationNV %main "main" %gl_LaunchIDNV %gl_LaunchSizeNV
               OpSource GLSL 460
               OpSourceExtension "GL_NV_ray_tracing"
               OpName %main "main"
               OpName %pixelCenter "pixelCenter"
               OpName %gl_LaunchIDNV "gl_LaunchIDNV"
               OpName %inUV "inUV"
               OpName %gl_LaunchSizeNV "gl_LaunchSizeNV"
               OpName %d "d"
               OpName %aspectRatio "aspectRatio"
               OpName %origin "origin"
               OpName %direction "direction"
               OpName %rayFlags "rayFlags"
               OpName %cullMask "cullMask"
               OpName %tmin "tmin"
               OpName %tmax "tmax"
               OpName %topLevelAS "topLevelAS"
               OpName %image "image"
               OpName %hitValue "hitValue"
               OpDecorate %gl_LaunchIDNV BuiltIn LaunchIdNV
               OpDecorate %gl_LaunchSizeNV BuiltIn LaunchSizeNV
               OpDecorate %topLevelAS DescriptorSet 0
               OpDecorate %topLevelAS Binding 0
               OpDecorate %image DescriptorSet 0
               OpDecorate %image Binding 1
               OpDecorate %hitValue Location 0
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v2float = OpTypeVector %float 2
%_ptr_Function_v2float = OpTypePointer Function %v2float
       %uint = OpTypeInt 32 0
     %v3uint = OpTypeVector %uint 3
%_ptr_Input_v3uint = OpTypePointer Input %v3uint
%gl_LaunchIDNV = OpVariable %_ptr_Input_v3uint Input
     %v2uint = OpTypeVector %uint 2
  %float_0_5 = OpConstant %float 0.5
         %19 = OpConstantComposite %v2float %float_0_5 %float_0_5
%gl_LaunchSizeNV = OpVariable %_ptr_Input_v3uint Input
    %float_2 = OpConstant %float 2
    %float_1 = OpConstant %float 1
%_ptr_Function_float = OpTypePointer Function %float
     %uint_0 = OpConstant %uint 0
%_ptr_Input_uint = OpTypePointer Input %uint
     %uint_1 = OpConstant %uint 1
    %v3float = OpTypeVector %float 3
%_ptr_Function_v3float = OpTypePointer Function %v3float
    %float_0 = OpConstant %float 0
   %float_n2 = OpConstant %float -2
         %52 = OpConstantComposite %v3float %float_0 %float_0 %float_n2
%_ptr_Function_uint = OpTypePointer Function %uint
   %uint_255 = OpConstant %uint 255
%float_0_00100000005 = OpConstant %float 0.00100000005
  %float_100 = OpConstant %float 100
         %71 = OpTypeAccelerationStructureNV
%_ptr_UniformConstant_71 = OpTypePointer UniformConstant %71
 %topLevelAS = OpVariable %_ptr_UniformConstant_71 UniformConstant
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
         %83 = OpTypeImage %float 2D 0 0 0 2 Rgba8
%_ptr_UniformConstant_83 = OpTypePointer UniformConstant %83
      %image = OpVariable %_ptr_UniformConstant_83 UniformConstant
      %v2int = OpTypeVector %int 2
%_ptr_RayPayloadNV_v3float = OpTypePointer RayPayloadNV %v3float
   %hitValue = OpVariable %_ptr_RayPayloadNV_v3float RayPayloadNV
    %v4float = OpTypeVector %float 4
       %main = OpFunction %void None %3
          %5 = OpLabel
%pixelCenter = OpVariable %_ptr_Function_v2float Function
       %inUV = OpVariable %_ptr_Function_v2float Function
          %d = OpVariable %_ptr_Function_v2float Function
%aspectRatio = OpVariable %_ptr_Function_float Function
     %origin = OpVariable %_ptr_Function_v3float Function
  %direction = OpVariable %_ptr_Function_v3float Function
   %rayFlags = OpVariable %_ptr_Function_uint Function
   %cullMask = OpVariable %_ptr_Function_uint Function
       %tmin = OpVariable %_ptr_Function_float Function
       %tmax = OpVariable %_ptr_Function_float Function
         %15 = OpLoad %v3uint %gl_LaunchIDNV
         %16 = OpVectorShuffle %v2uint %15 %15 0 1
         %17 = OpConvertUToF %v2float %16
         %20 = OpFAdd %v2float %17 %19
               OpStore %pixelCenter %20
         %22 = OpLoad %v2float %pixelCenter
         %24 = OpLoad %v3uint %gl_LaunchSizeNV
         %25 = OpVectorShuffle %v2uint %24 %24 0 1
         %26 = OpConvertUToF %v2float %25
         %27 = OpFDiv %v2float %22 %26
               OpStore %inUV %27
         %29 = OpLoad %v2float %inUV
         %31 = OpVectorTimesScalar %v2float %29 %float_2
         %33 = OpCompositeConstruct %v2float %float_1 %float_1
         %34 = OpFSub %v2float %31 %33
               OpStore %d %34
         %39 = OpAccessChain %_ptr_Input_uint %gl_LaunchSizeNV %uint_0
         %40 = OpLoad %uint %39
         %41 = OpConvertUToF %float %40
         %43 = OpAccessChain %_ptr_Input_uint %gl_LaunchSizeNV %uint_1
         %44 = OpLoad %uint %43
         %45 = OpConvertUToF %float %44
         %46 = OpFDiv %float %41 %45
               OpStore %aspectRatio %46
               OpStore %origin %52
         %54 = OpAccessChain %_ptr_Function_float %d %uint_0
         %55 = OpLoad %float %54
         %56 = OpLoad %float %aspectRatio
         %57 = OpFMul %float %55 %56
         %58 = OpAccessChain %_ptr_Function_float %d %uint_1
         %59 = OpLoad %float %58
         %60 = OpFNegate %float %59
         %61 = OpCompositeConstruct %v3float %57 %60 %float_1
         %62 = OpExtInst %v3float %1 Normalize %61
               OpStore %direction %62
               OpStore %rayFlags %uint_1
               OpStore %cullMask %uint_255
               OpStore %tmin %float_0_00100000005
               OpStore %tmax %float_100
         %74 = OpLoad %71 %topLevelAS
         %75 = OpLoad %uint %rayFlags
         %76 = OpLoad %uint %cullMask
         %77 = OpLoad %v3float %origin
         %78 = OpLoad %float %tmin
         %79 = OpLoad %v3float %direction
         %80 = OpLoad %float %tmax
               OpTraceNV %74 %75 %76 %uint_0 %uint_0 %uint_0 %77 %78 %79 %80 %int_0
         %86 = OpLoad %83 %image
         %87 = OpLoad %v3uint %gl_LaunchIDNV
         %88 = OpVectorShuffle %v2uint %87 %87 0 1
         %90 = OpBitcast %v2int %88
         %93 = OpLoad %v3float %hitValue
         %95 = OpCompositeExtract %float %93 0
         %96 = OpCompositeExtract %float %93 1
         %97 = OpCompositeExtract %float %93 2
         %98 = OpCompositeConstruct %v4float %95 %96 %97 %float_0
               OpImageWrite %86 %90 %98
               OpReturn
               OpFunctionEnd

HLSL Ray Gen SPIR-V:

% spirv-dis triangle.hlsl_rgen.spv

; SPIR-V
; Version: 1.0
; Generator: Google spiregg; 0
; Bound: 67
; Schema: 0
               OpCapability RayTracingNV
               OpExtension "SPV_NV_ray_tracing"
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint RayGenerationNV %main "main" %3 %4
               OpSource HLSL 630
               OpName %accelerationStructureNV "accelerationStructureNV"
               OpName %g_topLevel "g_topLevel"
               OpName %type_2d_image "type.2d.image"
               OpName %g_output "g_output"
               OpName %Payload "Payload"
               OpMemberName %Payload 0 "hitValue"
               OpName %payload "payload"
               OpName %main "main"
               OpDecorate %3 BuiltIn LaunchIdNV
               OpDecorate %4 BuiltIn LaunchSizeNV
               OpDecorate %payload Location 0
               OpDecorate %g_topLevel DescriptorSet 0
               OpDecorate %g_topLevel Binding 0
               OpDecorate %g_output DescriptorSet 0
               OpDecorate %g_output Binding 1
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
   %uint_255 = OpConstant %uint 255
      %float = OpTypeFloat 32
  %float_0_5 = OpConstant %float 0.5
    %v2float = OpTypeVector %float 2
         %18 = OpConstantComposite %v2float %float_0_5 %float_0_5
    %float_2 = OpConstant %float 2
    %float_1 = OpConstant %float 1
         %21 = OpConstantComposite %v2float %float_1 %float_1
    %float_0 = OpConstant %float 0
   %float_n2 = OpConstant %float -2
    %v3float = OpTypeVector %float 3
         %25 = OpConstantComposite %v3float %float_0 %float_0 %float_n2
%float_0_00100000005 = OpConstant %float 0.00100000005
 %float_1000 = OpConstant %float 1000
         %28 = OpConstantComposite %v3float %float_0 %float_0 %float_0
%accelerationStructureNV = OpTypeAccelerationStructureNV
%_ptr_UniformConstant_accelerationStructureNV = OpTypePointer UniformConstant %accelerationStructureNV
%type_2d_image = OpTypeImage %float 2D 2 0 0 2 Rgba32f
%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
     %v3uint = OpTypeVector %uint 3
%_ptr_Input_v3uint = OpTypePointer Input %v3uint
    %Payload = OpTypeStruct %v3float
%_ptr_RayPayloadNV_Payload = OpTypePointer RayPayloadNV %Payload
       %void = OpTypeVoid
         %35 = OpTypeFunction %void
     %v2uint = OpTypeVector %uint 2
    %v4float = OpTypeVector %float 4
 %g_topLevel = OpVariable %_ptr_UniformConstant_accelerationStructureNV UniformConstant
   %g_output = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
          %3 = OpVariable %_ptr_Input_v3uint Input
          %4 = OpVariable %_ptr_Input_v3uint Input
    %payload = OpVariable %_ptr_RayPayloadNV_Payload RayPayloadNV
         %38 = OpConstantComposite %Payload %28
       %main = OpFunction %void None %35
         %39 = OpLabel
         %40 = OpLoad %v3uint %3
         %41 = OpVectorShuffle %v2uint %40 %40 0 1
         %42 = OpLoad %v3uint %4
         %43 = OpVectorShuffle %v2uint %42 %42 0 1
         %44 = OpConvertUToF %v2float %43
         %45 = OpConvertUToF %v2float %41
         %46 = OpFAdd %v2float %45 %18
         %47 = OpFDiv %v2float %46 %44
         %48 = OpVectorTimesScalar %v2float %47 %float_2
         %49 = OpFSub %v2float %48 %21
         %50 = OpCompositeExtract %float %44 0
         %51 = OpCompositeExtract %float %44 1
         %52 = OpFDiv %float %50 %51
         %53 = OpCompositeExtract %float %49 0
         %54 = OpFMul %float %53 %52
         %55 = OpCompositeExtract %float %49 1
         %56 = OpFNegate %float %55
         %57 = OpCompositeConstruct %v3float %54 %56 %float_1
         %58 = OpExtInst %v3float %1 Normalize %57
               OpStore %payload %38
         %59 = OpLoad %accelerationStructureNV %g_topLevel
               OpTraceNV %59 %uint_1 %uint_255 %uint_0 %uint_0 %uint_0 %25 %float_0_00100000005 %58 %float_1000 %uint_0
         %60 = OpLoad %Payload %payload
         %61 = OpCompositeExtract %v3float %60 0
         %62 = OpCompositeExtract %float %61 0
         %63 = OpCompositeExtract %float %61 1
         %64 = OpCompositeExtract %float %61 2
         %65 = OpCompositeConstruct %v4float %62 %63 %64 %float_1
         %66 = OpLoad %type_2d_image %g_output
               OpImageWrite %66 %41 %65 None
               OpReturn
               OpFunctionEnd

GLSL Closest Hit SPIR-V:

% spirv-dis triangle.glsl_rchit.spv

; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 31
; Schema: 0
               OpCapability RayTracingNV
               OpExtension "SPV_NV_ray_tracing"
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint ClosestHitNV %main "main"
               OpSource GLSL 460
               OpSourceExtension "GL_NV_ray_tracing"
               OpName %main "main"
               OpName %barycentrics "barycentrics"
               OpName %attribs "attribs"
               OpName %hitValue "hitValue"
               OpDecorate %hitValue Location 0
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v3float = OpTypeVector %float 3
%_ptr_Function_v3float = OpTypePointer Function %v3float
    %float_1 = OpConstant %float 1
%_ptr_HitAttributeNV_v3float = OpTypePointer HitAttributeNV %v3float
    %attribs = OpVariable %_ptr_HitAttributeNV_v3float HitAttributeNV
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
%_ptr_HitAttributeNV_float = OpTypePointer HitAttributeNV %float
     %uint_1 = OpConstant %uint 1
%_ptr_IncomingRayPayloadNV_v3float = OpTypePointer IncomingRayPayloadNV %v3float
   %hitValue = OpVariable %_ptr_IncomingRayPayloadNV_v3float IncomingRayPayloadNV
       %main = OpFunction %void None %3
          %5 = OpLabel
%barycentrics = OpVariable %_ptr_Function_v3float Function
         %16 = OpAccessChain %_ptr_HitAttributeNV_float %attribs %uint_0
         %17 = OpLoad %float %16
         %18 = OpFSub %float %float_1 %17
         %20 = OpAccessChain %_ptr_HitAttributeNV_float %attribs %uint_1
         %21 = OpLoad %float %20
         %22 = OpFSub %float %18 %21
         %23 = OpAccessChain %_ptr_HitAttributeNV_float %attribs %uint_0
         %24 = OpLoad %float %23
         %25 = OpAccessChain %_ptr_HitAttributeNV_float %attribs %uint_1
         %26 = OpLoad %float %25
         %27 = OpCompositeConstruct %v3float %22 %24 %26
               OpStore %barycentrics %27
         %30 = OpLoad %v3float %barycentrics
               OpStore %hitValue %30
               OpReturn
               OpFunctionEnd

HLSL Closest Hit SPIR-V:

% spirv-dis triangle.hlsl_rchit.spv

; SPIR-V
; Version: 1.0
; Generator: Google spiregg; 0
; Bound: 23
; Schema: 0
               OpCapability RayTracingNV
               OpExtension "SPV_NV_ray_tracing"
               OpMemoryModel Logical GLSL450
               OpEntryPoint ClosestHitNV %main "main"
               OpSource HLSL 630
               OpName %Payload "Payload"
               OpMemberName %Payload 0 "hitValue"
               OpName %payload "payload"
               OpName %Attribute "Attribute"
               OpMemberName %Attribute 0 "bary"
               OpName %attribs "attribs"
               OpName %main "main"
      %float = OpTypeFloat 32
    %float_1 = OpConstant %float 1
    %v3float = OpTypeVector %float 3
    %Payload = OpTypeStruct %v3float
%_ptr_IncomingRayPayloadNV_Payload = OpTypePointer IncomingRayPayloadNV %Payload
    %v2float = OpTypeVector %float 2
  %Attribute = OpTypeStruct %v2float
%_ptr_HitAttributeNV_Attribute = OpTypePointer HitAttributeNV %Attribute
       %void = OpTypeVoid
         %13 = OpTypeFunction %void
    %payload = OpVariable %_ptr_IncomingRayPayloadNV_Payload IncomingRayPayloadNV
    %attribs = OpVariable %_ptr_HitAttributeNV_Attribute HitAttributeNV
       %main = OpFunction %void None %13
         %14 = OpLabel
         %15 = OpLoad %Attribute %attribs
         %16 = OpCompositeExtract %v2float %15 0
         %17 = OpCompositeExtract %float %16 0
         %18 = OpFSub %float %float_1 %17
         %19 = OpCompositeExtract %float %16 1
         %20 = OpFSub %float %18 %19
         %21 = OpCompositeConstruct %v3float %20 %17 %19
         %22 = OpCompositeConstruct %Payload %21
               OpStore %payload %22
               OpReturn
               OpFunctionEnd

GLSL Miss SPIR-V:

% spirv-dis triangle.glsl_rmiss.spv

; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 14
; Schema: 0
               OpCapability RayTracingNV
               OpExtension "SPV_NV_ray_tracing"
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint MissNV %main "main"
               OpSource GLSL 460
               OpSourceExtension "GL_NV_ray_tracing"
               OpName %main "main"
               OpName %hitValue "hitValue"
               OpDecorate %hitValue Location 0
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v3float = OpTypeVector %float 3
%_ptr_IncomingRayPayloadNV_v3float = OpTypePointer IncomingRayPayloadNV %v3float
   %hitValue = OpVariable %_ptr_IncomingRayPayloadNV_v3float IncomingRayPayloadNV
    %float_0 = OpConstant %float 0
%float_0_100000001 = OpConstant %float 0.100000001
%float_0_300000012 = OpConstant %float 0.300000012
         %13 = OpConstantComposite %v3float %float_0 %float_0_100000001 %float_0_300000012
       %main = OpFunction %void None %3
          %5 = OpLabel
               OpStore %hitValue %13
               OpReturn
               OpFunctionEnd

HLSL Miss SPIR-V:

% spirv-dis triangle.hlsl_rmiss.spv

; SPIR-V
; Version: 1.0
; Generator: Google spiregg; 0
; Bound: 15
; Schema: 0
               OpCapability RayTracingNV
               OpExtension "SPV_NV_ray_tracing"
               OpMemoryModel Logical GLSL450
               OpEntryPoint MissNV %main "main"
               OpSource HLSL 630
               OpName %Payload "Payload"
               OpMemberName %Payload 0 "hitValue"
               OpName %payload "payload"
               OpName %main "main"
      %float = OpTypeFloat 32
    %float_0 = OpConstant %float 0
%float_0_100000001 = OpConstant %float 0.100000001
%float_0_300000012 = OpConstant %float 0.300000012
    %v3float = OpTypeVector %float 3
          %9 = OpConstantComposite %v3float %float_0 %float_0_100000001 %float_0_300000012
    %Payload = OpTypeStruct %v3float
%_ptr_IncomingRayPayloadNV_Payload = OpTypePointer IncomingRayPayloadNV %Payload
       %void = OpTypeVoid
         %12 = OpTypeFunction %void
    %payload = OpVariable %_ptr_IncomingRayPayloadNV_Payload IncomingRayPayloadNV
         %13 = OpConstantComposite %Payload %9
       %main = OpFunction %void None %12
         %14 = OpLabel
               OpStore %payload %13
               OpReturn
               OpFunctionEnd

Shader Libraries

In some codebases, it can be useful to group ray tracing shaders into shader files with multiple entry points - also known as shader libraries. There were drivers issues when I first attempted to use multiple entry points, but these have been resolved now.

Shader Library:

struct Payload
{
    float3 hitValue;
};

struct Attribute
{
    float2 bary;
};

RaytracingAccelerationStructure g_topLevel : register(t0, space0);
RWTexture2D<float4> g_output : register(u1, space0);

[shader("raygeneration")]
void rgen_main()
{
    uint2 launchIndex = DispatchRaysIndex().xy;
    float2 dims = DispatchRaysDimensions().xy;

    float2 pixelCenter = launchIndex + 0.5;
    float2 uv = pixelCenter / dims.xy;

    float2 d = uv * 2.0 - 1.0;
    float aspectRatio = float(dims.x) / float(dims.y);

    RayDesc ray;
    ray.Origin = float3(0.0, 0.0, -2.0);
    ray.Direction = normalize(float3(d.x * aspectRatio, -d.y, 1.0));
    ray.TMin = 0.001;
    ray.TMax = 1000.0;

    Payload payload;
    payload.hitValue = float3(0.0, 0.0, 0.0);

    TraceRay(g_topLevel, RAY_FLAG_FORCE_OPAQUE, 0xff, 0, 0, 0, ray, payload);

    g_output[launchIndex] = float4(payload.hitValue, 1.0f);
}

[shader("miss")]
void rmiss_main(inout Payload payload : SV_RayPayload)
{
    payload.hitValue = float3(0.0, 0.1, 0.3);
}

[shader("closesthit")]
void rchit_main(inout Payload payload : SV_RayPayload, in Attribute attribs : SV_IntersectionAttributes)
{
    const float3 barycentrics = float3(1.0 - attribs.bary.x - attribs.bary.y, attribs.bary.x, attribs.bary.y);
    payload.hitValue = barycentrics;
}

Shader Library SPIR-V:

% spirv-dis triangle.hlsl_lib.spv

; SPIR-V
; Version: 1.0
; Generator: Google spiregg; 0
; Bound: 89
; Schema: 0
               OpCapability RayTracingNV
               OpExtension "SPV_NV_ray_tracing"
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint RayGenerationNV %rgen_main "rgen_main" %3 %4
               OpEntryPoint MissNV %rmiss_main "rmiss_main" %3 %4
               OpEntryPoint ClosestHitNV %rchit_main "rchit_main" %3 %4
               OpSource HLSL 630
               OpName %accelerationStructureNV "accelerationStructureNV"
               OpName %g_topLevel "g_topLevel"
               OpName %type_2d_image "type.2d.image"
               OpName %g_output "g_output"
               OpName %Payload "Payload"
               OpMemberName %Payload 0 "hitValue"
               OpName %payload "payload"
               OpName %payload_0 "payload"
               OpName %payload_1 "payload"
               OpName %Attribute "Attribute"
               OpMemberName %Attribute 0 "bary"
               OpName %attribs "attribs"
               OpName %rgen_main "rgen_main"
               OpName %rmiss_main "rmiss_main"
               OpName %rchit_main "rchit_main"
               OpDecorate %3 BuiltIn LaunchIdNV
               OpDecorate %4 BuiltIn LaunchSizeNV
               OpDecorate %payload Location 0
               OpDecorate %g_topLevel DescriptorSet 0
               OpDecorate %g_topLevel Binding 0
               OpDecorate %g_output DescriptorSet 0
               OpDecorate %g_output Binding 1
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
   %uint_255 = OpConstant %uint 255
      %float = OpTypeFloat 32
  %float_0_5 = OpConstant %float 0.5
    %v2float = OpTypeVector %float 2
         %24 = OpConstantComposite %v2float %float_0_5 %float_0_5
    %float_2 = OpConstant %float 2
    %float_1 = OpConstant %float 1
         %27 = OpConstantComposite %v2float %float_1 %float_1
    %float_0 = OpConstant %float 0
   %float_n2 = OpConstant %float -2
    %v3float = OpTypeVector %float 3
         %31 = OpConstantComposite %v3float %float_0 %float_0 %float_n2
%float_0_00100000005 = OpConstant %float 0.00100000005
 %float_1000 = OpConstant %float 1000
         %34 = OpConstantComposite %v3float %float_0 %float_0 %float_0
%float_0_100000001 = OpConstant %float 0.100000001
%float_0_300000012 = OpConstant %float 0.300000012
         %37 = OpConstantComposite %v3float %float_0 %float_0_100000001 %float_0_300000012
%accelerationStructureNV = OpTypeAccelerationStructureNV
%_ptr_UniformConstant_accelerationStructureNV = OpTypePointer UniformConstant %accelerationStructureNV
%type_2d_image = OpTypeImage %float 2D 2 0 0 2 Rgba32f
%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
     %v3uint = OpTypeVector %uint 3
%_ptr_Input_v3uint = OpTypePointer Input %v3uint
    %Payload = OpTypeStruct %v3float
%_ptr_RayPayloadNV_Payload = OpTypePointer RayPayloadNV %Payload
%_ptr_IncomingRayPayloadNV_Payload = OpTypePointer IncomingRayPayloadNV %Payload
  %Attribute = OpTypeStruct %v2float
%_ptr_HitAttributeNV_Attribute = OpTypePointer HitAttributeNV %Attribute
       %void = OpTypeVoid
         %46 = OpTypeFunction %void
     %v2uint = OpTypeVector %uint 2
    %v4float = OpTypeVector %float 4
 %g_topLevel = OpVariable %_ptr_UniformConstant_accelerationStructureNV UniformConstant
   %g_output = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
          %3 = OpVariable %_ptr_Input_v3uint Input
          %4 = OpVariable %_ptr_Input_v3uint Input
    %payload = OpVariable %_ptr_RayPayloadNV_Payload RayPayloadNV
  %payload_0 = OpVariable %_ptr_IncomingRayPayloadNV_Payload IncomingRayPayloadNV
  %payload_1 = OpVariable %_ptr_IncomingRayPayloadNV_Payload IncomingRayPayloadNV
    %attribs = OpVariable %_ptr_HitAttributeNV_Attribute HitAttributeNV
         %49 = OpConstantComposite %Payload %34
         %50 = OpConstantComposite %Payload %37
  %rgen_main = OpFunction %void None %46
         %51 = OpLabel
         %52 = OpLoad %v3uint %3
         %53 = OpVectorShuffle %v2uint %52 %52 0 1
         %54 = OpLoad %v3uint %4
         %55 = OpVectorShuffle %v2uint %54 %54 0 1
         %56 = OpConvertUToF %v2float %55
         %57 = OpConvertUToF %v2float %53
         %58 = OpFAdd %v2float %57 %24
         %59 = OpFDiv %v2float %58 %56
         %60 = OpVectorTimesScalar %v2float %59 %float_2
         %61 = OpFSub %v2float %60 %27
         %62 = OpCompositeExtract %float %56 0
         %63 = OpCompositeExtract %float %56 1
         %64 = OpFDiv %float %62 %63
         %65 = OpCompositeExtract %float %61 0
         %66 = OpFMul %float %65 %64
         %67 = OpCompositeExtract %float %61 1
         %68 = OpFNegate %float %67
         %69 = OpCompositeConstruct %v3float %66 %68 %float_1
         %70 = OpExtInst %v3float %1 Normalize %69
               OpStore %payload %49
         %71 = OpLoad %accelerationStructureNV %g_topLevel
               OpTraceNV %71 %uint_1 %uint_255 %uint_0 %uint_0 %uint_0 %31 %float_0_00100000005 %70 %float_1000 %uint_0
         %72 = OpLoad %Payload %payload
         %73 = OpCompositeExtract %v3float %72 0
         %74 = OpCompositeExtract %float %73 0
         %75 = OpCompositeExtract %float %73 1
         %76 = OpCompositeExtract %float %73 2
         %77 = OpCompositeConstruct %v4float %74 %75 %76 %float_1
         %78 = OpLoad %type_2d_image %g_output
               OpImageWrite %78 %53 %77 None
               OpReturn
               OpFunctionEnd
 %rmiss_main = OpFunction %void None %46
         %79 = OpLabel
               OpStore %payload_0 %50
               OpReturn
               OpFunctionEnd
 %rchit_main = OpFunction %void None %46
         %80 = OpLabel
         %81 = OpLoad %Attribute %attribs
         %82 = OpCompositeExtract %v2float %81 0
         %83 = OpCompositeExtract %float %82 0
         %84 = OpFSub %float %float_1 %83
         %85 = OpCompositeExtract %float %82 1
         %86 = OpFSub %float %84 %85
         %87 = OpCompositeConstruct %v3float %86 %83 %85
         %88 = OpCompositeConstruct %Payload %87
               OpStore %payload_1 %88
               OpReturn
               OpFunctionEnd

Bindless Resources

Bindless resources, also known as descriptor indexing in Vulkan parlance, essentially offer unbounded resource arrays. This feature is extremely useful in a variety of situations, and is complementary to ray tracing APIs. Like the previous examples, it is possible to use HLSL shaders with bindless resources that are backed by VK_EXT_descriptor_indexing on the Vulkan side of things.

Shaders must access these resources using dynamic non-uniform indexing; GLSL has GL_EXT_nonuniform_qualifier, and HLSL has NonUniformResourceIndex.

GLSL Bindless Closest Hit:

#version 460
#extension GL_NV_ray_tracing : require
#extension GL_EXT_nonuniform_qualifier : require

layout(set = 0, binding = 2) uniform UniformBuffer
{
    vec3 color;
} uniformBuffers[];

layout(location = 0) rayPayloadInNV vec3 hitValue;
hitAttributeNV vec3 attribs;

void main()
{
    const vec3 color = uniformBuffers[nonuniformEXT(gl_InstanceCustomIndexNV)].color.xyz;
    hitValue = color;
}

HLSL Bindless Closest Hit:

struct Payload
{
    float3 hitValue;
};

struct Attribute
{
    float2 bary;
};

struct UniformBuffer
{
    float3 color;
};

ConstantBuffer<UniformBuffer> g_uniformBuffers[] : register(b2, space0);

[shader("closesthit")]
void main(inout Payload payload : SV_RayPayload, in Attribute attribs : SV_IntersectionAttributes)
{
    const float3 color = g_uniformBuffers[NonUniformResourceIndex(InstanceID())].color.xyz;
    payload.hitValue = color;
}

GLSL Bindless Closest Hit SPIR-V:

% spirv-dis triangle.glsl_bindless_rchit.spv

; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 27
; Schema: 0
               OpCapability ShaderNonUniformEXT
               OpCapability RuntimeDescriptorArrayEXT
               OpCapability UniformBufferArrayNonUniformIndexingEXT
               OpCapability RayTracingNV
               OpExtension "SPV_EXT_descriptor_indexing"
               OpExtension "SPV_NV_ray_tracing"
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint ClosestHitNV %main "main" %gl_InstanceCustomIndexNV
               OpSource GLSL 460
               OpSourceExtension "GL_EXT_nonuniform_qualifier"
               OpSourceExtension "GL_NV_ray_tracing"
               OpName %main "main"
               OpName %color "color"
               OpName %UniformBuffer "UniformBuffer"
               OpMemberName %UniformBuffer 0 "color"
               OpName %uniformBuffers "uniformBuffers"
               OpName %gl_InstanceCustomIndexNV "gl_InstanceCustomIndexNV"
               OpName %hitValue "hitValue"
               OpName %attribs "attribs"
               OpMemberDecorate %UniformBuffer 0 Offset 0
               OpDecorate %UniformBuffer Block
               OpDecorate %uniformBuffers DescriptorSet 0
               OpDecorate %uniformBuffers Binding 2
               OpDecorate %gl_InstanceCustomIndexNV BuiltIn InstanceCustomIndexNV
               OpDecorate %gl_InstanceCustomIndexNV NonUniformEXT
               OpDecorate %17 NonUniformEXT
               OpDecorate %21 NonUniformEXT
               OpDecorate %hitValue Location 0
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v3float = OpTypeVector %float 3
%_ptr_Function_v3float = OpTypePointer Function %v3float
%UniformBuffer = OpTypeStruct %v3float
%_runtimearr_UniformBuffer = OpTypeRuntimeArray %UniformBuffer
%_ptr_Uniform__runtimearr_UniformBuffer = OpTypePointer Uniform %_runtimearr_UniformBuffer
%uniformBuffers = OpVariable %_ptr_Uniform__runtimearr_UniformBuffer Uniform
        %int = OpTypeInt 32 1
%_ptr_Input_int = OpTypePointer Input %int
%gl_InstanceCustomIndexNV = OpVariable %_ptr_Input_int Input
      %int_0 = OpConstant %int 0
%_ptr_Uniform_v3float = OpTypePointer Uniform %v3float
%_ptr_IncomingRayPayloadNV_v3float = OpTypePointer IncomingRayPayloadNV %v3float
   %hitValue = OpVariable %_ptr_IncomingRayPayloadNV_v3float IncomingRayPayloadNV
%_ptr_HitAttributeNV_v3float = OpTypePointer HitAttributeNV %v3float
    %attribs = OpVariable %_ptr_HitAttributeNV_v3float HitAttributeNV
       %main = OpFunction %void None %3
          %5 = OpLabel
      %color = OpVariable %_ptr_Function_v3float Function
         %17 = OpLoad %int %gl_InstanceCustomIndexNV
         %20 = OpAccessChain %_ptr_Uniform_v3float %uniformBuffers %17 %int_0
         %21 = OpLoad %v3float %20
               OpStore %color %21
         %24 = OpLoad %v3float %color
               OpStore %hitValue %24
               OpReturn
               OpFunctionEnd

HLSL Bindless Closest Hit SPIR-V:

% spirv-dis triangle.hlsl_bindless_rchit.spv

; SPIR-V
; Version: 1.0
; Generator: Google spiregg; 0
; Bound: 24
; Schema: 0
               OpCapability RayTracingNV
               OpCapability RuntimeDescriptorArrayEXT
               OpCapability ShaderNonUniformEXT
               OpExtension "SPV_NV_ray_tracing"
               OpExtension "SPV_EXT_descriptor_indexing"
               OpMemoryModel Logical GLSL450
               OpEntryPoint ClosestHitNV %main "main" %2
               OpSource HLSL 630
               OpName %type_ConstantBuffer_UniformBuffer "type.ConstantBuffer.UniformBuffer"
               OpMemberName %type_ConstantBuffer_UniformBuffer 0 "color"
               OpName %g_uniformBuffers "g_uniformBuffers"
               OpName %Payload "Payload"
               OpMemberName %Payload 0 "hitValue"
               OpName %payload "payload"
               OpName %main "main"
               OpDecorate %2 BuiltIn InstanceCustomIndexNV
               OpDecorate %g_uniformBuffers DescriptorSet 0
               OpDecorate %g_uniformBuffers Binding 2
               OpMemberDecorate %type_ConstantBuffer_UniformBuffer 0 Offset 0
               OpDecorate %type_ConstantBuffer_UniformBuffer Block
               OpDecorate %7 NonUniformEXT
               OpDecorate %8 NonUniformEXT
               OpDecorate %9 NonUniformEXT
       %uint = OpTypeInt 32 0
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
      %float = OpTypeFloat 32
    %v3float = OpTypeVector %float 3
%type_ConstantBuffer_UniformBuffer = OpTypeStruct %v3float
%_runtimearr_type_ConstantBuffer_UniformBuffer = OpTypeRuntimeArray %type_ConstantBuffer_UniformBuffer
%_ptr_Uniform__runtimearr_type_ConstantBuffer_UniformBuffer = OpTypePointer Uniform %_runtimearr_type_ConstantBuffer_UniformBuffer
    %Payload = OpTypeStruct %v3float
%_ptr_IncomingRayPayloadNV_Payload = OpTypePointer IncomingRayPayloadNV %Payload
%_ptr_Input_uint = OpTypePointer Input %uint
       %void = OpTypeVoid
         %20 = OpTypeFunction %void
%_ptr_Uniform_v3float = OpTypePointer Uniform %v3float
%g_uniformBuffers = OpVariable %_ptr_Uniform__runtimearr_type_ConstantBuffer_UniformBuffer Uniform
    %payload = OpVariable %_ptr_IncomingRayPayloadNV_Payload IncomingRayPayloadNV
          %2 = OpVariable %_ptr_Input_uint Input
       %main = OpFunction %void None %20
         %22 = OpLabel
          %7 = OpLoad %uint %2
          %8 = OpAccessChain %_ptr_Uniform_v3float %g_uniformBuffers %7 %int_0
          %9 = OpLoad %v3float %8
         %23 = OpCompositeConstruct %Payload %9
               OpStore %payload %23
               OpReturn
               OpFunctionEnd

Additional information:

Rust Example

I recently implemented support for VK_NV_ray_tracing in Ash, Vulkan bindings in Rust. In order to test the implementation, I wrote a full example here. This example supports GLSL, HLSL, single shader library, and bindless ray tracing shaders.


As expected, the Rust ray tracing example also works with Nvidia Nsight, which was very useful in tracking down various issues along the way.



Docker Image

A final thing worth mentioning, is that my gwihlidal/docker-shader image has been updated to include the SPIR-V ray tracing support.

Here is the compile.sh script from the Rust example which I used to very easily compile and disassemble all the shaders:

echo "** Compiling triangle.bindless.rchit.glsl"
docker run --entrypoint "/app/vulkan/glslangValidator" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -V -o compiled/triangle.glsl_bindless_rchit.spv triangle.bindless.rchit.glsl

echo "** Compiling triangle.bindless.rchit.hlsl"
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 triangle.bindless.rchit.hlsl -Fo compiled/triangle.hlsl_bindless_rchit.dxil
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 -spirv triangle.bindless.rchit.hlsl -Fo compiled/triangle.hlsl_bindless_rchit.spv

echo "** Compiling triangle.lib.hlsl"
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 triangle.lib.hlsl -Fo compiled/triangle.hlsl_lib.dxil
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 -spirv triangle.lib.hlsl -Fo compiled/triangle.hlsl_lib.spv

echo "** Compiling triangle.rchit.glsl"
docker run --entrypoint "/app/vulkan/glslangValidator" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -V -o compiled/triangle.glsl_rchit.spv triangle.rchit.glsl

echo "** Compiling triangle.rchit.hlsl"
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 triangle.rchit.hlsl -Fo compiled/triangle.hlsl_rchit.dxil
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 -spirv triangle.rchit.hlsl -Fo compiled/triangle.hlsl_rchit.spv

echo "** Compiling triangle.rgen.glsl"
docker run --entrypoint "/app/vulkan/glslangValidator" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -V -o compiled/triangle.glsl_rgen.spv triangle.rgen.glsl

echo "** Compiling triangle.rgen.hlsl"
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 triangle.rgen.hlsl -Fo compiled/triangle.hlsl_rgen.dxil
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 -spirv triangle.rgen.hlsl -Fo compiled/triangle.hlsl_rgen.spv

echo "** Compiling triangle.rmiss.glsl"
docker run --entrypoint "/app/vulkan/glslangValidator" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -V -o compiled/triangle.glsl_rmiss.spv triangle.rmiss.glsl

echo "** Compiling triangle.rmiss.hlsl"
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 triangle.rmiss.hlsl -Fo compiled/triangle.hlsl_rmiss.dxil
docker run --entrypoint "/app/dxc/bin/dxc" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -T lib_6_3 -spirv triangle.rmiss.hlsl -Fo compiled/triangle.hlsl_rmiss.spv

echo "** Disassembling SPIR-V"
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.glsl_bindless_rchit.txt compiled/triangle.glsl_bindless_rchit.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.glsl_rchit.txt compiled/triangle.glsl_rchit.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.glsl_rgen.txt compiled/triangle.glsl_rgen.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.glsl_rmiss.txt compiled/triangle.glsl_rmiss.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.hlsl_bindless_rchit.txt compiled/triangle.hlsl_bindless_rchit.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.hlsl_lib.txt compiled/triangle.hlsl_lib.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.hlsl_rchit.txt compiled/triangle.hlsl_rchit.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.hlsl_rgen.txt compiled/triangle.hlsl_rgen.spv
docker run --entrypoint "/app/vulkan/spirv-dis" --rm -v $(pwd):$(pwd) -w $(pwd) gwihlidal/docker-shader -o compiled/triangle.hlsl_rmiss.txt compiled/triangle.hlsl_rmiss.spv

echo "** DONE **"

© 2024. All rights reserved.