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