From ae3d6495f90d679958bc4b514096b90c03d73020 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Fri, 11 Sep 2026 10:02:55 -0700 Subject: [PATCH 01/12] Add onSamplerHeap parameter to getDescriptorHeapRuntimeArrayType for CLI stride support --- include/dxc/Support/HLSLOptions.td | 4 + include/dxc/Support/SPIRVOptions.h | 7 ++ lib/DxcSupport/HLSLOptions.cpp | 51 +++++++++++- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 10 ++- tools/clang/lib/SPIRV/SpirvEmitter.h | 11 ++- ...descriptorheap.ext.stride-cli-permute.hlsl | 80 +++++++++++++++++++ .../sm6_6.descriptorheap.ext.stride-cli.hlsl | 19 +++++ 7 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl diff --git a/include/dxc/Support/HLSLOptions.td b/include/dxc/Support/HLSLOptions.td index bfd986ad3ea..51913ae810d 100644 --- a/include/dxc/Support/HLSLOptions.td +++ b/include/dxc/Support/HLSLOptions.td @@ -454,6 +454,10 @@ def fvk_bind_sampler_heap : MultiArg<["-"], "fvk-bind-sampler-heap", 2>, MetaVar HelpText<"Specify Vulkan binding number and set number for the sampler heap.">; def fvk_bind_counter_heap : MultiArg<["-"], "fvk-bind-counter-heap", 2>, MetaVarName<" ">, Group, Flags<[CoreOption, DriverOption]>, HelpText<"Specify Vulkan binding number and set number for the counter heap.">; +def fvk_resource_heap_stride : Separate<["-"], "fvk-resource-heap-stride">, MetaVarName<"">, Group, Flags<[CoreOption, DriverOption]>, + HelpText<"Override the byte ArrayStride of the resource descriptor heap runtime array. Must be a power of 2 in [8, 256].">; +def fvk_sampler_heap_stride : Separate<["-"], "fvk-sampler-heap-stride">, MetaVarName<"">, Group, Flags<[CoreOption, DriverOption]>, + HelpText<"Override the byte ArrayStride of the sampler descriptor heap runtime array. Must be a power of 2 in [8, 256].">; // SPIRV Change Ends ////////////////////////////////////////////////////////////////////////////// diff --git a/include/dxc/Support/SPIRVOptions.h b/include/dxc/Support/SPIRVOptions.h index 0253caba63c..06b414d372b 100644 --- a/include/dxc/Support/SPIRVOptions.h +++ b/include/dxc/Support/SPIRVOptions.h @@ -110,6 +110,13 @@ struct SpirvCodeGenOptions { std::optional samplerHeapBinding; std::optional counterHeapBinding; + // User-defined byte ArrayStride overrides for the resource/sampler descriptor + // heap runtime arrays (-fvk-resource-heap-stride / -fvk-sampler-heap-stride). + // When set, the value is a literal power of 2 in [8, 256] and replaces the + // ArrayStrideIdEXT decoration that heap would otherwise carry. + std::optional resourceHeapStride; + std::optional samplerHeapStride; + bool signaturePacking = false; ///< Whether signature packing is enabled or not diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index 1630243aab6..4398a414a6b 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -357,6 +357,43 @@ handleFixedBinding(const InputArgList &args, OptSpecifier id, return true; } +// Parses the single-integer descriptor-heap stride flag |id| in |args|. If +// present, validates that the value is a power of 2 in [8, 256] and stores it +// in |stride|. Returns true on success (including when the flag is absent). +// Returns false and writes to |errors| when the value is malformed or invalid, +// using |name| as the pretty flag name. +static bool handleHeapStride(const InputArgList &args, OptSpecifier id, + std::optional *stride, + llvm::StringRef name, llvm::raw_ostream &errors) { + Arg *arg = args.getLastArg(id); + if (!arg) { + *stride = std::nullopt; + return true; + } + + if (!args.hasArg(OPT_spirv)) { + errors << name << " requires -spirv"; + return false; + } + + llvm::StringRef value = arg->getValue(); + uint32_t number = 0; + if (value.getAsInteger(10, number)) { + errors << "invalid " << name << " argument: '" << value << "'"; + return false; + } + // Power of 2 in [8, 256] inclusive. + if (number < 8 || number > 256 || (number & (number - 1)) != 0) { + errors << name + << " must be a power of 2 between 8 and 256 (inclusive); got " + << value; + return false; + } + + *stride = number; + return true; +} + // Check if any options that are unsupported with SPIR-V are used. static bool hasUnsupportedSpirvOption(const InputArgList &args, llvm::raw_ostream &errors) { @@ -1175,6 +1212,16 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, return 1; } + bool strideOk = true; + strideOk &= handleHeapStride(Args, OPT_fvk_resource_heap_stride, + &opts.SpirvOptions.resourceHeapStride, + "-fvk-resource-heap-stride", errors); + strideOk &= handleHeapStride(Args, OPT_fvk_sampler_heap_stride, + &opts.SpirvOptions.samplerHeapStride, + "-fvk-sampler-heap-stride", errors); + if (!strideOk) + return 1; + for (const Arg *A : Args.filtered(OPT_fspv_extension_EQ)) { opts.SpirvOptions.allowedExtensions.push_back(A->getValue()); } @@ -1316,7 +1363,9 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, !Args.getLastArgValue(OPT_fvk_u_shift).empty() || !Args.getLastArgValue(OPT_fvk_bind_resource_heap).empty() || !Args.getLastArgValue(OPT_fvk_bind_sampler_heap).empty() || - !Args.getLastArgValue(OPT_fvk_bind_counter_heap).empty()) { + !Args.getLastArgValue(OPT_fvk_bind_counter_heap).empty() || + !Args.getLastArgValue(OPT_fvk_resource_heap_stride).empty() || + !Args.getLastArgValue(OPT_fvk_sampler_heap_stride).empty()) { errors << "SPIR-V CodeGen not available. " "Please recompile with -DENABLE_SPIRV_CODEGEN=ON."; return 1; diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 983735439ef..dee94e62705 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -5657,8 +5657,9 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( const BufferEXTType *bufferDescriptorType = spvContext.getBufferEXTType(bufferSC); - const SpirvType *arrayType = - getDescriptorHeapRuntimeArrayType(bufferDescriptorType); + // Buffer descriptors are always on the resource heap. + const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType( + bufferDescriptorType, /*onSamplerHeap=*/false); SpirvUntypedAccessChainKHR *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, arrayType, heapVar, index, @@ -7381,8 +7382,9 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, const SpirvType *handleType = lowerTypeVisitor.lowerType(resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - const SpirvType *arrayType = - getDescriptorHeapRuntimeArrayType(handleType); + // Images and samplers may come from either heap; pick the right stride. + const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType( + handleType, isSamplerDescriptorHeap(decl)); SpirvUntypedAccessChainKHR *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, arrayType, var, index, diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 56effc43308..f11fcfc8154 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -439,10 +439,13 @@ class SpirvEmitter : public ASTConsumer { /// Translates the given varDecl into a spec constant. void createSpecConstant(const VarDecl *varDecl); - /// Returns the OpTypeRuntimeArray for a descriptor heap array of elemType, - /// decorated with ArrayStrideIdEXT referencing an OpConstantSizeOfEXT of the - /// element (descriptor) type. - const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType); + /// Returns the OpTypeRuntimeArray for a descriptor heap array of elemType. + /// onSamplerHeap selects which heap the descriptor is loaded from. If that + /// heap has a -fvk-{resource,sampler}-heap-stride override, the array carries + /// that literal ArrayStride; otherwise it is decorated with ArrayStrideIdEXT + /// referencing the shared stride for the heap. + const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap); /// Emits the descriptor-heap buffer access for a buffer-like resource /// (StructuredBuffer/ByteAddressBuffer/ConstantBuffer/TextureBuffer + RW diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl new file mode 100644 index 00000000000..d1e1ff5544d --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl @@ -0,0 +1,80 @@ +// Verifies: every legal CLI stride value flows independently to each heap's literal ArrayStride +// (full 6x6 cross product, RS!=SS proves no cross-contamination), and invalid values are rejected at option-parse time. +// +// [[RS]] = resource-heap stride, [[SS]] = sampler-heap stride (per-RUN via -D). + +// ---- Full 6x6 cross product of {8,16,32,64,128,256} x {8,16,32,64,128,256} ---- +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=8 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=8 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=8 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=8 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=8 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=8 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=16 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=16 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=16 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=16 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=16 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=16 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=32 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=32 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=32 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=32 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=32 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=32 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=64 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=64 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=64 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=64 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=64 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=64 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=128 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=128 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=128 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=128 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=128 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=128 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=256 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=256 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=256 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=256 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=256 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=256 -DSS=256 + +// ---- Invalid values are rejected at option-parsing time (power-of-two in [8,256]) ---- +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 48 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 4 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 512 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 0 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 24 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADSS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride abc -spirv %s 2>&1 | FileCheck %s --check-prefix=BADNUM + +// ---- Both flags invalid: both errors must be reported (no short-circuit) ---- +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 48 -fvk-sampler-heap-stride 24 -spirv %s 2>&1 | FileCheck %s --check-prefix=BOTH + +// Bind each heap's runtime array to its element type so the strides are checked +// independently. The resource Texture2D image array carries [[RS]]; the sampler +// array carries [[SS]]. +// CHECK-DAG: %[[TexType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SampType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[TexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexType]]{{$}} +// CHECK-DAG: %[[SampArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SampType]]{{$}} +// CHECK-DAG: OpDecorate %[[TexArray]] ArrayStride [[RS]] +// CHECK-DAG: OpDecorate %[[SampArray]] ArrayStride [[SS]] + +// BADRS: -fvk-resource-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// BADSS: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// BADNUM: invalid -fvk-resource-heap-stride argument: 'abc' +// Both flags invalid: resource error appears first, sampler error follows on next line. +// BOTH: -fvk-resource-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// BOTH: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2D tex = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + float4 c = tex.SampleLevel(samp, float2(0, 0), 0); + outputBytes.Store(0, asuint(c.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl new file mode 100644 index 00000000000..dcd46db0d3b --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl @@ -0,0 +1,19 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s + +// Verifies: -fvk-resource-heap-stride / -fvk-sampler-heap-stride emit a literal +// ArrayStride and suppress the default OpConstantSizeOfEXT stride. + +// CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 +// CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 +// CHECK-NOT: ArrayStrideIdEXT + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + StructuredBuffer sb = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + Texture2D tex = ResourceDescriptorHeap[1]; + outputBytes.Store(0, sb.Load(tid.x)); + outputBytes.Store(4, (uint)tex.SampleLevel(samp, float2(0, 0), 0).r); +} From c0f85ca5990cf929385990527d27269da1586039 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 3 Aug 2026 17:37:33 -0700 Subject: [PATCH 02/12] update docs --- docs/SPIR-V.rst | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 45021716027..3ce0542b1b4 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2102,9 +2102,10 @@ objects as untyped variables in ``UniformConstant`` storage class: The concrete descriptor type is selected at each heap access. For image, sampler, and texel buffer resources, DXC forms a runtime array of that descriptor type, decorates the array with a byte stride, and uses -``OpUntypedAccessChainKHR`` followed by ``OpLoad``. The stride is an -``ArrayStrideIdEXT`` decoration referencing a specialization constant rather -than a literal ``ArrayStride`` (see `Descriptor heap array stride`_ below): +``OpUntypedAccessChainKHR`` followed by ``OpLoad``. By default the stride is an +``ArrayStrideIdEXT`` decoration referencing a specialization constant; a literal +``ArrayStride`` is emitted only when the stride is overridden on the command +line (see `Descriptor heap array stride`_ below): .. code:: spirv @@ -2224,10 +2225,10 @@ Three further restrictions on heap accesses produce diagnostics: Descriptor heap array stride ++++++++++++++++++++++++++++ -All resource heap runtime arrays share a single ``ArrayStrideIdEXT`` decoration -rather than a literal ``ArrayStride``, because descriptor sizes are not known -until pipeline creation. The shared value is built from ``OpConstantSizeOfEXT`` -and ``OpSpecConstantOp`` and evaluates to +By default, all resource heap runtime arrays share a single ``ArrayStrideIdEXT`` +decoration rather than a literal ``ArrayStride``, because descriptor sizes are +not known until pipeline creation. The shared value is built from +``OpConstantSizeOfEXT`` and ``OpSpecConstantOp`` and evaluates to ``max(sizeof(image_descriptor), sizeof(buffer_descriptor))``. The sampler heap carries its own ``ArrayStrideIdEXT`` equal to ``sizeof(sampler_descriptor)``. @@ -2259,6 +2260,20 @@ a ray-tracing stage and heap-loads a ``RaytracingAccelerationStructure`` without such an extension is rejected, because its stride would not cover the acceleration structure descriptor. +The computed stride can be replaced with a fixed literal using +``-fvk-resource-heap-stride `` and ``-fvk-sampler-heap-stride ``, which +emit ``OpDecorate ArrayStride N`` on the resource and sampler heap +arrays respectively. ``N`` must be a power of two in the inclusive range +[8, 256], and both flags require ``-spirv``. The command-line override has the +highest precedence: when it is set for a heap, no ``ArrayStrideIdEXT`` is +emitted for that heap and no ``OpConstantSizeOfEXT`` is built for it. The two +flags are independent, so overriding one heap leaves the other on its computed +stride. + +The literal is not validated against the descriptor sizes of the target +implementation. A value smaller than the largest descriptor that may appear in +the heap produces out-of-bounds descriptor accesses at runtime. + HLSL Expressions ================ From 28f783f8dd4918a9788e174f00b560b9b5499fdc Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Tue, 15 Sep 2026 14:43:39 -0700 Subject: [PATCH 03/12] Suppress heap stride diagnostic when CLI option given --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 23 +++++++++++++++---- ...t.acceleration-structure.stride.error.hlsl | 22 +++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index dee94e62705..15448fc701c 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -7358,13 +7358,19 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, // The stride was decided without an acceleration structure term (not a // ray-tracing stage, no ray extension listed). Reject the load: the // heap would be indexed with a stride that may be too narrow. + // Exception: -fvk-resource-heap-stride supplies a literal stride that + // bypasses the spec-constant machinery entirely, so the user has + // explicitly taken ownership of the stride value. if (isRaytracingAccelerationStructure(resourceType) && - !spvBuilder.resourceHeapStrideIncludesAccelStruct()) { + !spvBuilder.resourceHeapStrideIncludesAccelStruct() && + !spirvOptions.resourceHeapStride.has_value()) { emitError("acceleration structure loaded from ResourceDescriptorHeap " "requires the resource heap stride to account for " "acceleration structure descriptors; compile with " - "-fspv-extension=SPV_KHR_ray_tracing or " - "-fspv-extension=SPV_KHR_ray_query", + "-fspv-extension=SPV_KHR_ray_tracing, " + "-fspv-extension=SPV_NV_ray_tracing, " + "-fspv-extension=SPV_KHR_ray_query, or " + "-fvk-resource-heap-stride", expr->getExprLoc()); return nullptr; } @@ -9831,7 +9837,16 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { } const SpirvType * -SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType) { +SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap) { + // -fvk-{resource,sampler}-heap-stride has the highest precedence: the array + // carries a literal ArrayStride and no ArrayStrideIdEXT. + const std::optional &cliStride = + onSamplerHeap ? spirvOptions.samplerHeapStride + : spirvOptions.resourceHeapStride; + if (cliStride.has_value()) + return spvContext.getRuntimeArrayType(elemType, *cliStride); + // Descriptor sizes are defined by the client API, so the stride is an // ArrayStrideIdEXT operand rather than a literal. The sampler heap holds one // descriptor type. The resource heap is a flat array in which any resource diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl index 84c2cd40dbc..f3d83c6eb16 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl @@ -8,6 +8,18 @@ // RUN: -fspv-extension=SPV_KHR_ray_query \ // RUN: -spirv %s | FileCheck %s --check-prefix=OK +// Verify -fvk-resource-heap-stride suppresses the diagnostic: the stride is +// user-supplied so no ray-extension pre-scan is needed to widen it. +// The AS runtime array must carry a literal ArrayStride (not ArrayStrideIdEXT) +// and the compiler must not emit OpConstantSizeOfEXT for the AS placeholder. +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 \ +// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ +// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ +// RUN: -fspv-extension=SPV_KHR_ray_query \ +// RUN: -fvk-resource-heap-stride 256 \ +// RUN: -spirv %s | FileCheck %s --check-prefix=CLI + // Verifies: an acceleration structure loaded from ResourceDescriptorHeap is // rejected when the resource-heap stride was not widened to include the // acceleration structure descriptor size. @@ -22,7 +34,7 @@ // shader also needs for RayQuery (SPV_KHR_ray_tracing alone does not provide // the RayQueryKHR capability), and must compile. -// CHECK: error: acceleration structure loaded from ResourceDescriptorHeap requires the resource heap stride to account for acceleration structure descriptors; compile with -fspv-extension=SPV_KHR_ray_tracing or -fspv-extension=SPV_KHR_ray_query +// CHECK: error: acceleration structure loaded from ResourceDescriptorHeap requires the resource heap stride to account for acceleration structure descriptors; compile with -fspv-extension=SPV_KHR_ray_tracing, -fspv-extension=SPV_NV_ray_tracing, -fspv-extension=SPV_KHR_ray_query, or -fvk-resource-heap-stride // The variable initialized from the rejected load must not cascade into a // fatal error that hides the diagnostic above. @@ -32,6 +44,14 @@ // OK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR // OK-DAG: OpConstantSizeOfEXT %uint %[[Accel]] +// AS runtime array carries the user-supplied literal stride (not ArrayStrideIdEXT), +// and no OpConstantSizeOfEXT is emitted for the AS placeholder. +// CLI-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR +// CLI-DAG: %[[AccelArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] +// CLI-DAG: OpDecorate %[[AccelArr]] ArrayStride 256 +// CLI-NOT: OpDecorateId %[[AccelArr]] ArrayStrideIdEXT +// CLI-NOT: OpConstantSizeOfEXT %uint %[[Accel]] + RWBuffer output : register(u0); [numthreads(1, 1, 1)] From c0dcd5adaaeeefc9784d0af75ae1d7b47b8646ed Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Tue, 15 Sep 2026 14:49:52 -0700 Subject: [PATCH 04/12] [SPIR-V] Add release note for descriptor heap stride CLI support (#8519) --- docs/ReleaseNotes.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 6c305354127..6a5d1e1d5d4 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -30,7 +30,11 @@ The included licenses apply to the following files: `-fspv-extension=SPV_KHR_ray_query`, `SPV_KHR_ray_tracing` or `SPV_NV_ray_tracing` so the resource heap stride covers acceleration structure descriptors; otherwise compilation fails. - +- Added `-fvk-resource-heap-stride` and `-fvk-sampler-heap-stride` to override + the `ArrayStride` of the descriptor heap runtime arrays emitted by + `-fspv-use-descriptor-heap`. The value must be a power of two in `[8, 256]` + and takes precedence over the default `OpConstantSizeOfEXT`-based stride. + Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. From 0f266d653661721bb39e4b9a2b3d9a8936c34e42 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 24 Aug 2026 13:50:56 -0700 Subject: [PATCH 05/12] nit testing fixes and clean up --- lib/DxcSupport/HLSLOptions.cpp | 6 +++--- .../sm6_6.descriptorheap.ext.stride-cli-permute.hlsl | 12 +++++++++--- .../sm6_6.descriptorheap.ext.stride-cli.hlsl | 10 ++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index 4398a414a6b..2c2dd308aca 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -372,21 +372,21 @@ static bool handleHeapStride(const InputArgList &args, OptSpecifier id, } if (!args.hasArg(OPT_spirv)) { - errors << name << " requires -spirv"; + errors << name << " requires -spirv\n"; return false; } llvm::StringRef value = arg->getValue(); uint32_t number = 0; if (value.getAsInteger(10, number)) { - errors << "invalid " << name << " argument: '" << value << "'"; + errors << "invalid " << name << " argument: '" << value << "'\n"; return false; } // Power of 2 in [8, 256] inclusive. if (number < 8 || number > 256 || (number & (number - 1)) != 0) { errors << name << " must be a power of 2 between 8 and 256 (inclusive); got " - << value; + << value << "\n"; return false; } diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl index d1e1ff5544d..b11d50df238 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl @@ -52,6 +52,10 @@ // ---- Both flags invalid: both errors must be reported (no short-circuit) ---- // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 48 -fvk-sampler-heap-stride 24 -spirv %s 2>&1 | FileCheck %s --check-prefix=BOTH +// ---- Flag without -spirv: "requires -spirv" diagnostic ---- +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 %s 2>&1 | FileCheck %s --check-prefix=NOSPIRVRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 16 %s 2>&1 | FileCheck %s --check-prefix=NOSPIRVSS + // Bind each heap's runtime array to its element type so the strides are checked // independently. The resource Texture2D image array carries [[RS]]; the sampler // array carries [[SS]]. @@ -65,9 +69,11 @@ // BADRS: -fvk-resource-heap-stride must be a power of 2 between 8 and 256 (inclusive) // BADSS: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) // BADNUM: invalid -fvk-resource-heap-stride argument: 'abc' -// Both flags invalid: resource error appears first, sampler error follows on next line. -// BOTH: -fvk-resource-heap-stride must be a power of 2 between 8 and 256 (inclusive) -// BOTH: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// Both flags invalid: resource error appears first, sampler error follows on the next line. +// BOTH: -fvk-resource-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// BOTH-NEXT: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// NOSPIRVRS: -fvk-resource-heap-stride requires -spirv +// NOSPIRVSS: -fvk-sampler-heap-stride requires -spirv RWByteAddressBuffer outputBytes : register(u0); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl index dcd46db0d3b..789747f5e42 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl @@ -1,12 +1,22 @@ // RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -spirv %s | FileCheck %s --check-prefix=RS-ONLY +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s --check-prefix=SS-ONLY // Verifies: -fvk-resource-heap-stride / -fvk-sampler-heap-stride emit a literal // ArrayStride and suppress the default OpConstantSizeOfEXT stride. +// Single-flag cases (RS-ONLY / SS-ONLY) verify the unflagged heap falls back to +// ArrayStrideIdEXT rather than picking up the other heap's literal stride. // CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 // CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 // CHECK-NOT: ArrayStrideIdEXT +// RS-ONLY-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 +// RS-ONLY: ArrayStrideIdEXT + +// SS-ONLY-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 +// SS-ONLY: ArrayStrideIdEXT + RWByteAddressBuffer outputBytes : register(u0); [numthreads(1, 1, 1)] From 89a6ce404277d318680a265dbf169c224ed2f1e2 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Fri, 11 Sep 2026 15:00:11 -0700 Subject: [PATCH 06/12] improve comments and convention compliance --- include/dxc/Support/SPIRVOptions.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/dxc/Support/SPIRVOptions.h b/include/dxc/Support/SPIRVOptions.h index 06b414d372b..c7c8ce3c183 100644 --- a/include/dxc/Support/SPIRVOptions.h +++ b/include/dxc/Support/SPIRVOptions.h @@ -110,9 +110,8 @@ struct SpirvCodeGenOptions { std::optional samplerHeapBinding; std::optional counterHeapBinding; - // User-defined byte ArrayStride overrides for the resource/sampler descriptor - // heap runtime arrays (-fvk-resource-heap-stride / -fvk-sampler-heap-stride). - // When set, the value is a literal power of 2 in [8, 256] and replaces the + // -fvk-resource-heap-stride / -fvk-sampler-heap-stride + // When set, a power of 2 in [8, 256] byte ArrayStride replaces the // ArrayStrideIdEXT decoration that heap would otherwise carry. std::optional resourceHeapStride; std::optional samplerHeapStride; From 60843f6a2eb1fd2f69dbaeccbef580d719b02542 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 2 Nov 2026 12:41:12 -0800 Subject: [PATCH 07/12] Require -fspv-use-descriptor-heap for heap stride flags --- docs/SPIR-V.rst | 4 +++- lib/DxcSupport/HLSLOptions.cpp | 8 ++++++++ tools/clang/lib/SPIRV/SpirvEmitter.cpp | 5 +++-- .../sm6_6.descriptorheap.ext.stride-cli-permute.hlsl | 8 ++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 3ce0542b1b4..1113f7c05c3 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2264,7 +2264,9 @@ The computed stride can be replaced with a fixed literal using ``-fvk-resource-heap-stride `` and ``-fvk-sampler-heap-stride ``, which emit ``OpDecorate ArrayStride N`` on the resource and sampler heap arrays respectively. ``N`` must be a power of two in the inclusive range -[8, 256], and both flags require ``-spirv``. The command-line override has the +[8, 256], and both flags require ``-spirv`` and ``-fspv-use-descriptor-heap`` +(they are rejected otherwise, since they would have no effect). The +command-line override has the highest precedence: when it is set for a heap, no ``ArrayStrideIdEXT`` is emitted for that heap and no ``OpConstantSizeOfEXT`` is built for it. The two flags are independent, so overriding one heap leaves the other on its computed diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index 2c2dd308aca..0b0048574dd 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -376,6 +376,14 @@ static bool handleHeapStride(const InputArgList &args, OptSpecifier id, return false; } + // The stride is only consumed by the native heap lowering. Without it the + // override would be accepted and silently have no effect. + if (!args.hasFlag(OPT_fspv_use_descriptor_heap, OPT_fspv_use_emulated_heap, + false)) { + errors << name << " requires -fspv-use-descriptor-heap\n"; + return false; + } + llvm::StringRef value = arg->getValue(); uint32_t number = 0; if (value.getAsInteger(10, number)) { diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 15448fc701c..05f9b5faca2 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -9852,8 +9852,9 @@ SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, // descriptor type. The resource heap is a flat array in which any resource // descriptor may sit at any slot, so all its runtime arrays share one stride // (see SpirvBuilder::getResourceHeapArrayStride) instead of the stride of the - // accessed type. - SpirvInstruction *strideId = isa(elemType) + // accessed type. As with the literal override above, the stride follows the + // heap that is accessed, not the element type. + SpirvInstruction *strideId = onSamplerHeap ? spvBuilder.getSamplerHeapArrayStride() : spvBuilder.getResourceHeapArrayStride(); return spvContext.getRuntimeArrayType(elemType, llvm::None, strideId); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl index b11d50df238..c2c0dcf5648 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl @@ -56,6 +56,12 @@ // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 %s 2>&1 | FileCheck %s --check-prefix=NOSPIRVRS // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 16 %s 2>&1 | FileCheck %s --check-prefix=NOSPIRVSS +// ---- Flag without the native heap (plain -spirv, or the emulated heap): the override would be ignored, so it is rejected ---- +// RUN: not %dxc -T cs_6_6 -E main -fvk-resource-heap-stride 128 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPRS +// RUN: not %dxc -T cs_6_6 -E main -fvk-sampler-heap-stride 16 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPSS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-emulated-heap -fvk-resource-heap-stride 128 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-emulated-heap -fvk-sampler-heap-stride 16 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPSS + // Bind each heap's runtime array to its element type so the strides are checked // independently. The resource Texture2D image array carries [[RS]]; the sampler // array carries [[SS]]. @@ -74,6 +80,8 @@ // BOTH-NEXT: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) // NOSPIRVRS: -fvk-resource-heap-stride requires -spirv // NOSPIRVSS: -fvk-sampler-heap-stride requires -spirv +// NOHEAPRS: -fvk-resource-heap-stride requires -fspv-use-descriptor-heap +// NOHEAPSS: -fvk-sampler-heap-stride requires -fspv-use-descriptor-heap RWByteAddressBuffer outputBytes : register(u0); From d533b703ee70387b073e57505ea0e16a7d9f4f14 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Tue, 3 Nov 2026 08:16:10 -0800 Subject: [PATCH 08/12] run CLI stride AS test without extension list so the override is what allows it --- ...criptorheap.ext.acceleration-structure.stride.error.hlsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl index f3d83c6eb16..82411fa3514 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl @@ -12,11 +12,11 @@ // user-supplied so no ray-extension pre-scan is needed to widen it. // The AS runtime array must carry a literal ArrayStride (not ArrayStrideIdEXT) // and the compiler must not emit OpConstantSizeOfEXT for the AS placeholder. +// There is deliberately no -fspv-extension list: SPV_KHR_ray_query would make +// the pre-scan widen the stride by itself, so compiling would not depend on the +// override. The extensions this shader needs are enabled by default. // RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ // RUN: -fspv-target-env=vulkan1.3 \ -// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ -// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ -// RUN: -fspv-extension=SPV_KHR_ray_query \ // RUN: -fvk-resource-heap-stride 256 \ // RUN: -spirv %s | FileCheck %s --check-prefix=CLI From 876b0590048dd129867f53b17d50c7a0d5f4448b Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Tue, 3 Nov 2026 08:30:21 -0800 Subject: [PATCH 09/12] fix formatting and release notes --- docs/ReleaseNotes.md | 3 ++- .../sm6_6.descriptorheap.ext.stride-cli-permute.hlsl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 6a5d1e1d5d4..4b1c7f3347f 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -34,7 +34,8 @@ The included licenses apply to the following files: the `ArrayStride` of the descriptor heap runtime arrays emitted by `-fspv-use-descriptor-heap`. The value must be a power of two in `[8, 256]` and takes precedence over the default `OpConstantSizeOfEXT`-based stride. - + Both flags require `-fspv-use-descriptor-heap` and are rejected otherwise. + Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl index c2c0dcf5648..76f90bf1702 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl @@ -1,4 +1,4 @@ -// Verifies: every legal CLI stride value flows independently to each heap's literal ArrayStride +// Verifies: every legal CLI stride value flows independently to each heap's literal ArrayStride // (full 6x6 cross product, RS!=SS proves no cross-contamination), and invalid values are rejected at option-parse time. // // [[RS]] = resource-heap stride, [[SS]] = sampler-heap stride (per-RUN via -D). From 3a83bfa890918ecef998b3d6e1345025ff7c8bc1 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Tue, 3 Nov 2026 09:08:12 -0800 Subject: [PATCH 10/12] improved documentation --- docs/ReleaseNotes.md | 13 +++--- docs/SPIR-V.rst | 43 +++++++++++-------- include/dxc/Support/HLSLOptions.td | 4 +- include/dxc/Support/SPIRVOptions.h | 4 +- lib/DxcSupport/HLSLOptions.cpp | 7 +-- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 9 ++-- ...t.acceleration-structure.stride.error.hlsl | 13 +++--- ...descriptorheap.ext.stride-cli-permute.hlsl | 14 +++--- .../sm6_6.descriptorheap.ext.stride-cli.hlsl | 6 +-- 9 files changed, 62 insertions(+), 51 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 4b1c7f3347f..6cc5d7a4434 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -29,12 +29,13 @@ The included licenses apply to the following files: loaded from `ResourceDescriptorHeap`. Outside ray-tracing stages, list `-fspv-extension=SPV_KHR_ray_query`, `SPV_KHR_ray_tracing` or `SPV_NV_ray_tracing` so the resource heap stride covers acceleration structure - descriptors; otherwise compilation fails. -- Added `-fvk-resource-heap-stride` and `-fvk-sampler-heap-stride` to override - the `ArrayStride` of the descriptor heap runtime arrays emitted by - `-fspv-use-descriptor-heap`. The value must be a power of two in `[8, 256]` - and takes precedence over the default `OpConstantSizeOfEXT`-based stride. - Both flags require `-fspv-use-descriptor-heap` and are rejected otherwise. + descriptors; otherwise compilation fails, unless `-fvk-resource-heap-stride` + is given. +- Added `-fvk-resource-heap-stride` and `-fvk-sampler-heap-stride`, which + override the computed `ArrayStrideIdEXT` of the resource and sampler + descriptor heaps with a literal `ArrayStride`. The value must be a power of + two in `[8, 256]`. Both flags require `-spirv` and + `-fspv-use-descriptor-heap` and are rejected otherwise. Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 1113f7c05c3..7862ede893a 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2103,9 +2103,10 @@ The concrete descriptor type is selected at each heap access. For image, sampler, and texel buffer resources, DXC forms a runtime array of that descriptor type, decorates the array with a byte stride, and uses ``OpUntypedAccessChainKHR`` followed by ``OpLoad``. By default the stride is an -``ArrayStrideIdEXT`` decoration referencing a specialization constant; a literal -``ArrayStride`` is emitted only when the stride is overridden on the command -line (see `Descriptor heap array stride`_ below): +``ArrayStrideIdEXT`` decoration referencing a constant built from +``OpConstantSizeOfEXT``; a literal ``ArrayStride`` is emitted only when the +stride is overridden on the command line (see `Descriptor heap array stride`_ +below): .. code:: spirv @@ -2258,23 +2259,29 @@ of the listed extension (``RayQueryKHR``, ``RayTracingKHR`` or then ``SPV_KHR_ray_tracing``, then ``SPV_NV_ray_tracing``. A shader that is not a ray-tracing stage and heap-loads a ``RaytracingAccelerationStructure`` without such an extension is rejected, because its stride would not cover the -acceleration structure descriptor. - -The computed stride can be replaced with a fixed literal using -``-fvk-resource-heap-stride `` and ``-fvk-sampler-heap-stride ``, which -emit ``OpDecorate ArrayStride N`` on the resource and sampler heap -arrays respectively. ``N`` must be a power of two in the inclusive range -[8, 256], and both flags require ``-spirv`` and ``-fspv-use-descriptor-heap`` -(they are rejected otherwise, since they would have no effect). The -command-line override has the -highest precedence: when it is set for a heap, no ``ArrayStrideIdEXT`` is -emitted for that heap and no ``OpConstantSizeOfEXT`` is built for it. The two -flags are independent, so overriding one heap leaves the other on its computed -stride. +acceleration structure descriptor, unless ``-fvk-resource-heap-stride`` is given +(see below). + +The computed stride can be replaced with a literal using +``-fvk-resource-heap-stride `` and ``-fvk-sampler-heap-stride ``. They +emit ``OpDecorate ArrayStride N`` on every runtime array of the resource +heap and of the sampler heap respectively. ``N`` must be a power of two in the +inclusive range [8, 256]. Both flags require ``-spirv`` and +``-fspv-use-descriptor-heap``, and are rejected otherwise because they would +have no effect. + +For a heap with an override, no ``ArrayStrideIdEXT`` is emitted and no +``OpConstantSizeOfEXT`` is built. The flags are independent: the other heap +keeps its computed stride. The literal is not validated against the descriptor sizes of the target -implementation. A value smaller than the largest descriptor that may appear in -the heap produces out-of-bounds descriptor accesses at runtime. +implementation. It must match the stride at which the application lays out +descriptors in the heap, and be at least the size of the largest descriptor +that may appear in it; otherwise heap indices select the wrong descriptors or +read out of bounds. +``-fvk-resource-heap-stride`` also disables the acceleration structure check +above, so the stride must cover acceleration structure descriptors when the +shader loads them from the heap. HLSL Expressions ================ diff --git a/include/dxc/Support/HLSLOptions.td b/include/dxc/Support/HLSLOptions.td index 51913ae810d..0bde31edc6a 100644 --- a/include/dxc/Support/HLSLOptions.td +++ b/include/dxc/Support/HLSLOptions.td @@ -455,9 +455,9 @@ def fvk_bind_sampler_heap : MultiArg<["-"], "fvk-bind-sampler-heap", 2>, MetaVar def fvk_bind_counter_heap : MultiArg<["-"], "fvk-bind-counter-heap", 2>, MetaVarName<" ">, Group, Flags<[CoreOption, DriverOption]>, HelpText<"Specify Vulkan binding number and set number for the counter heap.">; def fvk_resource_heap_stride : Separate<["-"], "fvk-resource-heap-stride">, MetaVarName<"">, Group, Flags<[CoreOption, DriverOption]>, - HelpText<"Override the byte ArrayStride of the resource descriptor heap runtime array. Must be a power of 2 in [8, 256].">; + HelpText<"Override the resource descriptor heap array stride with a literal ArrayStride of bytes. Requires -spirv and -fspv-use-descriptor-heap. Must be a power of 2 in [8, 256].">; def fvk_sampler_heap_stride : Separate<["-"], "fvk-sampler-heap-stride">, MetaVarName<"">, Group, Flags<[CoreOption, DriverOption]>, - HelpText<"Override the byte ArrayStride of the sampler descriptor heap runtime array. Must be a power of 2 in [8, 256].">; + HelpText<"Override the sampler descriptor heap array stride with a literal ArrayStride of bytes. Requires -spirv and -fspv-use-descriptor-heap. Must be a power of 2 in [8, 256].">; // SPIRV Change Ends ////////////////////////////////////////////////////////////////////////////// diff --git a/include/dxc/Support/SPIRVOptions.h b/include/dxc/Support/SPIRVOptions.h index c7c8ce3c183..c12e6415736 100644 --- a/include/dxc/Support/SPIRVOptions.h +++ b/include/dxc/Support/SPIRVOptions.h @@ -111,8 +111,8 @@ struct SpirvCodeGenOptions { std::optional counterHeapBinding; // -fvk-resource-heap-stride / -fvk-sampler-heap-stride - // When set, a power of 2 in [8, 256] byte ArrayStride replaces the - // ArrayStrideIdEXT decoration that heap would otherwise carry. + // When set, every runtime array of that heap gets a literal ArrayStride of + // this many bytes (a power of 2 in [8, 256]) instead of ArrayStrideIdEXT. std::optional resourceHeapStride; std::optional samplerHeapStride; diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index 0b0048574dd..62cdf3ca11a 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -358,9 +358,10 @@ handleFixedBinding(const InputArgList &args, OptSpecifier id, } // Parses the single-integer descriptor-heap stride flag |id| in |args|. If -// present, validates that the value is a power of 2 in [8, 256] and stores it -// in |stride|. Returns true on success (including when the flag is absent). -// Returns false and writes to |errors| when the value is malformed or invalid, +// present, requires -spirv and -fspv-use-descriptor-heap, validates that the +// value is a power of 2 in [8, 256] and stores it in |stride|. Returns true on +// success (including when the flag is absent). Returns false and writes to +// |errors| if a requirement is not met or the value is malformed or invalid, // using |name| as the pretty flag name. static bool handleHeapStride(const InputArgList &args, OptSpecifier id, std::optional *stride, diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 05f9b5faca2..650f5982b87 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -7358,9 +7358,8 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, // The stride was decided without an acceleration structure term (not a // ray-tracing stage, no ray extension listed). Reject the load: the // heap would be indexed with a stride that may be too narrow. - // Exception: -fvk-resource-heap-stride supplies a literal stride that - // bypasses the spec-constant machinery entirely, so the user has - // explicitly taken ownership of the stride value. + // Exception: with -fvk-resource-heap-stride the stride is a literal + // chosen by the user, so there is no computed stride to widen. if (isRaytracingAccelerationStructure(resourceType) && !spvBuilder.resourceHeapStrideIncludesAccelStruct() && !spirvOptions.resourceHeapStride.has_value()) { @@ -7388,7 +7387,7 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, const SpirvType *handleType = lowerTypeVisitor.lowerType(resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - // Images and samplers may come from either heap; pick the right stride. + // The stride belongs to the heap being indexed. const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType( handleType, isSamplerDescriptorHeap(decl)); SpirvUntypedAccessChainKHR *untypedAccessChainPtr = @@ -9839,7 +9838,7 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { const SpirvType * SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, bool onSamplerHeap) { - // -fvk-{resource,sampler}-heap-stride has the highest precedence: the array + // -fvk-{resource,sampler}-heap-stride replaces the computed stride: the array // carries a literal ArrayStride and no ArrayStrideIdEXT. const std::optional &cliStride = onSamplerHeap ? spirvOptions.samplerHeapStride diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl index 82411fa3514..07ce4e6c426 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl @@ -8,10 +8,11 @@ // RUN: -fspv-extension=SPV_KHR_ray_query \ // RUN: -spirv %s | FileCheck %s --check-prefix=OK -// Verify -fvk-resource-heap-stride suppresses the diagnostic: the stride is -// user-supplied so no ray-extension pre-scan is needed to widen it. -// The AS runtime array must carry a literal ArrayStride (not ArrayStrideIdEXT) -// and the compiler must not emit OpConstantSizeOfEXT for the AS placeholder. +// Verify -fvk-resource-heap-stride suppresses the diagnostic: the stride is a +// user-supplied literal, so there is no computed stride to widen. The +// acceleration structure runtime array must carry a literal ArrayStride (not +// ArrayStrideIdEXT) and no OpConstantSizeOfEXT may be emitted for the +// acceleration structure type. // There is deliberately no -fspv-extension list: SPV_KHR_ray_query would make // the pre-scan widen the stride by itself, so compiling would not depend on the // override. The extensions this shader needs are enabled by default. @@ -44,8 +45,8 @@ // OK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR // OK-DAG: OpConstantSizeOfEXT %uint %[[Accel]] -// AS runtime array carries the user-supplied literal stride (not ArrayStrideIdEXT), -// and no OpConstantSizeOfEXT is emitted for the AS placeholder. +// The acceleration structure runtime array carries the literal stride, and no +// OpConstantSizeOfEXT is emitted for the acceleration structure type. // CLI-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR // CLI-DAG: %[[AccelArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] // CLI-DAG: OpDecorate %[[AccelArr]] ArrayStride 256 diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl index 76f90bf1702..551d10a0cc4 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl @@ -1,5 +1,7 @@ -// Verifies: every legal CLI stride value flows independently to each heap's literal ArrayStride -// (full 6x6 cross product, RS!=SS proves no cross-contamination), and invalid values are rejected at option-parse time. +// Verifies: every legal CLI stride value reaches the literal ArrayStride of its +// own heap (full 6x6 cross product; the RS != SS cases show the heaps do not +// affect each other), and invalid values or missing prerequisite flags are +// rejected at option-parse time. // // [[RS]] = resource-heap stride, [[SS]] = sampler-heap stride (per-RUN via -D). @@ -56,15 +58,15 @@ // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 %s 2>&1 | FileCheck %s --check-prefix=NOSPIRVRS // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 16 %s 2>&1 | FileCheck %s --check-prefix=NOSPIRVSS -// ---- Flag without the native heap (plain -spirv, or the emulated heap): the override would be ignored, so it is rejected ---- +// ---- Flag without the native heap (plain -spirv or the emulated heap): rejected, since it would have no effect ---- // RUN: not %dxc -T cs_6_6 -E main -fvk-resource-heap-stride 128 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPRS // RUN: not %dxc -T cs_6_6 -E main -fvk-sampler-heap-stride 16 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPSS // RUN: not %dxc -T cs_6_6 -E main -fspv-use-emulated-heap -fvk-resource-heap-stride 128 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPRS // RUN: not %dxc -T cs_6_6 -E main -fspv-use-emulated-heap -fvk-sampler-heap-stride 16 -spirv %s 2>&1 | FileCheck %s --check-prefix=NOHEAPSS -// Bind each heap's runtime array to its element type so the strides are checked -// independently. The resource Texture2D image array carries [[RS]]; the sampler -// array carries [[SS]]. +// Capture each heap's runtime array by its element type so the two strides are +// checked independently. The Texture2D array (resource heap) must carry [[RS]]; +// the sampler array (sampler heap) must carry [[SS]]. // CHECK-DAG: %[[TexType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown // CHECK-DAG: %[[SampType:[a-zA-Z0-9_]+]] = OpTypeSampler // CHECK-DAG: %[[TexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexType]]{{$}} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl index 789747f5e42..27968e379f2 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl @@ -3,9 +3,9 @@ // RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s --check-prefix=SS-ONLY // Verifies: -fvk-resource-heap-stride / -fvk-sampler-heap-stride emit a literal -// ArrayStride and suppress the default OpConstantSizeOfEXT stride. -// Single-flag cases (RS-ONLY / SS-ONLY) verify the unflagged heap falls back to -// ArrayStrideIdEXT rather than picking up the other heap's literal stride. +// ArrayStride for their heap instead of ArrayStrideIdEXT. +// The single-flag cases (RS-ONLY / SS-ONLY) verify that the other heap keeps its +// computed ArrayStrideIdEXT instead of taking the literal stride. // CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 // CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 From c74bad8841b831a08c8b40b643be89723d65d18a Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Tue, 3 Nov 2026 09:27:30 -0800 Subject: [PATCH 11/12] review: check absence of computed stride over the whole output --- ...t.acceleration-structure.stride.error.hlsl | 15 ++++-- .../sm6_6.descriptorheap.ext.stride-cli.hlsl | 50 +++++++++++++++---- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl index 07ce4e6c426..a2c1d2ddecd 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl @@ -19,7 +19,9 @@ // RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ // RUN: -fspv-target-env=vulkan1.3 \ // RUN: -fvk-resource-heap-stride 256 \ -// RUN: -spirv %s | FileCheck %s --check-prefix=CLI +// RUN: -spirv %s > %t +// RUN: FileCheck %s --check-prefix=CLI < %t +// RUN: FileCheck %s --check-prefix=ABSENT < %t // Verifies: an acceleration structure loaded from ResourceDescriptorHeap is // rejected when the resource-heap stride was not widened to include the @@ -45,13 +47,16 @@ // OK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR // OK-DAG: OpConstantSizeOfEXT %uint %[[Accel]] -// The acceleration structure runtime array carries the literal stride, and no -// OpConstantSizeOfEXT is emitted for the acceleration structure type. +// The acceleration structure runtime array carries the literal stride. // CLI-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR // CLI-DAG: %[[AccelArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] // CLI-DAG: OpDecorate %[[AccelArr]] ArrayStride 256 -// CLI-NOT: OpDecorateId %[[AccelArr]] ArrayStrideIdEXT -// CLI-NOT: OpConstantSizeOfEXT %uint %[[Accel]] + +// No computed stride may appear anywhere in the module. This is a separate +// check, because a NOT after the last DAG match only covers the rest of the +// output, and the decorations come before the types. +// ABSENT-NOT: ArrayStrideIdEXT +// ABSENT-NOT: OpConstantSizeOfEXT RWBuffer output : register(u0); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl index 27968e379f2..40c987e5ebf 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl @@ -1,21 +1,53 @@ -// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 16 -spirv %s > %t +// RUN: FileCheck %s < %t +// RUN: FileCheck %s --check-prefix=ABSENT < %t // RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -spirv %s | FileCheck %s --check-prefix=RS-ONLY // RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s --check-prefix=SS-ONLY // Verifies: -fvk-resource-heap-stride / -fvk-sampler-heap-stride emit a literal -// ArrayStride for their heap instead of ArrayStrideIdEXT. +// ArrayStride for their heap instead of ArrayStrideIdEXT. The shader loads a +// buffer and an image from the resource heap and a sampler from the sampler +// heap, and each runtime array is checked by name. // The single-flag cases (RS-ONLY / SS-ONLY) verify that the other heap keeps its // computed ArrayStrideIdEXT instead of taking the literal stride. -// CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 -// CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 -// CHECK-NOT: ArrayStrideIdEXT +// Both flags: all three arrays carry their heap's literal stride. +// CHECK-DAG: %[[BufType:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer{{$}} +// CHECK-DAG: %[[ImgType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SampType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[BufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufType]]{{$}} +// CHECK-DAG: %[[ImgArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[ImgType]]{{$}} +// CHECK-DAG: %[[SampArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SampType]]{{$}} +// CHECK-DAG: OpDecorate %[[BufArray]] ArrayStride 128 +// CHECK-DAG: OpDecorate %[[ImgArray]] ArrayStride 128 +// CHECK-DAG: OpDecorate %[[SampArray]] ArrayStride 16 -// RS-ONLY-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 -// RS-ONLY: ArrayStrideIdEXT +// No computed stride may appear anywhere in the module. This is a separate +// check, because a NOT after the last DAG match only covers the rest of the +// output, and the decorations come before the types. +// ABSENT-NOT: ArrayStrideIdEXT -// SS-ONLY-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 -// SS-ONLY: ArrayStrideIdEXT +// Resource heap only: the sampler heap keeps its computed stride. +// RS-ONLY-DAG: %[[BufType:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer{{$}} +// RS-ONLY-DAG: %[[ImgType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// RS-ONLY-DAG: %[[SampType:[a-zA-Z0-9_]+]] = OpTypeSampler +// RS-ONLY-DAG: %[[BufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufType]]{{$}} +// RS-ONLY-DAG: %[[ImgArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[ImgType]]{{$}} +// RS-ONLY-DAG: %[[SampArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SampType]]{{$}} +// RS-ONLY-DAG: OpDecorate %[[BufArray]] ArrayStride 128 +// RS-ONLY-DAG: OpDecorate %[[ImgArray]] ArrayStride 128 +// RS-ONLY-DAG: OpDecorateId %[[SampArray]] ArrayStrideIdEXT + +// Sampler heap only: the resource heap keeps its computed stride. +// SS-ONLY-DAG: %[[BufType:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer{{$}} +// SS-ONLY-DAG: %[[ImgType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// SS-ONLY-DAG: %[[SampType:[a-zA-Z0-9_]+]] = OpTypeSampler +// SS-ONLY-DAG: %[[BufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufType]]{{$}} +// SS-ONLY-DAG: %[[ImgArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[ImgType]]{{$}} +// SS-ONLY-DAG: %[[SampArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SampType]]{{$}} +// SS-ONLY-DAG: OpDecorateId %[[BufArray]] ArrayStrideIdEXT +// SS-ONLY-DAG: OpDecorateId %[[ImgArray]] ArrayStrideIdEXT +// SS-ONLY-DAG: OpDecorate %[[SampArray]] ArrayStride 16 RWByteAddressBuffer outputBytes : register(u0); From cd16d03d4a78e4ac8b16b3bcecba83400f2e4989 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Tue, 3 Nov 2026 14:32:16 -0800 Subject: [PATCH 12/12] review: integrate desc heap stride flag into AS documentation --- docs/ReleaseNotes.md | 9 ++++----- docs/SPIR-V.rst | 12 ++++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 6cc5d7a4434..360a612c387 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -26,11 +26,10 @@ The included licenses apply to the following files: `ConstantBuffer`, `TextureBuffer`, and RW variants) via `SPV_KHR_untyped_pointers`. Requires `-fspv-target-env=vulkan1.3`. - Extended `-fspv-use-descriptor-heap` to support `RaytracingAccelerationStructure` - loaded from `ResourceDescriptorHeap`. Outside ray-tracing stages, list - `-fspv-extension=SPV_KHR_ray_query`, `SPV_KHR_ray_tracing` or - `SPV_NV_ray_tracing` so the resource heap stride covers acceleration structure - descriptors; otherwise compilation fails, unless `-fvk-resource-heap-stride` - is given. + loaded from `ResourceDescriptorHeap`. The stride that covers acceleration + structure descriptors is calculated automatically in ray-tracing stages or + when a ray-tracing extension is explicitly enabled, or can be provided with + `-fvk-resource-heap-stride`; otherwise compilation fails. - Added `-fvk-resource-heap-stride` and `-fvk-sampler-heap-stride`, which override the computed `ArrayStrideIdEXT` of the resource and sampler descriptor heaps with a literal `ArrayStride`. The value must be a power of diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 7862ede893a..85807b201cc 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2240,9 +2240,10 @@ types the shader actually uses; a module will normally contain both the placeholder type and the distinct image types its heap accesses lower to. The stride is a property of the heap layout, not of the shader's loads. DXC -decides it before translating any function, from the shader stage and the -``-fspv-extension`` list. When acceleration structure descriptors may be on the -resource heap, the formula expands to a three-way max +decides it before translating any function, either from explicit user input +(``-fvk-resource-heap-stride`` and ``-fvk-sampler-heap-stride``, see below) or +by calculating it from the shader stage and the ``-fspv-extension`` list. When +acceleration structure descriptors may be on the resource heap, the formula expands to a three-way max ``max(max(sizeof(image_descriptor), sizeof(buffer_descriptor)), sizeof(acceleration_structure))``. A ray-tracing shader that only heap-loads a texture still gets the three-way max. DXC adds the acceleration structure term when either @@ -2258,9 +2259,8 @@ of the listed extension (``RayQueryKHR``, ``RayTracingKHR`` or ``RayTracingNV``); if several are listed, ``SPV_KHR_ray_query`` is preferred, then ``SPV_KHR_ray_tracing``, then ``SPV_NV_ray_tracing``. A shader that is not a ray-tracing stage and heap-loads a ``RaytracingAccelerationStructure`` -without such an extension is rejected, because its stride would not cover the -acceleration structure descriptor, unless ``-fvk-resource-heap-stride`` is given -(see below). +without such an extension or ``-fvk-resource-heap-stride`` is rejected, because +its stride would not cover the acceleration structure descriptor. The computed stride can be replaced with a literal using ``-fvk-resource-heap-stride `` and ``-fvk-sampler-heap-stride ``. They