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);
}
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:
- VK_EXT_descriptor_indexing Specification
- Descriptor Indexing Vulkan Dev Day 2018
- A Note on Descriptor Indexing
- Bindless Texturing for Deferred Rendering and Decals
- Resource Binding in HLSL
- GL_EXT_nonuniform_qualifier
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 **"