From 6cb1f2fa04f218cfa9e27627da60e52e8a48a0d2 Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:02:54 -0700 Subject: [PATCH 01/13] Use uint64 AS descriptor heap stride --- docs/user-guide/03-convenience-features.md | 7 ++-- source/slang/slang-emit-spirv.cpp | 35 +++++++++++++++---- ...r-heap-acceleration-structure-raygen.slang | 6 ++-- ...scriptor-heap-acceleration-structure.slang | 8 ++--- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/docs/user-guide/03-convenience-features.md b/docs/user-guide/03-convenience-features.md index 96547544377..5cc2f7cc60c 100644 --- a/docs/user-guide/03-convenience-features.md +++ b/docs/user-guide/03-convenience-features.md @@ -702,9 +702,10 @@ stride with the `-spirv-resource-heap-stride` or `-spirv-sampler-heap-stride` co > **Note on `RaytracingAccelerationStructure`:** When the `spvDescriptorHeapEXT` capability is active and > a `DescriptorHandle` is dereferenced, Slang loads a 64-bit device address > from the descriptor heap and converts it to an acceleration structure handle via -> `OpConvertUToAccelerationStructureKHR`. This matches how GPU drivers expose acceleration structure -> descriptors in the heap (as device addresses), and requires either the `SPV_KHR_ray_tracing` or -> `SPV_KHR_ray_query` extension. +> `OpConvertUToAccelerationStructureKHR`. The heap entry type is `uint64_t`, so the default heap stride is +> based on the address width rather than the opaque acceleration structure type. This matches how GPU +> drivers expose acceleration structure descriptors in the heap (as device addresses), and requires either +> the `SPV_KHR_ray_tracing` or `SPV_KHR_ray_query` extension. ### Custom Descriptor Fetch diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 4ae749770a1..0601826c372 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -7052,7 +7052,9 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex return type; } - SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType) + SpvInst* getDescriptorRuntimeArrayType( + SpvInst* descriptorElementType, + int defaultArrayStride = 0) { if (auto found = m_descriptorHeapRuntimeArrayTypes.tryGetValue(descriptorElementType)) return *found; @@ -7071,10 +7073,18 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex } if (userDefinedStride == 0) { - IRBuilder builder(m_irModule); - builder.setInsertInto(m_irModule->getModuleInst()); + if (defaultArrayStride != 0) + { + userDefinedStride = defaultArrayStride; + } + else + { + IRBuilder builder(m_irModule); + builder.setInsertInto(m_irModule->getModuleInst()); - stride = emitOpConstantSizeOfEXT(nullptr, builder.getUIntType(), descriptorElementType); + stride = + emitOpConstantSizeOfEXT(nullptr, builder.getUIntType(), descriptorElementType); + } } auto runtimeArrayType = emitOpTypeRuntimeArray(nullptr, descriptorElementType); @@ -7106,11 +7116,19 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex switch (valueType->getOp()) { case kIROp_TextureType: - case kIROp_RaytracingAccelerationStructureType: case kIROp_SamplerStateType: case kIROp_SamplerComparisonStateType: descriptorElementType = ensureInst(valueType); break; + case kIROp_RaytracingAccelerationStructureType: + { + // Acceleration structure heap entries are 64-bit device addresses that are + // converted to acceleration structure handles after loading. + IRBuilder builder(m_irModule); + builder.setInsertInto(m_irModule->getModuleInst()); + descriptorElementType = ensureInst(builder.getUInt64Type()); + break; + } default: isBufferResource = true; descriptorElementType = ensureDescriptorHeapBufferDescriptorType( @@ -7121,7 +7139,12 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex if (outIsBufferResource) *outIsBufferResource = isBufferResource; - return getDescriptorRuntimeArrayType(descriptorElementType); + const int defaultAccelerationStructureStride = 8; + return getDescriptorRuntimeArrayType( + descriptorElementType, + valueType->getOp() == kIROp_RaytracingAccelerationStructureType + ? defaultAccelerationStructureStride + : 0); } SpvInst* emitAccelerationStructureFromDescriptorHeap( diff --git a/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang b/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang index f342df75f4e..de50f8c2dc2 100644 --- a/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang +++ b/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang @@ -2,7 +2,7 @@ // Exercises the RayTracingKHR branch of requireSPIRVAnyCapability when // loading a RaytracingAccelerationStructure from a descriptor heap in a // raygeneration shader (which already requires SpvCapabilityRayTracingKHR). -//TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage raygeneration -entry rayGenMain -profile sm_6_6 -capability spvDescriptorHeapEXT -spirv-resource-heap-stride 32 +//TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage raygeneration -entry rayGenMain -profile sm_6_6 -capability spvDescriptorHeapEXT struct PushConstants { @@ -20,8 +20,8 @@ void rayGenMain(uniform PushConstants pushConstants) { // CHECK-DAG: %[[U64:[A-Za-z0-9_]+]] = OpTypeInt 64 0 // CHECK-DAG: %[[AS_TYPE:[A-Za-z0-9_]+]] = OpTypeAccelerationStructureKHR - // CHECK-DAG: %[[AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[AS_TYPE]] - // CHECK-DAG: OpDecorate %[[AS_HEAP_ARRAY]] ArrayStride 32 + // CHECK-DAG: %[[AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[U64]] + // CHECK-DAG: OpDecorate %[[AS_HEAP_ARRAY]] ArrayStride 8 // CHECK-DAG: OpCapability RayTracingKHR // CHECK: OpUntypedAccessChainKHR %_ptr_UniformConstant %[[AS_HEAP_ARRAY]] %slang_resourceHeap // CHECK-NEXT: %[[AS_ADDR:[A-Za-z0-9_]+]] = OpLoad %[[U64]] diff --git a/tests/spirv/descriptor-heap-acceleration-structure.slang b/tests/spirv/descriptor-heap-acceleration-structure.slang index 4a567f06542..ec14c3b1a69 100644 --- a/tests/spirv/descriptor-heap-acceleration-structure.slang +++ b/tests/spirv/descriptor-heap-acceleration-structure.slang @@ -1,5 +1,5 @@ -// Regression test for GitHub issue #10671. -//TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR -spirv-resource-heap-stride 32 +// Regression test for GitHub issues #10671 and #11231. +//TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR struct PushConstants { @@ -39,8 +39,8 @@ void computeMain(uint3 dispatchThreadID: SV_DispatchThreadID, uniform PushConsta { // CHECK-DAG: %[[U64:[A-Za-z0-9_]+]] = OpTypeInt 64 0 // CHECK-DAG: %[[AS_TYPE:[A-Za-z0-9_]+]] = OpTypeAccelerationStructureKHR - // CHECK-DAG: %[[AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[AS_TYPE]] - // CHECK-DAG: OpDecorate %[[AS_HEAP_ARRAY]] ArrayStride 32 + // CHECK-DAG: %[[AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[U64]] + // CHECK-DAG: OpDecorate %[[AS_HEAP_ARRAY]] ArrayStride 8 // CHECK-DAG: OpCapability RayQueryKHR // CHECK: OpUntypedAccessChainKHR %_ptr_UniformConstant %[[AS_HEAP_ARRAY]] %slang_resourceHeap // CHECK-NEXT: %[[AS_ADDR:[A-Za-z0-9_]+]] = OpLoad %[[U64]] From 2783f5598be1613c321af0a4ce1bf60fd771e9cf Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:32:28 -0700 Subject: [PATCH 02/13] Clarify AS descriptor heap stride docs --- docs/command-line-slangc-reference.md | 2 +- docs/user-guide/08-compiling.md | 2 +- source/slang/slang-options.cpp | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md index 51c8f002688..1b485b9dc1b 100644 --- a/docs/command-line-slangc-reference.md +++ b/docs/command-line-slangc-reference.md @@ -608,7 +608,7 @@ Specify the space index for the system defined global bindless resource array. **-spirv-resource-heap-stride <stride>** -Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType). +Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType), except RaytracingAccelerationStructure entries use an 8-byte uint64 address stride. diff --git a/docs/user-guide/08-compiling.md b/docs/user-guide/08-compiling.md index 955318193fd..e63ea0ed679 100644 --- a/docs/user-guide/08-compiling.md +++ b/docs/user-guide/08-compiling.md @@ -1089,7 +1089,7 @@ meanings of their `CompilerOptionValue` encodings. | GenerateWholeProgram | When set will emit target code for the entire program instead of for a specific entry point. `intValue0` specifies a bool value for the setting. | | UseUpToDateBinaryModule | When set will only load precompiled modules if it is up-to-date with its source. `intValue0` specifies a bool value for the setting. | | ValidateUniformity | When set will perform [uniformity analysis](a1-05-uniformity.md).| -| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`. | +| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`, except `RaytracingAccelerationStructure` entries use the 8-byte `uint64` address stride. | | SPIRVSamplerHeapStride | Specifies the byte stride for the sampler descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`. | | ForceDXLayout | When set forces the compiler to use DirectX-compatible (HLSL register packing) rules when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | | ForceCLayout | When set forces the compiler to use C struct layout rules (natural alignment, no HLSL/GLSL padding) when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index d2c403d142b..dcb2953ddfd 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -883,7 +883,8 @@ void initCommandOptions(CommandOptions& options) "-spirv-resource-heap-stride", "-spirv-resource-heap-stride ", "Specify the byte stride for the resource descriptor heap when generating SPIRV with " - "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType)."}, + "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType), " + "except RaytracingAccelerationStructure entries use an 8-byte uint64 address stride."}, {OptionKind::SPIRVSamplerHeapStride, "-spirv-sampler-heap-stride", "-spirv-sampler-heap-stride ", From 01b79f44333ff4e112252f784be9643abe169a68 Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:43:03 -0700 Subject: [PATCH 03/13] Clarify emitted heap stride wording --- docs/user-guide/08-compiling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/08-compiling.md b/docs/user-guide/08-compiling.md index e63ea0ed679..3bd95193959 100644 --- a/docs/user-guide/08-compiling.md +++ b/docs/user-guide/08-compiling.md @@ -1089,7 +1089,7 @@ meanings of their `CompilerOptionValue` encodings. | GenerateWholeProgram | When set will emit target code for the entire program instead of for a specific entry point. `intValue0` specifies a bool value for the setting. | | UseUpToDateBinaryModule | When set will only load precompiled modules if it is up-to-date with its source. `intValue0` specifies a bool value for the setting. | | ValidateUniformity | When set will perform [uniformity analysis](a1-05-uniformity.md).| -| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`, except `RaytracingAccelerationStructure` entries use the 8-byte `uint64` address stride. | +| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to emit `OpConstantSizeOfEXT(ResourceType)` as the default stride, except `RaytracingAccelerationStructure` entries use the 8-byte `uint64` address stride. | | SPIRVSamplerHeapStride | Specifies the byte stride for the sampler descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`. | | ForceDXLayout | When set forces the compiler to use DirectX-compatible (HLSL register packing) rules when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | | ForceCLayout | When set forces the compiler to use C struct layout rules (natural alignment, no HLSL/GLSL padding) when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | From 21ebd9bdf5ed8c9a834b37d7b1fabecc12c4955b Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:07:25 -0700 Subject: [PATCH 04/13] Assert AS descriptor heap element type --- source/slang/slang-emit-spirv.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 0601826c372..143c1ff5145 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -7056,6 +7056,10 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex SpvInst* descriptorElementType, int defaultArrayStride = 0) { + SLANG_ASSERT( + descriptorElementType->opcode != SpvOpTypeAccelerationStructureKHR && + "acceleration structure descriptor heaps must use uint64 elements"); + if (auto found = m_descriptorHeapRuntimeArrayTypes.tryGetValue(descriptorElementType)) return *found; From f91f0110d8c2430d4fdb1b670125a0508cdbc55d Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:44:34 -0700 Subject: [PATCH 05/13] Address CodeRabbit feedback --- docs/command-line-slangc-reference.md | 2 +- docs/user-guide/08-compiling.md | 2 +- source/slang/slang-options.cpp | 3 ++- tests/spirv/descriptor-heap-acceleration-structure.slang | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md index 1b485b9dc1b..d9fa70ccbb8 100644 --- a/docs/command-line-slangc-reference.md +++ b/docs/command-line-slangc-reference.md @@ -608,7 +608,7 @@ Specify the space index for the system defined global bindless resource array. **-spirv-resource-heap-stride <stride>** -Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType), except RaytracingAccelerationStructure entries use an 8-byte uint64 address stride. +Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType), except RaytracingAccelerationStructure entries emit a literal 8-byte ArrayStride for the uint64 device address elements. diff --git a/docs/user-guide/08-compiling.md b/docs/user-guide/08-compiling.md index 3bd95193959..d234cb32342 100644 --- a/docs/user-guide/08-compiling.md +++ b/docs/user-guide/08-compiling.md @@ -1089,7 +1089,7 @@ meanings of their `CompilerOptionValue` encodings. | GenerateWholeProgram | When set will emit target code for the entire program instead of for a specific entry point. `intValue0` specifies a bool value for the setting. | | UseUpToDateBinaryModule | When set will only load precompiled modules if it is up-to-date with its source. `intValue0` specifies a bool value for the setting. | | ValidateUniformity | When set will perform [uniformity analysis](a1-05-uniformity.md).| -| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to emit `OpConstantSizeOfEXT(ResourceType)` as the default stride, except `RaytracingAccelerationStructure` entries use the 8-byte `uint64` address stride. | +| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to emit `OpConstantSizeOfEXT(ResourceType)` as the default stride, except `RaytracingAccelerationStructure` entries emit a literal 8-byte `ArrayStride` for the `uint64` device address elements. | | SPIRVSamplerHeapStride | Specifies the byte stride for the sampler descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`. | | ForceDXLayout | When set forces the compiler to use DirectX-compatible (HLSL register packing) rules when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | | ForceCLayout | When set forces the compiler to use C struct layout rules (natural alignment, no HLSL/GLSL padding) when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index dcb2953ddfd..dd78b94b013 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -884,7 +884,8 @@ void initCommandOptions(CommandOptions& options) "-spirv-resource-heap-stride ", "Specify the byte stride for the resource descriptor heap when generating SPIRV with " "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType), " - "except RaytracingAccelerationStructure entries use an 8-byte uint64 address stride."}, + "except RaytracingAccelerationStructure entries emit a literal 8-byte ArrayStride for " + "the uint64 device address elements."}, {OptionKind::SPIRVSamplerHeapStride, "-spirv-sampler-heap-stride", "-spirv-sampler-heap-stride ", diff --git a/tests/spirv/descriptor-heap-acceleration-structure.slang b/tests/spirv/descriptor-heap-acceleration-structure.slang index ec14c3b1a69..2614fe7606a 100644 --- a/tests/spirv/descriptor-heap-acceleration-structure.slang +++ b/tests/spirv/descriptor-heap-acceleration-structure.slang @@ -1,5 +1,6 @@ // Regression test for GitHub issues #10671 and #11231. //TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR +//TEST:SIMPLE(filecheck=OVERRIDE): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR -spirv-resource-heap-stride 32 struct PushConstants { @@ -41,6 +42,9 @@ void computeMain(uint3 dispatchThreadID: SV_DispatchThreadID, uniform PushConsta // CHECK-DAG: %[[AS_TYPE:[A-Za-z0-9_]+]] = OpTypeAccelerationStructureKHR // CHECK-DAG: %[[AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[U64]] // CHECK-DAG: OpDecorate %[[AS_HEAP_ARRAY]] ArrayStride 8 + // OVERRIDE-DAG: %[[OVERRIDE_U64:[A-Za-z0-9_]+]] = OpTypeInt 64 0 + // OVERRIDE-DAG: %[[OVERRIDE_AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[OVERRIDE_U64]] + // OVERRIDE-DAG: OpDecorate %[[OVERRIDE_AS_HEAP_ARRAY]] ArrayStride 32 // CHECK-DAG: OpCapability RayQueryKHR // CHECK: OpUntypedAccessChainKHR %_ptr_UniformConstant %[[AS_HEAP_ARRAY]] %slang_resourceHeap // CHECK-NEXT: %[[AS_ADDR:[A-Za-z0-9_]+]] = OpLoad %[[U64]] From 1d535711113acfc047eeb959b76370ad0eadf8c7 Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Sat, 6 Jun 2026 08:40:37 -0700 Subject: [PATCH 06/13] Refine AS descriptor heap stride handling --- source/slang/slang-emit-spirv.cpp | 65 ++++++++++++++++++------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 143c1ff5145..d95cf0a90d8 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -6880,6 +6880,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex SpvInst* m_descriptorHeapUntypedPointerType = nullptr; Dictionary m_descriptorHeapBufferDescriptorTypes; Dictionary m_descriptorHeapRuntimeArrayTypes; + SpvInst* m_descriptorHeapAccelerationStructureRuntimeArrayType = nullptr; bool isInstUsedInStage(IRInst* inst, Stage s) @@ -7052,9 +7053,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex return type; } - SpvInst* getDescriptorRuntimeArrayType( - SpvInst* descriptorElementType, - int defaultArrayStride = 0) + SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType) { SLANG_ASSERT( descriptorElementType->opcode != SpvOpTypeAccelerationStructureKHR && @@ -7077,18 +7076,10 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex } if (userDefinedStride == 0) { - if (defaultArrayStride != 0) - { - userDefinedStride = defaultArrayStride; - } - else - { - IRBuilder builder(m_irModule); - builder.setInsertInto(m_irModule->getModuleInst()); + IRBuilder builder(m_irModule); + builder.setInsertInto(m_irModule->getModuleInst()); - stride = - emitOpConstantSizeOfEXT(nullptr, builder.getUIntType(), descriptorElementType); - } + stride = emitOpConstantSizeOfEXT(nullptr, builder.getUIntType(), descriptorElementType); } auto runtimeArrayType = emitOpTypeRuntimeArray(nullptr, descriptorElementType); @@ -7113,6 +7104,34 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex return runtimeArrayType; } + SpvInst* getAccelerationStructureDescriptorRuntimeArrayType() + { + if (m_descriptorHeapAccelerationStructureRuntimeArrayType) + return m_descriptorHeapAccelerationStructureRuntimeArrayType; + + IRBuilder builder(m_irModule); + builder.setInsertInto(m_irModule->getModuleInst()); + + // Acceleration structure heap entries are 64-bit device addresses that are + // converted to acceleration structure handles after loading. + auto descriptorElementType = ensureInst(builder.getUInt64Type()); + + auto userDefinedStride = m_targetProgram->getOptionSet().getIntOption( + CompilerOptionName::SPIRVResourceHeapStride); + if (userDefinedStride == 0) + userDefinedStride = 8; + + auto runtimeArrayType = emitOpTypeRuntimeArray(nullptr, descriptorElementType); + m_descriptorHeapAccelerationStructureRuntimeArrayType = runtimeArrayType; + + emitOpDecorateArrayStride( + getSection(SpvLogicalSectionID::Annotations), + nullptr, + runtimeArrayType, + SpvLiteralInteger::from32(userDefinedStride)); + return runtimeArrayType; + } + SpvInst* getDescriptorHeapBaseType(IRType* valueType, bool* outIsBufferResource = nullptr) { SpvInst* descriptorElementType = nullptr; @@ -7125,14 +7144,9 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex descriptorElementType = ensureInst(valueType); break; case kIROp_RaytracingAccelerationStructureType: - { - // Acceleration structure heap entries are 64-bit device addresses that are - // converted to acceleration structure handles after loading. - IRBuilder builder(m_irModule); - builder.setInsertInto(m_irModule->getModuleInst()); - descriptorElementType = ensureInst(builder.getUInt64Type()); - break; - } + if (outIsBufferResource) + *outIsBufferResource = false; + return getAccelerationStructureDescriptorRuntimeArrayType(); default: isBufferResource = true; descriptorElementType = ensureDescriptorHeapBufferDescriptorType( @@ -7143,12 +7157,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex if (outIsBufferResource) *outIsBufferResource = isBufferResource; - const int defaultAccelerationStructureStride = 8; - return getDescriptorRuntimeArrayType( - descriptorElementType, - valueType->getOp() == kIROp_RaytracingAccelerationStructureType - ? defaultAccelerationStructureStride - : 0); + return getDescriptorRuntimeArrayType(descriptorElementType); } SpvInst* emitAccelerationStructureFromDescriptorHeap( From e964d4b9f5330a7783f455c00d8abc2c2ef992bd Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Sat, 6 Jun 2026 09:14:14 -0700 Subject: [PATCH 07/13] Cover AS heap stride override in raygen --- docs/command-line-slangc-reference.md | 2 +- docs/user-guide/08-compiling.md | 2 +- source/slang/slang-options.cpp | 7 ++++--- .../descriptor-heap-acceleration-structure-raygen.slang | 5 +++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md index d9fa70ccbb8..5df334cd511 100644 --- a/docs/command-line-slangc-reference.md +++ b/docs/command-line-slangc-reference.md @@ -608,7 +608,7 @@ Specify the space index for the system defined global bindless resource array. **-spirv-resource-heap-stride <stride>** -Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType), except RaytracingAccelerationStructure entries emit a literal 8-byte ArrayStride for the uint64 device address elements. +Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType); for RaytracingAccelerationStructure entries, the 0 default emits a literal 8-byte ArrayStride for the uint64 device address elements. An explicit stride value still overrides these defaults. diff --git a/docs/user-guide/08-compiling.md b/docs/user-guide/08-compiling.md index d234cb32342..37aca751cac 100644 --- a/docs/user-guide/08-compiling.md +++ b/docs/user-guide/08-compiling.md @@ -1089,7 +1089,7 @@ meanings of their `CompilerOptionValue` encodings. | GenerateWholeProgram | When set will emit target code for the entire program instead of for a specific entry point. `intValue0` specifies a bool value for the setting. | | UseUpToDateBinaryModule | When set will only load precompiled modules if it is up-to-date with its source. `intValue0` specifies a bool value for the setting. | | ValidateUniformity | When set will perform [uniformity analysis](a1-05-uniformity.md).| -| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to emit `OpConstantSizeOfEXT(ResourceType)` as the default stride, except `RaytracingAccelerationStructure` entries emit a literal 8-byte `ArrayStride` for the `uint64` device address elements. | +| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to emit `OpConstantSizeOfEXT(ResourceType)` as the default stride. For `RaytracingAccelerationStructure` entries, the 0 default emits a literal 8-byte `ArrayStride` for the `uint64` device address elements; explicit stride values still override these defaults. | | SPIRVSamplerHeapStride | Specifies the byte stride for the sampler descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`. | | ForceDXLayout | When set forces the compiler to use DirectX-compatible (HLSL register packing) rules when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | | ForceCLayout | When set forces the compiler to use C struct layout rules (natural alignment, no HLSL/GLSL padding) when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index dd78b94b013..a131a27d401 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -883,9 +883,10 @@ void initCommandOptions(CommandOptions& options) "-spirv-resource-heap-stride", "-spirv-resource-heap-stride ", "Specify the byte stride for the resource descriptor heap when generating SPIRV with " - "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType), " - "except RaytracingAccelerationStructure entries emit a literal 8-byte ArrayStride for " - "the uint64 device address elements."}, + "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType); " + "for RaytracingAccelerationStructure entries, the 0 default emits a literal 8-byte " + "ArrayStride for the uint64 device address elements. An explicit stride value still " + "overrides these defaults."}, {OptionKind::SPIRVSamplerHeapStride, "-spirv-sampler-heap-stride", "-spirv-sampler-heap-stride ", diff --git a/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang b/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang index de50f8c2dc2..6b4814c2938 100644 --- a/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang +++ b/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang @@ -3,6 +3,7 @@ // loading a RaytracingAccelerationStructure from a descriptor heap in a // raygeneration shader (which already requires SpvCapabilityRayTracingKHR). //TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage raygeneration -entry rayGenMain -profile sm_6_6 -capability spvDescriptorHeapEXT +//TEST:SIMPLE(filecheck=OVERRIDE): -target spirv-asm -stage raygeneration -entry rayGenMain -profile sm_6_6 -capability spvDescriptorHeapEXT -spirv-resource-heap-stride 32 struct PushConstants { @@ -22,6 +23,10 @@ void rayGenMain(uniform PushConstants pushConstants) // CHECK-DAG: %[[AS_TYPE:[A-Za-z0-9_]+]] = OpTypeAccelerationStructureKHR // CHECK-DAG: %[[AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[U64]] // CHECK-DAG: OpDecorate %[[AS_HEAP_ARRAY]] ArrayStride 8 + // OVERRIDE-DAG: %[[OVERRIDE_U64:[A-Za-z0-9_]+]] = OpTypeInt 64 0 + // OVERRIDE-DAG: %[[OVERRIDE_AS_TYPE:[A-Za-z0-9_]+]] = OpTypeAccelerationStructureKHR + // OVERRIDE-DAG: %[[OVERRIDE_AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[OVERRIDE_U64]] + // OVERRIDE-DAG: OpDecorate %[[OVERRIDE_AS_HEAP_ARRAY]] ArrayStride 32 // CHECK-DAG: OpCapability RayTracingKHR // CHECK: OpUntypedAccessChainKHR %_ptr_UniformConstant %[[AS_HEAP_ARRAY]] %slang_resourceHeap // CHECK-NEXT: %[[AS_ADDR:[A-Za-z0-9_]+]] = OpLoad %[[U64]] From 57f1ac360ad4b3369af5229d83ecb56f8008b0ec Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Mon, 8 Jun 2026 08:49:02 -0700 Subject: [PATCH 08/13] Validate acceleration structure heap stride --- docs/command-line-slangc-reference.md | 2 +- docs/user-guide/03-convenience-features.md | 3 ++- docs/user-guide/08-compiling.md | 2 +- source/slang/slang-diagnostics.lua | 8 +++++++- source/slang/slang-emit-spirv.cpp | 14 +++++++++++++- source/slang/slang-options.cpp | 3 ++- .../descriptor-heap-acceleration-structure.slang | 3 +++ 7 files changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md index 5df334cd511..c878d9c15e8 100644 --- a/docs/command-line-slangc-reference.md +++ b/docs/command-line-slangc-reference.md @@ -608,7 +608,7 @@ Specify the space index for the system defined global bindless resource array. **-spirv-resource-heap-stride <stride>** -Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType); for RaytracingAccelerationStructure entries, the 0 default emits a literal 8-byte ArrayStride for the uint64 device address elements. An explicit stride value still overrides these defaults. +Specify the byte stride for the resource descriptor heap when generating SPIRV with spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType); for RaytracingAccelerationStructure entries, the 0 default emits a literal 8-byte ArrayStride for the uint64 device address elements. An explicit stride value still overrides these defaults; for acceleration-structure entries it must be at least 8 bytes. diff --git a/docs/user-guide/03-convenience-features.md b/docs/user-guide/03-convenience-features.md index 5cc2f7cc60c..d68dd8dbf8b 100644 --- a/docs/user-guide/03-convenience-features.md +++ b/docs/user-guide/03-convenience-features.md @@ -697,7 +697,8 @@ state, and combines the objects with an `OpSampledImage` instruction. By default, when using the `spvDescriptorHeapEXT` capability, Slang reinterprets the resource or sampler heap as an array of the requested resource type, whose stride is defined by the resource type and obtained from the `OpConstantSizeOfEXT` instruction. The user can override this behavior and specify a different -stride with the `-spirv-resource-heap-stride` or `-spirv-sampler-heap-stride` compiler options. +stride with the `-spirv-resource-heap-stride` or `-spirv-sampler-heap-stride` compiler options. For +acceleration-structure entries, an explicit resource heap stride must be at least 8 bytes. > **Note on `RaytracingAccelerationStructure`:** When the `spvDescriptorHeapEXT` capability is active and > a `DescriptorHandle` is dereferenced, Slang loads a 64-bit device address diff --git a/docs/user-guide/08-compiling.md b/docs/user-guide/08-compiling.md index 37aca751cac..afab1288bb4 100644 --- a/docs/user-guide/08-compiling.md +++ b/docs/user-guide/08-compiling.md @@ -1089,7 +1089,7 @@ meanings of their `CompilerOptionValue` encodings. | GenerateWholeProgram | When set will emit target code for the entire program instead of for a specific entry point. `intValue0` specifies a bool value for the setting. | | UseUpToDateBinaryModule | When set will only load precompiled modules if it is up-to-date with its source. `intValue0` specifies a bool value for the setting. | | ValidateUniformity | When set will perform [uniformity analysis](a1-05-uniformity.md).| -| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to emit `OpConstantSizeOfEXT(ResourceType)` as the default stride. For `RaytracingAccelerationStructure` entries, the 0 default emits a literal 8-byte `ArrayStride` for the `uint64` device address elements; explicit stride values still override these defaults. | +| SPIRVResourceHeapStride | Specifies the byte stride for the resource descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to emit `OpConstantSizeOfEXT(ResourceType)` as the default stride. For `RaytracingAccelerationStructure` entries, the 0 default emits a literal 8-byte `ArrayStride` for the `uint64` device address elements; explicit stride values still override these defaults, but must be at least 8 bytes for acceleration-structure entries. | | SPIRVSamplerHeapStride | Specifies the byte stride for the sampler descriptor heap when generating SPIR-V with `spvDescriptorHeapEXT`. `intValue0` encodes the stride in bytes; use 0 to let the driver compute the stride via `OpConstantSizeOfEXT`. | | ForceDXLayout | When set forces the compiler to use DirectX-compatible (HLSL register packing) rules when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | | ForceCLayout | When set forces the compiler to use C struct layout rules (natural alignment, no HLSL/GLSL padding) when laying out buffer struct fields during code generation. `intValue0` specifies a bool value for the setting. | diff --git a/source/slang/slang-diagnostics.lua b/source/slang/slang-diagnostics.lua index b8715b62f35..c1b4640a02e 100644 --- a/source/slang/slang-diagnostics.lua +++ b/source/slang/slang-diagnostics.lua @@ -5225,7 +5225,7 @@ err( span { loc = "location", message = "SubpassInput cannot be placed inside a ParameterBlock on Metal; framebuffer fetch inputs must be direct entry-point parameters." } ) --- SPIRV (57001-57004) +-- SPIRV (57001-57005) warning( "spirv-opt-failed", @@ -5255,6 +5255,12 @@ err( span { loc = "location", message = "output SPIR-V contains no exported symbols. Please make sure to specify at least one entrypoint." } ) +err( + "spirv-resource-heap-stride-too-small", + 57005, + "SPIR-V resource heap stride '~stride:Int' is too small for RaytracingAccelerationStructure descriptor heap entries; expected at least '~minimumStride:Int' bytes." +) + -- GLSL Compatibility (58001-58003) err( diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index d95cf0a90d8..cb6ed70c347 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -7116,10 +7116,22 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex // converted to acceleration structure handles after loading. auto descriptorElementType = ensureInst(builder.getUInt64Type()); + static const int kAccelerationStructureDescriptorHeapStride = 8; + auto userDefinedStride = m_targetProgram->getOptionSet().getIntOption( CompilerOptionName::SPIRVResourceHeapStride); if (userDefinedStride == 0) - userDefinedStride = 8; + { + userDefinedStride = kAccelerationStructureDescriptorHeapStride; + } + else if (userDefinedStride < kAccelerationStructureDescriptorHeapStride) + { + m_sink->diagnose(Diagnostics::SpirvResourceHeapStrideTooSmall{ + .stride = userDefinedStride, + .minimumStride = kAccelerationStructureDescriptorHeapStride, + }); + userDefinedStride = kAccelerationStructureDescriptorHeapStride; + } auto runtimeArrayType = emitOpTypeRuntimeArray(nullptr, descriptorElementType); m_descriptorHeapAccelerationStructureRuntimeArrayType = runtimeArrayType; diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index a131a27d401..7498b9858ce 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -886,7 +886,8 @@ void initCommandOptions(CommandOptions& options) "spvDescriptorHeapEXT. Defaults to 0, which will use OpConstantSizeOfEXT(ResourceType); " "for RaytracingAccelerationStructure entries, the 0 default emits a literal 8-byte " "ArrayStride for the uint64 device address elements. An explicit stride value still " - "overrides these defaults."}, + "overrides these defaults; for acceleration-structure entries it must be at least 8 " + "bytes."}, {OptionKind::SPIRVSamplerHeapStride, "-spirv-sampler-heap-stride", "-spirv-sampler-heap-stride ", diff --git a/tests/spirv/descriptor-heap-acceleration-structure.slang b/tests/spirv/descriptor-heap-acceleration-structure.slang index 2614fe7606a..25dcb6a3bb0 100644 --- a/tests/spirv/descriptor-heap-acceleration-structure.slang +++ b/tests/spirv/descriptor-heap-acceleration-structure.slang @@ -1,6 +1,8 @@ // Regression test for GitHub issues #10671 and #11231. //TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR //TEST:SIMPLE(filecheck=OVERRIDE): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR -spirv-resource-heap-stride 32 +//TEST:SIMPLE(filecheck=TOO_SMALL): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR -spirv-resource-heap-stride 4 +// TOO_SMALL: error[E57005]: SPIR-V resource heap stride '4' is too small for RaytracingAccelerationStructure descriptor heap entries; expected at least '8' bytes. struct PushConstants { @@ -43,6 +45,7 @@ void computeMain(uint3 dispatchThreadID: SV_DispatchThreadID, uniform PushConsta // CHECK-DAG: %[[AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[U64]] // CHECK-DAG: OpDecorate %[[AS_HEAP_ARRAY]] ArrayStride 8 // OVERRIDE-DAG: %[[OVERRIDE_U64:[A-Za-z0-9_]+]] = OpTypeInt 64 0 + // OVERRIDE-DAG: %[[OVERRIDE_AS_TYPE:[A-Za-z0-9_]+]] = OpTypeAccelerationStructureKHR // OVERRIDE-DAG: %[[OVERRIDE_AS_HEAP_ARRAY:[A-Za-z0-9_]+]] = OpTypeRuntimeArray %[[OVERRIDE_U64]] // OVERRIDE-DAG: OpDecorate %[[OVERRIDE_AS_HEAP_ARRAY]] ArrayStride 32 // CHECK-DAG: OpCapability RayQueryKHR From 65f46ce95c2cd9528b63081fe28b9895cccb9f18 Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Fri, 12 Jun 2026 04:33:10 +0000 Subject: [PATCH 09/13] Address descriptor heap review feedback --- source/slang/slang-emit-spirv.cpp | 54 +++++++++++++------------------ 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index cb6ed70c347..c86f3f97297 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -6880,7 +6880,6 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex SpvInst* m_descriptorHeapUntypedPointerType = nullptr; Dictionary m_descriptorHeapBufferDescriptorTypes; Dictionary m_descriptorHeapRuntimeArrayTypes; - SpvInst* m_descriptorHeapAccelerationStructureRuntimeArrayType = nullptr; bool isInstUsedInStage(IRInst* inst, Stage s) @@ -7053,7 +7052,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex return type; } - SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType) + SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType, int arrayStride = 0) { SLANG_ASSERT( descriptorElementType->opcode != SpvOpTypeAccelerationStructureKHR && @@ -7063,18 +7062,17 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex return *found; SpvInst* stride = nullptr; - int userDefinedStride = 0; - if (descriptorElementType->opcode == SpvOpTypeSampler) + if (arrayStride == 0 && descriptorElementType->opcode == SpvOpTypeSampler) { - userDefinedStride = m_targetProgram->getOptionSet().getIntOption( + arrayStride = m_targetProgram->getOptionSet().getIntOption( CompilerOptionName::SPIRVSamplerHeapStride); } - else + else if (arrayStride == 0) { - userDefinedStride = m_targetProgram->getOptionSet().getIntOption( + arrayStride = m_targetProgram->getOptionSet().getIntOption( CompilerOptionName::SPIRVResourceHeapStride); } - if (userDefinedStride == 0) + if (arrayStride == 0) { IRBuilder builder(m_irModule); builder.setInsertInto(m_irModule->getModuleInst()); @@ -7099,23 +7097,13 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex getSection(SpvLogicalSectionID::Annotations), nullptr, runtimeArrayType, - SpvLiteralInteger::from32(userDefinedStride)); + SpvLiteralInteger::from32(arrayStride)); } return runtimeArrayType; } - SpvInst* getAccelerationStructureDescriptorRuntimeArrayType() + int getAccelerationStructureDescriptorHeapStride() { - if (m_descriptorHeapAccelerationStructureRuntimeArrayType) - return m_descriptorHeapAccelerationStructureRuntimeArrayType; - - IRBuilder builder(m_irModule); - builder.setInsertInto(m_irModule->getModuleInst()); - - // Acceleration structure heap entries are 64-bit device addresses that are - // converted to acceleration structure handles after loading. - auto descriptorElementType = ensureInst(builder.getUInt64Type()); - static const int kAccelerationStructureDescriptorHeapStride = 8; auto userDefinedStride = m_targetProgram->getOptionSet().getIntOption( @@ -7133,15 +7121,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex userDefinedStride = kAccelerationStructureDescriptorHeapStride; } - auto runtimeArrayType = emitOpTypeRuntimeArray(nullptr, descriptorElementType); - m_descriptorHeapAccelerationStructureRuntimeArrayType = runtimeArrayType; - - emitOpDecorateArrayStride( - getSection(SpvLogicalSectionID::Annotations), - nullptr, - runtimeArrayType, - SpvLiteralInteger::from32(userDefinedStride)); - return runtimeArrayType; + return userDefinedStride; } SpvInst* getDescriptorHeapBaseType(IRType* valueType, bool* outIsBufferResource = nullptr) @@ -7156,9 +7136,19 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex descriptorElementType = ensureInst(valueType); break; case kIROp_RaytracingAccelerationStructureType: - if (outIsBufferResource) - *outIsBufferResource = false; - return getAccelerationStructureDescriptorRuntimeArrayType(); + { + if (outIsBufferResource) + *outIsBufferResource = false; + + IRBuilder builder(m_irModule); + builder.setInsertInto(m_irModule->getModuleInst()); + + // Acceleration structure heap entries are 64-bit device addresses that are + // converted to acceleration structure handles after loading. + descriptorElementType = ensureInst(builder.getUInt64Type()); + auto arrayStride = getAccelerationStructureDescriptorHeapStride(); + return getDescriptorRuntimeArrayType(descriptorElementType, arrayStride); + } default: isBufferResource = true; descriptorElementType = ensureDescriptorHeapBufferDescriptorType( From 022b0f09a658f1f815c7120b3c5bcca1989520f3 Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Fri, 12 Jun 2026 04:59:57 +0000 Subject: [PATCH 10/13] Use release assert for descriptor heap contract --- source/slang/slang-emit-spirv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index c86f3f97297..1ce803260c9 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -7054,7 +7054,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType, int arrayStride = 0) { - SLANG_ASSERT( + SLANG_RELEASE_ASSERT( descriptorElementType->opcode != SpvOpTypeAccelerationStructureKHR && "acceleration structure descriptor heaps must use uint64 elements"); From 5969ccfbc6eb1347b1c68e5b56e18d2576b7081e Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:22:40 +0000 Subject: [PATCH 11/13] Address descriptor runtime array review feedback --- source/slang/slang-emit-spirv.cpp | 60 ++++++++++++++----- ...scriptor-heap-acceleration-structure.slang | 2 + 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 1ce803260c9..b6718ff0b00 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -4935,7 +4935,10 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex { auto loadDesc = as(inst); auto resourceType = loadDesc->getTextureType(); - auto resourceArrayType = getDescriptorRuntimeArrayType(ensureInst(resourceType)); + auto descriptorElementType = ensureInst(resourceType); + auto resourceArrayType = getDescriptorRuntimeArrayType( + descriptorElementType, + getDescriptorHeapArrayStride(descriptorElementType)); auto imagePtr = emitInst( parent, nullptr, @@ -6877,9 +6880,28 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex } }; Dictionary m_builtinGlobalVars; + struct DescriptorRuntimeArrayKey + { + SpvInst* descriptorElementType = nullptr; + int arrayStride = 0; + + bool operator==(const DescriptorRuntimeArrayKey& other) const + { + return descriptorElementType == other.descriptorElementType && + arrayStride == other.arrayStride; + } + + HashCode getHashCode() const + { + return combineHash( + Slang::getHashCode(descriptorElementType), + Slang::getHashCode(arrayStride)); + } + }; + SpvInst* m_descriptorHeapUntypedPointerType = nullptr; Dictionary m_descriptorHeapBufferDescriptorTypes; - Dictionary m_descriptorHeapRuntimeArrayTypes; + Dictionary m_descriptorHeapRuntimeArrayTypes; bool isInstUsedInStage(IRInst* inst, Stage s) @@ -7052,26 +7074,31 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex return type; } - SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType, int arrayStride = 0) + int getDescriptorHeapArrayStride(SpvInst* descriptorElementType) + { + if (descriptorElementType->opcode == SpvOpTypeSampler) + { + return m_targetProgram->getOptionSet().getIntOption( + CompilerOptionName::SPIRVSamplerHeapStride); + } + + return m_targetProgram->getOptionSet().getIntOption( + CompilerOptionName::SPIRVResourceHeapStride); + } + + SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType, int arrayStride) { SLANG_RELEASE_ASSERT( descriptorElementType->opcode != SpvOpTypeAccelerationStructureKHR && "acceleration structure descriptor heaps must use uint64 elements"); - if (auto found = m_descriptorHeapRuntimeArrayTypes.tryGetValue(descriptorElementType)) + DescriptorRuntimeArrayKey key; + key.descriptorElementType = descriptorElementType; + key.arrayStride = arrayStride; + if (auto found = m_descriptorHeapRuntimeArrayTypes.tryGetValue(key)) return *found; SpvInst* stride = nullptr; - if (arrayStride == 0 && descriptorElementType->opcode == SpvOpTypeSampler) - { - arrayStride = m_targetProgram->getOptionSet().getIntOption( - CompilerOptionName::SPIRVSamplerHeapStride); - } - else if (arrayStride == 0) - { - arrayStride = m_targetProgram->getOptionSet().getIntOption( - CompilerOptionName::SPIRVResourceHeapStride); - } if (arrayStride == 0) { IRBuilder builder(m_irModule); @@ -7081,7 +7108,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex } auto runtimeArrayType = emitOpTypeRuntimeArray(nullptr, descriptorElementType); - m_descriptorHeapRuntimeArrayTypes[descriptorElementType] = runtimeArrayType; + m_descriptorHeapRuntimeArrayTypes[key] = runtimeArrayType; if (stride) { @@ -7159,7 +7186,8 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex if (outIsBufferResource) *outIsBufferResource = isBufferResource; - return getDescriptorRuntimeArrayType(descriptorElementType); + auto arrayStride = getDescriptorHeapArrayStride(descriptorElementType); + return getDescriptorRuntimeArrayType(descriptorElementType, arrayStride); } SpvInst* emitAccelerationStructureFromDescriptorHeap( diff --git a/tests/spirv/descriptor-heap-acceleration-structure.slang b/tests/spirv/descriptor-heap-acceleration-structure.slang index 25dcb6a3bb0..200b7945b0e 100644 --- a/tests/spirv/descriptor-heap-acceleration-structure.slang +++ b/tests/spirv/descriptor-heap-acceleration-structure.slang @@ -2,7 +2,9 @@ //TEST:SIMPLE(filecheck=CHECK): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR //TEST:SIMPLE(filecheck=OVERRIDE): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR -spirv-resource-heap-stride 32 //TEST:SIMPLE(filecheck=TOO_SMALL): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR -spirv-resource-heap-stride 4 +// TOO_SMALL-NOT: ArrayStride 4 // TOO_SMALL: error[E57005]: SPIR-V resource heap stride '4' is too small for RaytracingAccelerationStructure descriptor heap entries; expected at least '8' bytes. +// TOO_SMALL-NOT: ArrayStride 4 struct PushConstants { From dee174f34a70302f0c02e510aaf96ef664cf307b Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:33:55 +0000 Subject: [PATCH 12/13] Document descriptor heap stride helpers --- source/slang/slang-emit-spirv.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index b6718ff0b00..e2f183f1194 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -7074,6 +7074,10 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex return type; } + // Selects the configured heap stride for a non-acceleration-structure descriptor element. + // Keeping this lookup at the call site makes `getDescriptorRuntimeArrayType` consume only a + // caller-chosen stride; for example, sampler heaps use `SPIRVSamplerHeapStride`, while + // texture and buffer resource heaps use `SPIRVResourceHeapStride`. int getDescriptorHeapArrayStride(SpvInst* descriptorElementType) { if (descriptorElementType->opcode == SpvOpTypeSampler) @@ -7086,6 +7090,10 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex CompilerOptionName::SPIRVResourceHeapStride); } + // Builds or reuses the descriptor runtime array for a specific element type and stride. + // A zero stride emits an `ArrayStrideIdEXT` from `OpConstantSizeOfEXT` for descriptor-typed + // resources, while acceleration-structure heap entries pass the explicit `uint64` stride + // computed by `getAccelerationStructureDescriptorHeapStride`. SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType, int arrayStride) { SLANG_RELEASE_ASSERT( From 6aa13e5dad945609deb4cdc9d0254d4c4dfb392b Mon Sep 17 00:00:00 2001 From: Jay Kwak <82421531+jkwak-work@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:49:11 +0000 Subject: [PATCH 13/13] Deduplicate AS stride diagnostics --- source/slang/slang-emit-spirv.cpp | 13 +++++++++---- .../descriptor-heap-acceleration-structure.slang | 11 ++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index e2f183f1194..a2e07cf199e 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -6902,6 +6902,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex SpvInst* m_descriptorHeapUntypedPointerType = nullptr; Dictionary m_descriptorHeapBufferDescriptorTypes; Dictionary m_descriptorHeapRuntimeArrayTypes; + bool m_didDiagnoseAccelerationStructureDescriptorHeapStrideTooSmall = false; bool isInstUsedInStage(IRInst* inst, Stage s) @@ -7149,10 +7150,14 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex } else if (userDefinedStride < kAccelerationStructureDescriptorHeapStride) { - m_sink->diagnose(Diagnostics::SpirvResourceHeapStrideTooSmall{ - .stride = userDefinedStride, - .minimumStride = kAccelerationStructureDescriptorHeapStride, - }); + if (!m_didDiagnoseAccelerationStructureDescriptorHeapStrideTooSmall) + { + m_sink->diagnose(Diagnostics::SpirvResourceHeapStrideTooSmall{ + .stride = userDefinedStride, + .minimumStride = kAccelerationStructureDescriptorHeapStride, + }); + m_didDiagnoseAccelerationStructureDescriptorHeapStrideTooSmall = true; + } userDefinedStride = kAccelerationStructureDescriptorHeapStride; } diff --git a/tests/spirv/descriptor-heap-acceleration-structure.slang b/tests/spirv/descriptor-heap-acceleration-structure.slang index 200b7945b0e..25b8c8d03be 100644 --- a/tests/spirv/descriptor-heap-acceleration-structure.slang +++ b/tests/spirv/descriptor-heap-acceleration-structure.slang @@ -4,11 +4,13 @@ //TEST:SIMPLE(filecheck=TOO_SMALL): -target spirv-asm -stage compute -entry computeMain -profile sm_6_6 -capability spvDescriptorHeapEXT -capability spvRayQueryKHR -spirv-resource-heap-stride 4 // TOO_SMALL-NOT: ArrayStride 4 // TOO_SMALL: error[E57005]: SPIR-V resource heap stride '4' is too small for RaytracingAccelerationStructure descriptor heap entries; expected at least '8' bytes. +// TOO_SMALL-NOT: SPIR-V resource heap stride '4' is too small // TOO_SMALL-NOT: ArrayStride 4 struct PushConstants { DescriptorHandle accelerationStructure; + DescriptorHandle secondaryAccelerationStructure; DescriptorHandle> outputTexture; } @@ -62,6 +64,13 @@ void computeMain(uint3 dispatchThreadID: SV_DispatchThreadID, uniform PushConsta float3(0.0, 0.0, 1.0), t); + float secondaryT = 0.0; + traceRayClosestHit( + RaytracingAccelerationStructure(pushConstants.secondaryAccelerationStructure), + float3(0.2, 0.2, 0.0), + float3(0.0, 0.0, 1.0), + secondaryT); + RWTexture2D outputTexture = pushConstants.outputTexture; - outputTexture[dispatchThreadID.xy] = t; + outputTexture[dispatchThreadID.xy] = t + secondaryT; }