Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/command-line-slangc-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


<a id="spirv-sampler-heap-stride"></a>
Expand Down
10 changes: 6 additions & 4 deletions docs/user-guide/03-convenience-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<RaytracingAccelerationStructure>` 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

Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/08-compiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
8 changes: 7 additions & 1 deletion source/slang/slang-diagnostics.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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(
Expand Down
117 changes: 98 additions & 19 deletions source/slang/slang-emit-spirv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4935,7 +4935,10 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
{
auto loadDesc = as<IRSPIRVLoadTexelPointerFromHeap>(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,
Expand Down Expand Up @@ -6877,9 +6880,29 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
}
};
Dictionary<BuiltinSpvVarKey, SpvInst*> 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<SpvStorageClass, SpvInst*> m_descriptorHeapBufferDescriptorTypes;
Dictionary<SpvInst*, SpvInst*> m_descriptorHeapRuntimeArrayTypes;
Dictionary<DescriptorRuntimeArrayKey, SpvInst*> m_descriptorHeapRuntimeArrayTypes;
bool m_didDiagnoseAccelerationStructureDescriptorHeapStrideTooSmall = false;


bool isInstUsedInStage(IRInst* inst, Stage s)
Expand Down Expand Up @@ -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());
Expand All @@ -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)
{
Expand All @@ -7094,23 +7133,62 @@ 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;
bool isBufferResource = false;
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(
Expand All @@ -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(
Expand Down
6 changes: 5 additions & 1 deletion source/slang/slang-options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,11 @@ void initCommandOptions(CommandOptions& options)
"-spirv-resource-heap-stride",
"-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)."},
"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 <stride>",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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]]
Expand Down
28 changes: 23 additions & 5 deletions tests/spirv/descriptor-heap-acceleration-structure.slang
Original file line number Diff line number Diff line change
@@ -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<RaytracingAccelerationStructure> accelerationStructure;
DescriptorHandle<RaytracingAccelerationStructure> secondaryAccelerationStructure;
DescriptorHandle<RWTexture2D<float4>> outputTexture;
}

Expand Down Expand Up @@ -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]]
Expand All @@ -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<float4> outputTexture = pushConstants.outputTexture;
outputTexture[dispatchThreadID.xy] = t;
outputTexture[dispatchThreadID.xy] = t + secondaryT;
}
Loading