diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md
index 51c8f002688..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).
+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 96547544377..d68dd8dbf8b 100644
--- a/docs/user-guide/03-convenience-features.md
+++ b/docs/user-guide/03-convenience-features.md
@@ -697,14 +697,16 @@ 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
> 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/docs/user-guide/08-compiling.md b/docs/user-guide/08-compiling.md
index 955318193fd..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 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 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 4ae749770a1..a2e07cf199e 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,29 @@ 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 m_didDiagnoseAccelerationStructureDescriptorHeapStrideTooSmall = false;
bool isInstUsedInStage(IRInst* inst, Stage s)
@@ -7052,24 +7075,40 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
return type;
}
- SpvInst* getDescriptorRuntimeArrayType(SpvInst* descriptorElementType)
+ // 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 (auto found = m_descriptorHeapRuntimeArrayTypes.tryGetValue(descriptorElementType))
- return *found;
-
- SpvInst* stride = nullptr;
- int userDefinedStride = 0;
if (descriptorElementType->opcode == SpvOpTypeSampler)
{
- userDefinedStride = m_targetProgram->getOptionSet().getIntOption(
+ return m_targetProgram->getOptionSet().getIntOption(
CompilerOptionName::SPIRVSamplerHeapStride);
}
- else
- {
- userDefinedStride = m_targetProgram->getOptionSet().getIntOption(
- CompilerOptionName::SPIRVResourceHeapStride);
- }
- if (userDefinedStride == 0)
+
+ return m_targetProgram->getOptionSet().getIntOption(
+ 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(
+ descriptorElementType->opcode != SpvOpTypeAccelerationStructureKHR &&
+ "acceleration structure descriptor heaps must use uint64 elements");
+
+ DescriptorRuntimeArrayKey key;
+ key.descriptorElementType = descriptorElementType;
+ key.arrayStride = arrayStride;
+ if (auto found = m_descriptorHeapRuntimeArrayTypes.tryGetValue(key))
+ return *found;
+
+ SpvInst* stride = nullptr;
+ if (arrayStride == 0)
{
IRBuilder builder(m_irModule);
builder.setInsertInto(m_irModule->getModuleInst());
@@ -7078,7 +7117,7 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
}
auto runtimeArrayType = emitOpTypeRuntimeArray(nullptr, descriptorElementType);
- m_descriptorHeapRuntimeArrayTypes[descriptorElementType] = runtimeArrayType;
+ m_descriptorHeapRuntimeArrayTypes[key] = runtimeArrayType;
if (stride)
{
@@ -7094,11 +7133,37 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
getSection(SpvLogicalSectionID::Annotations),
nullptr,
runtimeArrayType,
- SpvLiteralInteger::from32(userDefinedStride));
+ SpvLiteralInteger::from32(arrayStride));
}
return runtimeArrayType;
}
+ int getAccelerationStructureDescriptorHeapStride()
+ {
+ static const int kAccelerationStructureDescriptorHeapStride = 8;
+
+ auto userDefinedStride = m_targetProgram->getOptionSet().getIntOption(
+ CompilerOptionName::SPIRVResourceHeapStride);
+ if (userDefinedStride == 0)
+ {
+ userDefinedStride = kAccelerationStructureDescriptorHeapStride;
+ }
+ else if (userDefinedStride < kAccelerationStructureDescriptorHeapStride)
+ {
+ if (!m_didDiagnoseAccelerationStructureDescriptorHeapStrideTooSmall)
+ {
+ m_sink->diagnose(Diagnostics::SpirvResourceHeapStrideTooSmall{
+ .stride = userDefinedStride,
+ .minimumStride = kAccelerationStructureDescriptorHeapStride,
+ });
+ m_didDiagnoseAccelerationStructureDescriptorHeapStrideTooSmall = true;
+ }
+ userDefinedStride = kAccelerationStructureDescriptorHeapStride;
+ }
+
+ return userDefinedStride;
+ }
+
SpvInst* getDescriptorHeapBaseType(IRType* valueType, bool* outIsBufferResource = nullptr)
{
SpvInst* descriptorElementType = nullptr;
@@ -7106,11 +7171,24 @@ 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:
+ {
+ 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(
@@ -7121,7 +7199,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/source/slang/slang-options.cpp b/source/slang/slang-options.cpp
index d2c403d142b..7498b9858ce 100644
--- a/source/slang/slang-options.cpp
+++ b/source/slang/slang-options.cpp
@@ -883,7 +883,11 @@ 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); "
+ "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."},
{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 f342df75f4e..6b4814c2938 100644
--- a/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang
+++ b/tests/spirv/descriptor-heap-acceleration-structure-raygen.slang
@@ -2,7 +2,8 @@
// 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
+//TEST:SIMPLE(filecheck=OVERRIDE): -target spirv-asm -stage raygeneration -entry rayGenMain -profile sm_6_6 -capability spvDescriptorHeapEXT -spirv-resource-heap-stride 32
struct PushConstants
{
@@ -20,8 +21,12 @@ 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
+ // 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]]
diff --git a/tests/spirv/descriptor-heap-acceleration-structure.slang b/tests/spirv/descriptor-heap-acceleration-structure.slang
index 4a567f06542..25b8c8d03be 100644
--- a/tests/spirv/descriptor-heap-acceleration-structure.slang
+++ b/tests/spirv/descriptor-heap-acceleration-structure.slang
@@ -1,9 +1,16 @@
-// 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
+//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: SPIR-V resource heap stride '4' is too small
+// TOO_SMALL-NOT: ArrayStride 4
struct PushConstants
{
DescriptorHandle accelerationStructure;
+ DescriptorHandle secondaryAccelerationStructure;
DescriptorHandle> outputTexture;
}
@@ -39,8 +46,12 @@ 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
+ // 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
// CHECK: OpUntypedAccessChainKHR %_ptr_UniformConstant %[[AS_HEAP_ARRAY]] %slang_resourceHeap
// CHECK-NEXT: %[[AS_ADDR:[A-Za-z0-9_]+]] = OpLoad %[[U64]]
@@ -53,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;
}