From cbcae3883c0ab9c9633ec6b03c489698f186e045 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:50 -0700 Subject: [PATCH 1/4] [SPIR-V] Add SPV_EXT_descriptor_heap + SPV_KHR_untyped_pointers codegen Building off of #8281, this commit adds a native lowering via SPV_EXT_descriptor_heap and SPV_KHR_untyped_pointers. ResourceDescriptorHeap and SamplerDescriptorHeap are lowered to untyped variables decorated with ResourceHeapEXT and SamplerHeapEXT. Each heap access emits OpUntypedAccessChainKHR into a runtime array of the appropriate descriptor type. Buffer-like resources (StructuredBuffer, ByteAddressBuffer, ConstantBuffer, TextureBuffer) use OpTypeBufferEXT and OpBufferPointerEXT; image and sampler resources use OpLoad. Interlocked operations on RWTexture use OpUntypedImageTexelPointerEXT. Requires -fspv-target-env=vulkan1.3. Assisted-by: Claude. --- docs/SPIR-V.rst | 87 +++- .../clang/include/clang/SPIRV/SpirvBuilder.h | 2 +- .../clang/include/clang/SPIRV/SpirvContext.h | 4 + .../include/clang/SPIRV/SpirvInstruction.h | 12 + tools/clang/lib/SPIRV/CapabilityVisitor.cpp | 6 +- tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 6 + tools/clang/lib/SPIRV/DeclResultIdMapper.h | 4 +- tools/clang/lib/SPIRV/EmitVisitor.cpp | 1 + tools/clang/lib/SPIRV/LowerTypeVisitor.cpp | 6 + tools/clang/lib/SPIRV/SpirvBuilder.cpp | 3 +- tools/clang/lib/SPIRV/SpirvContext.cpp | 11 + tools/clang/lib/SPIRV/SpirvEmitter.cpp | 458 ++++++++++++++++-- tools/clang/lib/SPIRV/SpirvEmitter.h | 131 +++++ tools/clang/lib/SPIRV/SpirvInstruction.cpp | 8 +- .../resource-heap-ext-texture.hlsl | 59 --- ...scriptorheap.ext.append-consume.error.hlsl | 19 + .../sm6_6.descriptorheap.ext.buffer.hlsl | 57 +++ ...orheap.ext.constant-buffer-assignment.hlsl | 78 +++ ...iptorheap.ext.constant-texture-buffer.hlsl | 51 ++ ....descriptorheap.ext.counter-ops.error.hlsl | 24 + ..._6.descriptorheap.ext.discarded.error.hlsl | 14 + ..._6.descriptorheap.ext.function-params.hlsl | 97 ++++ .../sm6_6.descriptorheap.ext.gather.hlsl | 42 ++ .../sm6_6.descriptorheap.ext.groupshared.hlsl | 35 ++ .../sm6_6.descriptorheap.ext.load-offset.hlsl | 27 ++ .../sm6_6.descriptorheap.ext.mixed-bound.hlsl | 54 +++ .../sm6_6.descriptorheap.ext.nonuniform.hlsl | 30 ++ ...escriptorheap.ext.rwbyteaddressbuffer.hlsl | 21 + ....descriptorheap.ext.rwtexture-atomics.hlsl | 53 ++ ...6_6.descriptorheap.ext.rwtexture-dims.hlsl | 53 ++ ...6.descriptorheap.ext.sample-grad-bias.hlsl | 40 ++ ...descriptorheap.ext.sampler-comparison.hlsl | 30 ++ ...m6_6.descriptorheap.ext.static-global.hlsl | 64 +++ ...ptorheap.ext.structured-buffer-atomic.hlsl | 28 ++ ...sm6_6.descriptorheap.ext.texture-dims.hlsl | 54 +++ .../sm6_6.descriptorheap.ext.texture-ms.hlsl | 33 ++ ...orheap.ext.texture-sampler-assignment.hlsl | 77 +++ .../sm6_6.descriptorheap.ext.texture.hlsl | 34 ++ .../sm6_6.descriptorheap.ext.texturecube.hlsl | 46 ++ ...m6_6.descriptorheap.ext.typed-formats.hlsl | 59 +++ .../unittests/SPIRV/SpirvContextTest.cpp | 3 + 41 files changed, 1818 insertions(+), 103 deletions(-) delete mode 100644 tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 423262e15d..70f1f934c3 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -336,6 +336,8 @@ Supported extensions * SPV_KHR_float_controls * SPV_NV_shader_subgroup_partitioned * SPV_KHR_quad_control +* SPV_KHR_untyped_pointers +* SPV_EXT_descriptor_heap Vulkan specific attributes -------------------------- @@ -1993,10 +1995,14 @@ responsibility to provide proper numbers and avoid binding overlaps. ResourceDescriptorHeaps & SamplerDescriptorHeaps ------------------------------------------------ -The SPIR-V backend supported SM6.6 resource heaps, using 2 extensions: +By default, the SPIR-V backend supports SM6.6 resource heaps by emulating the +heaps with descriptor-indexing runtime arrays, using 2 extensions: + - `SPV_EXT_descriptor_indexing` - `VK_EXT_mutable_descriptor_type` +This is also the behavior selected by ``-fspv-use-emulated-heap``. + Each type loaded from a heap is considered to be an unbounded RuntimeArray bound to the descriptor set 0. @@ -2074,6 +2080,85 @@ Bindings & sets associated with each heap can be explicitly set using: - `-fvk-bind-counter-heap `: Specify Vulkan binding number and set number for the counter heap. +Native descriptor heap extension lowering +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When ``-fspv-use-descriptor-heap`` is specified, DXC lowers +``ResourceDescriptorHeap`` and ``SamplerDescriptorHeap`` through +``SPV_EXT_descriptor_heap`` instead of the default emulated heap path. This +also requires ``SPV_KHR_untyped_pointers`` and ``-fspv-target-env=vulkan1.3`` +(targeting a lower environment is an error), and a SPIRV-Headers / SPIRV-Tools +build that defines these extensions. The emitted module declares the heap +objects as untyped variables in ``UniformConstant`` storage class: + +.. code:: spirv + + %uptr_uc = OpTypeUntypedPointerKHR UniformConstant + %resource_heap = OpUntypedVariableKHR %uptr_uc UniformConstant + %sampler_heap = OpUntypedVariableKHR %uptr_uc UniformConstant + OpDecorate %resource_heap BuiltIn ResourceHeapEXT + OpDecorate %sampler_heap BuiltIn SamplerHeapEXT + +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 and decorates the array with a byte ``ArrayStride``, then uses +``OpUntypedAccessChainKHR`` followed by ``OpLoad``: + +.. code:: spirv + + %image_type = OpTypeImage %float 2D 2 0 0 1 Unknown + %image_array = OpTypeRuntimeArray %image_type + OpDecorate %image_array ArrayStride 64 + %descriptor = OpUntypedAccessChainKHR %uptr_uc %image_array %resource_heap %index + %image = OpLoad %image_type %descriptor + +For buffer-like resources, DXC uses ``OpTypeBufferEXT`` as the descriptor type +and ``OpBufferPointerEXT`` to recover the pointer to the buffer data. The +descriptor storage class matches the recovered buffer pointer storage class; for +example, ``ConstantBuffer`` uses ``Uniform`` and ``TextureBuffer`` uses +``StorageBuffer``: + +.. code:: spirv + + %buffer_type = OpTypeBufferEXT Uniform + %buffer_array = OpTypeRuntimeArray %buffer_type + OpDecorate %buffer_array ArrayStride 64 + %descriptor = OpUntypedAccessChainKHR %uptr_uc %buffer_array %resource_heap %index + %buffer_ptr = OpBufferPointerEXT %_ptr_Uniform_type_BufferData %descriptor + +For ``RWTexture`` resources loaded from ``ResourceDescriptorHeap``, interlocked +operations that need a texel pointer use ``OpUntypedImageTexelPointerEXT``. +The image descriptor pointer produced by ``OpUntypedAccessChainKHR`` is passed +directly to the texel-pointer instruction instead of first storing the image +handle into a function-scope image variable: + +.. code:: spirv + + %image_type = OpTypeImage %uint 2D 2 0 0 2 R32ui + %image_array = OpTypeRuntimeArray %image_type + %descriptor = OpUntypedAccessChainKHR %uptr_uc %image_array %resource_heap %index + %uptr_image = OpTypeUntypedPointerKHR Image + %texel_ptr = OpUntypedImageTexelPointerEXT %uptr_image %image_type %descriptor %coord %sample + %old = OpAtomicIAdd %uint %texel_ptr %scope %semantics %value + +This path supports texture, RWTexture, sampler, Buffer/RWBuffer, +StructuredBuffer/RWStructuredBuffer without associated counter operations, +ByteAddressBuffer/RWByteAddressBuffer, ConstantBuffer, and TextureBuffer heap +loads, including direct field and array-element accesses for +``ConstantBuffer`` and ``TextureBuffer``. ``NonUniformResourceIndex`` is +accepted but the ``NonUniform`` decoration is not emitted on +``OpUntypedAccessChainKHR`` or the loaded value; ``SPV_EXT_descriptor_heap`` +deprecates the ``NonUniform`` decoration for heap accesses. + +Append/consume structured buffers and UAV counter heap lowering are not +supported by the native descriptor heap path yet. Those forms should continue +to use the default emulated heap lowering, or DXC will emit a diagnostic for +unsupported append/consume structured-buffer heap loads. Heap-loaded +``RWStructuredBuffer`` resources are supported for ordinary data access, but +associated counter operations such as ``IncrementCounter`` and +``DecrementCounter`` emit a diagnostic because the native descriptor heap path +does not recover an associated counter descriptor. + HLSL Expressions ================ diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index dba103207c..ae8add20a9 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -277,7 +277,7 @@ class SpirvBuilder { /// \brief Creates an OpUntypedImageTexelPointerEXT SPIR-V instruction with /// the given parameters. SpirvUntypedImageTexelPointerEXT *createUntypedImageTexelPointerEXT( - QualType resultType, SpirvInstruction *image, + QualType resultType, const SpirvType *imageType, SpirvInstruction *image, SpirvInstruction *coordinate, SpirvInstruction *sample, SourceLocation); /// \brief Creates an OpConverPtrToU SPIR-V instruction with the given diff --git a/tools/clang/include/clang/SPIRV/SpirvContext.h b/tools/clang/include/clang/SPIRV/SpirvContext.h index 97a62d4af8..8a49f961ca 100644 --- a/tools/clang/include/clang/SPIRV/SpirvContext.h +++ b/tools/clang/include/clang/SPIRV/SpirvContext.h @@ -298,6 +298,7 @@ class SpirvContext { spv::StorageClass); const UntypedPointerKHRType *getUntypedPointerKHRType(spv::StorageClass sc); + const BufferEXTType *getBufferEXTType(spv::StorageClass sc); FunctionType *getFunctionType(const SpirvType *ret, llvm::ArrayRef param); @@ -536,6 +537,9 @@ class SpirvContext { llvm::DenseMap untypedPointerKHRTypes; + llvm::DenseMap + bufferEXTTypes; llvm::MapVector forwardPointerTypes; llvm::MapVector forwardReferences; llvm::DenseSet functionTypes; diff --git a/tools/clang/include/clang/SPIRV/SpirvInstruction.h b/tools/clang/include/clang/SPIRV/SpirvInstruction.h index 39cff251c6..d83d8f7294 100644 --- a/tools/clang/include/clang/SPIRV/SpirvInstruction.h +++ b/tools/clang/include/clang/SPIRV/SpirvInstruction.h @@ -2065,6 +2065,7 @@ class SpirvImageTexelPointer : public SpirvInstruction { class SpirvUntypedImageTexelPointerEXT : public SpirvInstruction { public: SpirvUntypedImageTexelPointerEXT(QualType resultType, SourceLocation loc, + const SpirvType *imageType, SpirvInstruction *image, SpirvInstruction *coordinate, SpirvInstruction *sample); @@ -2078,11 +2079,22 @@ class SpirvUntypedImageTexelPointerEXT : public SpirvInstruction { bool invokeVisitor(Visitor *v) override; + const SpirvType *getImageType() const { return imageType; } SpirvInstruction *getImage() const { return image; } SpirvInstruction *getCoordinate() const { return coordinate; } SpirvInstruction *getSample() const { return sample; } + void replaceOperand( + llvm::function_ref remapOp, + bool inEntryFunctionWrapper) override { + // imageType is a compile-time SpirvType, not an SSA operand. + image = remapOp(image); + coordinate = remapOp(coordinate); + sample = remapOp(sample); + } + private: + const SpirvType *imageType; SpirvInstruction *image; SpirvInstruction *coordinate; SpirvInstruction *sample; diff --git a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp index 9f012d24e8..c48f898a28 100644 --- a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp +++ b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp @@ -955,8 +955,10 @@ bool CapabilityVisitor::visit(SpirvModule *, Visitor::Phase phase) { {spv::Capability::QuadControlKHR}); if (spvOptions.useDescriptorHeap) { - addExtension(Extension::EXT_descriptor_heap, "DescriptorHeap", {}); - addExtension(Extension::KHR_untyped_pointers, "DescriptorHeap", {}); + const llvm::StringRef feature = "DescriptorHeap"; + featureManager.requestTargetEnv(SPV_ENV_VULKAN_1_3, feature, {}); + addExtension(Extension::EXT_descriptor_heap, feature, {}); + addExtension(Extension::KHR_untyped_pointers, feature, {}); addCapability(spv::Capability::DescriptorHeapEXT); } diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 1a1078bf0b..62e5ff1e3e 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -1136,6 +1136,12 @@ DeclResultIdMapper::createFnVar(const VarDecl *var, return varInstr; } +void DeclResultIdMapper::registerFnVarAlias(const VarDecl *var, + SpirvInstruction *varInstr) { + if (varInstr) + registerVariableForDecl(var, createDeclSpirvInfo(varInstr)); +} + SpirvDebugGlobalVariable *DeclResultIdMapper::createDebugGlobalVariable( SpirvVariable *var, const QualType &type, const SourceLocation &loc, const StringRef &name) { diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.h b/tools/clang/lib/SPIRV/DeclResultIdMapper.h index 6e8177edf2..d6ae75e8fa 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.h +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.h @@ -10,7 +10,6 @@ #ifndef LLVM_CLANG_LIB_SPIRV_DECLRESULTIDMAPPER_H #define LLVM_CLANG_LIB_SPIRV_DECLRESULTIDMAPPER_H -#include #include #include "dxc/Support/SPIRVOptions.h" @@ -286,6 +285,9 @@ class DeclResultIdMapper { SpirvVariable *createFnVar(const VarDecl *var, llvm::Optional init); + /// \brief Registers a function-scope alias to an existing instruction. + void registerFnVarAlias(const VarDecl *var, SpirvInstruction *varInstr); + /// \brief Creates a file-scope variable and returns its instruction. SpirvVariable *createFileVar(const VarDecl *var, llvm::Optional init); diff --git a/tools/clang/lib/SPIRV/EmitVisitor.cpp b/tools/clang/lib/SPIRV/EmitVisitor.cpp index fcdcf5bcd0..75d081ee62 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.cpp +++ b/tools/clang/lib/SPIRV/EmitVisitor.cpp @@ -820,6 +820,7 @@ bool EmitVisitor::visit(SpirvUntypedImageTexelPointerEXT *inst) { initInstruction(inst); curInst.push_back(inst->getResultTypeId()); curInst.push_back(getOrAssignResultId(inst)); + curInst.push_back(typeHandler.emitType(inst->getImageType())); curInst.push_back(getOrAssignResultId(inst->getImage())); curInst.push_back( getOrAssignResultId(inst->getCoordinate())); diff --git a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp index 4c06cd4113..2d9abfb1c9 100644 --- a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp +++ b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp @@ -224,6 +224,12 @@ bool LowerTypeVisitor::visitInstruction(SpirvInstruction *instr) { instr->setResultType(pointerType); break; } + case spv::Op::OpUntypedImageTexelPointerEXT: { + instr->setResultType( + spvContext.getUntypedPointerKHRType(spv::StorageClass::Image)); + instr->setStorageClass(spv::StorageClass::Image); + break; + } // Sparse image operations return a sparse residency struct. case spv::Op::OpImageSparseSampleImplicitLod: case spv::Op::OpImageSparseSampleExplicitLod: diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index f61d61ed71..9a48ed5f85 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -533,13 +533,14 @@ SpirvImageTexelPointer *SpirvBuilder::createImageTexelPointer( SpirvUntypedImageTexelPointerEXT * SpirvBuilder::createUntypedImageTexelPointerEXT(QualType resultType, + const SpirvType *imageType, SpirvInstruction *image, SpirvInstruction *coordinate, SpirvInstruction *sample, SourceLocation loc) { assert(insertPoint && "null insert point"); auto *instruction = new (context) SpirvUntypedImageTexelPointerEXT( - resultType, loc, image, coordinate, sample); + resultType, loc, imageType, image, coordinate, sample); insertPoint->addInstruction(instruction); return instruction; } diff --git a/tools/clang/lib/SPIRV/SpirvContext.cpp b/tools/clang/lib/SPIRV/SpirvContext.cpp index c28da78f9d..a3397d74b4 100644 --- a/tools/clang/lib/SPIRV/SpirvContext.cpp +++ b/tools/clang/lib/SPIRV/SpirvContext.cpp @@ -65,6 +65,9 @@ SpirvContext::~SpirvContext() { for (auto *npaType : nodePayloadArrayTypes) npaType->~NodePayloadArrayType(); + for (auto &pair : bufferEXTTypes) + pair.second->~BufferEXTType(); + for (auto *fnType : functionTypes) fnType->~FunctionType(); @@ -402,6 +405,14 @@ const StructType *SpirvContext::getByteAddressBufferType(bool isWritable) { !isWritable, StructInterfaceType::StorageBuffer); } +const BufferEXTType *SpirvContext::getBufferEXTType(spv::StorageClass sc) { + auto found = bufferEXTTypes.find(sc); + if (found != bufferEXTTypes.end()) + return found->second; + + return bufferEXTTypes[sc] = new (this) BufferEXTType(sc); +} + const StructType *SpirvContext::getACSBufferCounterType() { // Create int32. const auto *int32Type = getSIntType(32); diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 288b926f24..d967520fe2 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -1227,6 +1227,12 @@ SpirvInstruction *SpirvEmitter::doExpr(const Expr *expr, auto *decl = declRefExpr->getDecl(); if (isImplicitVarDeclInVkNamespace(declRefExpr->getDecl())) { result = doExpr(cast(decl)->getInit()); + } else if (const auto *varDecl = dyn_cast(decl)) { + if (auto *alias = + emitDescriptorHeapBufferPointer(varDecl, expr->getLocStart())) + result = alias; + else + result = declIdMapper.getDeclEvalInfo(decl, expr->getLocStart(), range); } else { result = declIdMapper.getDeclEvalInfo(decl, expr->getLocStart(), range); } @@ -2020,6 +2026,22 @@ void SpirvEmitter::doEnumDecl(const EnumDecl *decl) { declIdMapper.createEnumConstant(*it); } +bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, + const Expr *init) { + if (!spirvOptions.useDescriptorHeap || !init || + !isDescriptorHeap(init->IgnoreParenCasts())) + return false; + + if (isConstantTextureBuffer(decl->getType()) || + isAKindOfStructuredOrByteBuffer(decl->getType())) { + (void)doExpr(init->IgnoreParenCasts()); + tryToAssignDescriptorHeapBufferAlias(decl, init); + return true; + } + + return false; +} + void SpirvEmitter::doVarDecl(const VarDecl *decl) { if (!validateVKAttributes(decl)) return; @@ -2209,8 +2231,11 @@ void SpirvEmitter::doVarDecl(const VarDecl *decl) { declIdMapper.tryToCreateConstantVar(decl)) return; var = declIdMapper.createFileVar(decl, llvm::None); - } else + } else { + if (tryToCreateDescriptorHeapAlias(decl, decl->getInit())) + return; var = declIdMapper.createFnVar(decl, llvm::None); + } // Emit OpStore to initialize the variable // TODO: revert back to use OpVariable initializer @@ -2233,6 +2258,7 @@ void SpirvEmitter::doVarDecl(const VarDecl *decl) { spvBuilder.createStore(var, constInit, loc, range); } else { storeValue(var, loadIfGLValue(init), decl->getType(), loc, range); + tryToAssignDescriptorHeapImageAlias(decl, init); } // Update counter variable associated with local variables @@ -3123,6 +3149,33 @@ SpirvEmitter::doArraySubscriptExpr(const ArraySubscriptExpr *expr, return loadVal; } +llvm::Optional +SpirvEmitter::tryToAssignToDescriptorHeapBuffer( + const BinaryOperator *assignExpr) { + if (!spirvOptions.useDescriptorHeap) + return llvm::None; + + const QualType lhsType = assignExpr->getLHS()->getType(); + if (!isConstantTextureBuffer(lhsType) && + !isAKindOfStructuredOrByteBuffer(lhsType)) + return llvm::None; + + const Expr *rhsValue = assignExpr->getRHS()->IgnoreParenCasts(); + if (!isDescriptorHeap(rhsValue)) + return llvm::None; + + (void)doExpr(rhsValue); + if (!tryToAssignDescriptorHeapBufferAlias(assignExpr->getLHS(), + assignExpr->getRHS())) + return llvm::None; + + const auto *decl = + dyn_cast_or_null(getReferencedDef(assignExpr->getLHS())); + if (!decl) + return static_cast(nullptr); + return emitDescriptorHeapBufferPointer(decl, assignExpr->getExprLoc()); +} + SpirvInstruction *SpirvEmitter::doBinaryOperator(const BinaryOperator *expr) { const auto opcode = expr->getOpcode(); @@ -3132,7 +3185,14 @@ SpirvInstruction *SpirvEmitter::doBinaryOperator(const BinaryOperator *expr) { // Update counter variable associated with lhs of assignments tryToAssignCounterVar(expr->getLHS(), expr->getRHS()); - return processAssignment(expr->getLHS(), loadIfGLValue(expr->getRHS()), + if (llvm::Optional aliasResult = + tryToAssignToDescriptorHeapBuffer(expr)) + return aliasResult.getValue(); + + auto *rhs = loadIfGLValue(expr->getRHS()); + tryToAssignDescriptorHeapImageAlias(expr->getLHS(), expr->getRHS()); + + return processAssignment(expr->getLHS(), rhs, /*isCompoundAssignment=*/false, nullptr, expr->getSourceRange()); } @@ -5036,9 +5096,237 @@ SpirvEmitter::processStructuredBufferLoad(const CXXMemberCallExpr *expr) { auto *zero = spvBuilder.getConstantInt(astContext.IntTy, llvm::APInt(32, 0)); auto *index = doExpr(expr->getArg(0)); - return derefOrCreatePointerToValue(buffer->getType(), info, structType, - {zero, index}, buffer->getExprLoc(), - range); + auto *result = + derefOrCreatePointerToValue(buffer->getType(), info, structType, + {zero, index}, buffer->getExprLoc(), range); + + // derefOrCreatePointerToValue returns an lvalue (AccessChain) when the base + // is an lvalue. This covers descriptor-heap buffers reached either directly + // (ResourceDescriptorHeap[i].Load()) or through a local alias var. + // StructuredBuffer::Load semantically returns a value, and the AST emits no + // LValueToRValue cast for the call expression, so emit the load explicitly. + // (Verified required: scoping this to alias vars only regresses the direct + // heap-access tests; non-heap callers are unaffected in the existing suite.) + if (result && !result->isRValue()) { + result = + spvBuilder.createLoad(structType, result, buffer->getExprLoc(), range); + } + + return result; +} + +void SpirvEmitter::markDescriptorHeapCounterUnsupported( + const DeclaratorDecl *decl) { + if (decl) + descriptorHeapUnsupportedCounters.insert(decl); +} + +bool SpirvEmitter::isDescriptorHeapCounterUnsupported(const Expr *expr) const { + if (const auto *decl = getReferencedDef(expr)) + return descriptorHeapUnsupportedCounters.count(decl) != 0; + return false; +} + +SpirvInstruction *SpirvEmitter::emitDescriptorHeapAccessChain( + const SpirvType *arrayType, SpirvInstruction *heap, SpirvVariable *indexVar, + SourceLocation loc) { + const auto *untypedUniformConstantType = + spvContext.getUntypedPointerKHRType(spv::StorageClass::UniformConstant); + auto *index = spvBuilder.createLoad(astContext.UnsignedIntTy, indexVar, loc); + return spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, + arrayType, heap, index, loc); +} + +void SpirvEmitter::storeDescriptorHeapIndex(SpirvVariable *indexVar, + SpirvInstruction *index, + QualType indexType, + const Expr *srcExpr) { + if (!astContext.hasSameType(indexType, astContext.UnsignedIntTy)) + index = castToType(index, indexType, astContext.UnsignedIntTy, + srcExpr->getExprLoc(), srcExpr->getSourceRange()); + spvBuilder.createStore(indexVar, index, srcExpr->getExprLoc(), + srcExpr->getSourceRange()); +} + +SpirvVariable * +SpirvEmitter::createDescriptorHeapIndexVar(const VarDecl *dstVar) { + const std::string name = dstVar->getName().str() + ".descriptor.index"; + return spvBuilder.addFnVar(astContext.UnsignedIntTy, dstVar->getLocation(), + name); +} + +bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias( + const DeclaratorDecl *dstDecl, const Expr *srcExpr) { + if (!spirvOptions.useDescriptorHeap || !dstDecl || !srcExpr) + return false; + + const auto *dstVar = dyn_cast(dstDecl); + if (!dstVar || + (!isRWTexture(dstVar->getType()) && !isRWBuffer(dstVar->getType()))) + return false; + + const auto *src = srcExpr->IgnoreParenCasts(); + auto found = descriptorHeapImageAccesses.find(src); + if (found == descriptorHeapImageAccesses.end()) + return false; + + auto &alias = descriptorHeapImageAliasVars[dstVar]; + if (!alias.indexVar) + alias.indexVar = createDescriptorHeapIndexVar(dstVar); + alias.imageType = found->second.imageType; + alias.arrayType = found->second.arrayType; + alias.heap = found->second.heap; + storeDescriptorHeapIndex(alias.indexVar, found->second.index, + found->second.indexType, srcExpr); + return true; +} + +bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias(const Expr *dstExpr, + const Expr *srcExpr) { + return tryToAssignDescriptorHeapImageAlias(getReferencedDef(dstExpr), + srcExpr); +} + +bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias( + const DeclaratorDecl *dstDecl, const Expr *srcExpr) { + if (!spirvOptions.useDescriptorHeap || !dstDecl || !srcExpr) + return false; + + const auto *dstVar = dyn_cast(dstDecl); + if (!dstVar || !(isConstantTextureBuffer(dstVar->getType()) || + isAKindOfStructuredOrByteBuffer(dstVar->getType()))) + return false; + + const auto *src = srcExpr->IgnoreParenCasts(); + auto found = descriptorHeapBufferAccesses.find(src); + if (found == descriptorHeapBufferAccesses.end()) + return false; + + if (isRWStructuredBuffer(dstVar->getType())) + markDescriptorHeapCounterUnsupported(dstVar); + + auto &alias = descriptorHeapBufferAliasVars[dstVar]; + if (!alias.indexVar) + alias.indexVar = createDescriptorHeapIndexVar(dstVar); + alias.bufferPointerType = found->second.bufferPointerType; + alias.arrayType = found->second.arrayType; + alias.heap = found->second.heap; + alias.layoutRule = found->second.layoutRule; + storeDescriptorHeapIndex(alias.indexVar, found->second.index, + found->second.indexType, srcExpr); + return true; +} + +bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias(const Expr *dstExpr, + const Expr *srcExpr) { + return tryToAssignDescriptorHeapBufferAlias(getReferencedDef(dstExpr), + srcExpr); +} + +SpirvInstruction * +SpirvEmitter::emitDescriptorHeapBufferPointer(const VarDecl *decl, + SourceLocation loc) { + auto found = descriptorHeapBufferAliasVars.find(decl); + if (found == descriptorHeapBufferAliasVars.end()) + return nullptr; + + auto *descriptorPtr = emitDescriptorHeapAccessChain( + found->second.arrayType, found->second.heap, found->second.indexVar, loc); + auto *bufferDataPtr = spvBuilder.createUnaryOp( + spv::Op::OpBufferPointerEXT, found->second.bufferPointerType, + descriptorPtr, loc); + bufferDataPtr->setStorageClass( + found->second.bufferPointerType->getStorageClass()); + bufferDataPtr->setLayoutRule(found->second.layoutRule); + bufferDataPtr->setRValue(false); + return bufferDataPtr; +} + +SpirvInstruction *SpirvEmitter::emitDescriptorHeapImageTexelPointer( + const VarDecl *decl, SpirvInstruction *coordinate, SpirvInstruction *sample, + QualType resultType, SourceLocation loc) { + auto found = descriptorHeapImageAliasVars.find(decl); + if (found == descriptorHeapImageAliasVars.end()) + return nullptr; + + auto *descriptorPtr = emitDescriptorHeapAccessChain( + found->second.arrayType, found->second.heap, found->second.indexVar, loc); + auto *ptr = spvBuilder.createUntypedImageTexelPointerEXT( + resultType, found->second.imageType, descriptorPtr, coordinate, sample, + loc); + ptr->setStorageClass(spv::StorageClass::Image); + return ptr; +} + +// Descriptor-heap buffers: ConstantBuffer is a UBO (Uniform); every other +// buffer resource (Structured/RW/ByteAddress, TextureBuffer) is an SSBO +// (StorageBuffer). Here because the opaque OpTypeBufferEXT descriptor +// carries no pointee interface type, so RemoveBufferBlockVisitor +// cannot infer/correct its storage class post-lowering. +static spv::StorageClass +getDescriptorHeapBufferStorageClass(QualType resourceType) { + return isConstantBuffer(resourceType) ? spv::StorageClass::Uniform + : spv::StorageClass::StorageBuffer; +} + +SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( + QualType resourceType, SpirvInstruction *heapVar, SpirvInstruction *index, + const Expr *expr, const Expr *baseExpr, const Expr *indexExpr) { + const auto *untypedUniformConstantType = + spvContext.getUntypedPointerKHRType(spv::StorageClass::UniformConstant); + LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, + spvBuilder); + const SpirvType *bufferDataType = lowerTypeVisitor.lowerType( + resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); + + const SpirvPointerType *bufferDataPointerType = nullptr; + SpirvLayoutRule layoutRule = spirvOptions.sBufferLayoutRule; + if (isConstantTextureBuffer(resourceType)) { + layoutRule = isConstantBuffer(resourceType) + ? spirvOptions.cBufferLayoutRule + : spirvOptions.tBufferLayoutRule; + bufferDataPointerType = spvContext.getPointerType( + bufferDataType, getDescriptorHeapBufferStorageClass(resourceType)); + } else { + bufferDataPointerType = dyn_cast(bufferDataType); + } + + if (!bufferDataPointerType) { + emitError("descriptor heap buffer type lowering failed", + expr->getExprLoc()); + return nullptr; + } + + // ConstantBuffer -> Uniform (UBO); all others -> StorageBuffer (SSBO) + // TODO: Remove this manual override once LowerTypeVisitor returns the + // correct StorageClass for descriptor-heap alias pointer types + // (currently it returns Uniform for all of them). + const spv::StorageClass bufferExtSC = isConstantBuffer(resourceType) + ? spv::StorageClass::Uniform + : spv::StorageClass::StorageBuffer; + const auto *bufferDescriptorType = spvContext.getBufferEXTType(bufferExtSC); + // Buffer descriptors are always on the resource heap. + const auto *arrayType = getDescriptorHeapRuntimeArrayType( + bufferDescriptorType, /*onSamplerHeap=*/false); + auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( + untypedUniformConstantType, arrayType, heapVar, index, + baseExpr->getExprLoc()); + auto *bufferDataPtr = spvBuilder.createUnaryOp( + spv::Op::OpBufferPointerEXT, bufferDataPointerType, untypedAccessChainPtr, + baseExpr->getExprLoc()); + bufferDataPtr->setStorageClass(bufferDataPointerType->getStorageClass()); + bufferDataPtr->setLayoutRule(layoutRule); + bufferDataPtr->setRValue(false); + if (isRasterizerOrderedView(resourceType)) { + bufferDataPtr->setRasterizerOrdered(true); + spvBuilder.addExecutionMode(entryFunction, + declIdMapper.getInterlockExecutionMode(), {}, + baseExpr->getExprLoc()); + } + descriptorHeapBufferAccesses[expr] = { + bufferDataPointerType, arrayType, heapVar, index, + indexExpr->getType(), layoutRule}; + return bufferDataPtr; } SpirvInstruction * @@ -5062,6 +5350,22 @@ SpirvEmitter::incDecRWACSBufferCounter(const CXXMemberCallExpr *expr, (void)doExpr(object); } + if (isDescriptorHeapCounterUnsupported(object)) { + emitError("counter operations on heap-loaded RWStructuredBuffer are not " + "supported with SPV_EXT_descriptor_heap", + expr->getCallee()->getExprLoc()); + return nullptr; + } + + if (spirvOptions.useDescriptorHeap && + (isAppendStructuredBuffer(object->getType()) || + isConsumeStructuredBuffer(object->getType()))) { + emitError("append/consume structured buffers are not supported with " + "SPV_EXT_descriptor_heap", + expr->getCallee()->getExprLoc()); + return nullptr; + } + auto *counter = getFinalACSBufferCounterInstruction(object); if (!counter) { emitFatalError("Cannot access associated counter variable for an array of " @@ -5108,6 +5412,11 @@ bool SpirvEmitter::tryToAssignCounterVar(const DeclaratorDecl *dstDecl, declIdMapper.getOrCreateCounterIdAliasPair(dstDecl)) { auto *srcCounter = getFinalACSBufferCounterInstruction(srcExpr); if (!srcCounter) { + if (spirvOptions.useDescriptorHeap && + isDescriptorHeap(srcExpr->IgnoreParenCasts())) { + markDescriptorHeapCounterUnsupported(dstDecl); + return true; + } emitFatalError("cannot find the associated counter variable", srcExpr->getExprLoc()); return false; @@ -5147,6 +5456,11 @@ bool SpirvEmitter::tryToAssignCounterVar(const Expr *dstExpr, auto *srcCounter = getFinalACSBufferCounterInstruction(srcExpr); if ((dstCounter == nullptr) != (srcCounter == nullptr)) { + if (spirvOptions.useDescriptorHeap && dstCounter && + isDescriptorHeap(srcExpr->IgnoreParenCasts())) { + markDescriptorHeapCounterUnsupported(getReferencedDef(dstExpr)); + return true; + } emitFatalError("cannot handle associated counter variable assignment", srcExpr->getExprLoc()); return false; @@ -5278,6 +5592,8 @@ SpirvEmitter::processACSBufferAppendConsume(const CXXMemberCallExpr *expr) { expr, isAppend, // We have already translated the object in the above. Avoid duplication. /*loadObject=*/false); + if (!index) + return nullptr; auto bufferElemTy = hlsl::GetHLSLResourceResultType(object->getType()); @@ -5700,16 +6016,16 @@ SpirvEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr, retVal = processTextureLevelOfDetail(expr, /* unclamped */ true); break; case IntrinsicOp::MOP_IncrementCounter: - retVal = spvBuilder.createUnaryOp( - spv::Op::OpBitcast, astContext.UnsignedIntTy, - incDecRWACSBufferCounter(expr, /*isInc*/ true), - expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); + if (auto *counter = incDecRWACSBufferCounter(expr, /*isInc*/ true)) + retVal = spvBuilder.createUnaryOp( + spv::Op::OpBitcast, astContext.UnsignedIntTy, counter, + expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); break; case IntrinsicOp::MOP_DecrementCounter: - retVal = spvBuilder.createUnaryOp( - spv::Op::OpBitcast, astContext.UnsignedIntTy, - incDecRWACSBufferCounter(expr, /*isInc*/ false), - expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); + if (auto *counter = incDecRWACSBufferCounter(expr, /*isInc*/ false)) + retVal = spvBuilder.createUnaryOp( + spv::Op::OpBitcast, astContext.UnsignedIntTy, counter, + expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); break; case IntrinsicOp::MOP_Append: if (hlsl::IsHLSLStreamOutputType( @@ -6652,36 +6968,75 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, const Expr *indexExpr = nullptr; getDescriptorHeapOperands(expr, &baseExpr, &indexExpr); - const Expr *parentExpr = cast(parentMap->getParent(expr)); + // The heap index expression must be immediately converted to a concrete + // resource type (an implicit cast inserted by the front-end). If the + // parent is missing or is not a cast (e.g. the result is discarded as + // a statement, or used in a context with no target resource type) we + // cannot determine the resource type. + const auto *parentExpr = + dyn_cast_or_null(parentMap->getParent(expr)); + if (!parentExpr) { + emitError("ResourceDescriptorHeap/SamplerDescriptorHeap indexing must " + "be used as a resource", + expr->getExprLoc()); + return nullptr; + } QualType resourceType = parentExpr->getType(); + // The heap object must be a direct reference to the builtin heap + // variable. Anything else (e.g. a non-variable expression) has no backing + // VarDecl. const auto *declRefExpr = dyn_cast(baseExpr->IgnoreCasts()); - auto *decl = cast(declRefExpr->getDecl()); + const auto *decl = + declRefExpr ? dyn_cast(declRefExpr->getDecl()) : nullptr; + if (!decl) { + emitError("unsupported ResourceDescriptorHeap/SamplerDescriptorHeap " + "expression", + baseExpr->getExprLoc()); + return nullptr; + } auto *var = declIdMapper.createResourceHeap(decl, resourceType); auto *index = doExpr(indexExpr); if (spirvOptions.useDescriptorHeap) { - emitWarning("SPV_EXT_descriptor_heap support is incomplete.", - baseExpr->getExprLoc()); needsLegalization = true; - if (isAKindOfStructuredOrByteBuffer(resourceType)) { - emitError("UAV support not implemented with non-emulated heaps.", + if (isAppendStructuredBuffer(resourceType) || + isConsumeStructuredBuffer(resourceType)) { + emitError("append/consume structured buffers are not supported with " + "SPV_EXT_descriptor_heap", expr->getExprLoc()); return nullptr; } - const auto *untypedType = spvContext.getUntypedPointerKHRType( - spv::StorageClass::UniformConstant); + if (isAKindOfStructuredOrByteBuffer(resourceType) || + isConstantTextureBuffer(resourceType)) { + return emitDescriptorHeapBufferAccess(resourceType, var, index, expr, + baseExpr, indexExpr); + } + + const auto *untypedUniformConstantType = + spvContext.getUntypedPointerKHRType( + spv::StorageClass::UniformConstant); LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, spvBuilder); const SpirvType *handleType = lowerTypeVisitor.lowerType(resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - const auto *arrayType = - spvContext.getRuntimeArrayType(handleType, llvm::None); + // Images/samplers may come from either heap; pick the right stride. + const auto *arrayType = getDescriptorHeapRuntimeArrayType( + handleType, isSamplerDescriptorHeap(decl)); auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( - untypedType, arrayType, var, index, baseExpr->getExprLoc()); + untypedUniformConstantType, arrayType, var, index, + baseExpr->getExprLoc()); + if (isRasterizerOrderedView(resourceType)) { + spvBuilder.addExecutionMode(entryFunction, + declIdMapper.getInterlockExecutionMode(), + {}, baseExpr->getExprLoc()); + } + descriptorHeapImageAccesses[expr] = { + untypedAccessChainPtr, handleType, arrayType, var, index, + indexExpr->getType()}; return spvBuilder.createLoad(resourceType, untypedAccessChainPtr, baseExpr->getExprLoc(), range); } @@ -8876,6 +9231,16 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { declIdMapper.registerSpecConstant(varDecl, specConstant); } +const SpirvType * +SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap) { + constexpr uint32_t kDefaultResourceHeapStride = 64; + constexpr uint32_t kDefaultSamplerHeapStride = 32; + const uint32_t stride = + onSamplerHeap ? kDefaultSamplerHeapStride : kDefaultResourceHeapStride; + return spvContext.getRuntimeArrayType(elemType, stride); +} + SpirvInstruction * SpirvEmitter::processMatrixBinaryOp(const Expr *lhs, const Expr *rhs, const BinaryOperatorKind opcode, @@ -10575,18 +10940,41 @@ SpirvEmitter::processIntrinsicInterlockedMethod(const CallExpr *expr, return nullptr; } } - auto *baseInstr = doExpr(base); - if (baseInstr->isRValue()) { - // OpImageTexelPointer's Image argument must have a type of - // OpTypePointer with Type OpTypeImage. Need to create a temporary - // variable if the baseId is an rvalue. - baseInstr = - createTemporaryVar(base->getType(), getAstTypeName(base->getType()), - baseInstr, base->getExprLoc()); - } auto *coordInstr = doExpr(index); - ptr = spvBuilder.createImageTexelPointer(baseType, baseInstr, coordInstr, - zero, srcLoc); + + if (spirvOptions.useDescriptorHeap) { + const Expr *heapBase = base->IgnoreParenCasts(); + auto access = descriptorHeapImageAccesses.find(heapBase); + if (access == descriptorHeapImageAccesses.end() && + isDescriptorHeap(heapBase)) { + (void)doExpr(heapBase); + access = descriptorHeapImageAccesses.find(heapBase); + } + if (access != descriptorHeapImageAccesses.end()) { + ptr = spvBuilder.createUntypedImageTexelPointerEXT( + baseType, access->second.imageType, access->second.accessChain, + coordInstr, zero, srcLoc); + ptr->setStorageClass(spv::StorageClass::Image); + } else if (const auto *decl = + dyn_cast_or_null(getReferencedDef(base))) { + ptr = emitDescriptorHeapImageTexelPointer(decl, coordInstr, zero, + baseType, srcLoc); + } + } + + if (!ptr) { + auto *baseInstr = doExpr(base); + if (baseInstr->isRValue()) { + // OpImageTexelPointer's Image argument must have a type of + // OpTypePointer with Type OpTypeImage. Need to create a temporary + // variable if the baseId is an rvalue. + baseInstr = createTemporaryVar(base->getType(), + getAstTypeName(base->getType()), + baseInstr, base->getExprLoc()); + } + ptr = spvBuilder.createImageTexelPointer(baseType, baseInstr, + coordInstr, zero, srcLoc); + } } } if (!ptr) { diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 10cc31023c..76c174eab4 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -33,6 +33,7 @@ #include "clang/SPIRV/FeatureManager.h" #include "clang/SPIRV/SpirvBuilder.h" #include "clang/SPIRV/SpirvContext.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "ConstEvaluator.h" @@ -398,6 +399,23 @@ 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 the default ArrayStride (64 bytes for the resource heap, + /// 32 bytes for the sampler heap). + const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap); + + /// Emits the native (SPV_EXT_descriptor_heap) access for a buffer-like + /// resource (StructuredBuffer/ByteAddressBuffer/ConstantBuffer/TextureBuffer + /// and their RW variants) loaded from heapVar at index: + /// OpUntypedAccessChainKHR -> OpBufferPointerEXT. Records the access in + /// descriptorHeapBufferAccesses[expr] and returns the buffer-data pointer, or + /// nullptr (after emitting an error) on type-lowering failure. Caller must + /// have already checked the resource is buffer-like. + SpirvInstruction *emitDescriptorHeapBufferAccess( + QualType resourceType, SpirvInstruction *heapVar, SpirvInstruction *index, + const Expr *expr, const Expr *baseExpr, const Expr *indexExpr); + /// Generates the necessary instructions for conducting the given binary /// operation on lhs and rhs. /// @@ -1202,6 +1220,74 @@ class SpirvEmitter : public ASTConsumer { const Expr *srcExpr); bool tryToAssignCounterVar(const Expr *dstExpr, const Expr *srcExpr); + /// \brief Marks an alias resource as heap-loaded with no associated counter. + void markDescriptorHeapCounterUnsupported(const DeclaratorDecl *decl); + + /// \brief Returns true if counter operations on the resource expression are + /// known to be unsupported because the resource came from + /// ResourceDescriptorHeap. + bool isDescriptorHeapCounterUnsupported(const Expr *expr) const; + + /// \brief Records the descriptor heap index assigned to a local image + /// resource alias, if the source expression came directly from a descriptor + /// heap. This mirrors the normal resource handle store while preserving + /// enough information to recreate OpUntypedImageTexelPointerEXT after + /// reassignment. + bool tryToAssignDescriptorHeapImageAlias(const DeclaratorDecl *dstDecl, + const Expr *srcExpr); + bool tryToAssignDescriptorHeapImageAlias(const Expr *dstExpr, + const Expr *srcExpr); + bool tryToAssignDescriptorHeapBufferAlias(const DeclaratorDecl *dstDecl, + const Expr *srcExpr); + bool tryToAssignDescriptorHeapBufferAlias(const Expr *dstExpr, + const Expr *srcExpr); + + /// \brief Creates the ".descriptor.index" function variable used to + /// remember the descriptor heap index of a local resource alias dstVar. + SpirvVariable *createDescriptorHeapIndexVar(const VarDecl *dstVar); + + /// \brief If decl is a function-local variable initialized directly from a + /// descriptor heap subscript (e.g. ResourceDescriptorHeap[i]), creates the + /// appropriate alias and returns true. Returns false if decl is not such a + /// descriptor-heap alias and should be emitted as a normal variable. + bool tryToCreateDescriptorHeapAlias(const VarDecl *decl, const Expr *init); + + /// \brief Handles a buffer = ResourceDescriptorHeap[i] assignment. Returns + /// None if assignExpr is not such an assignment (caller should fall back to a + /// normal assignment). Otherwise the alias was created and the wrapped value + /// is the result of the assignment expression (possibly nullptr). + llvm::Optional + tryToAssignToDescriptorHeapBuffer(const BinaryOperator *assignExpr); + + /// \brief Emits the instructions that re-derive the buffer-data pointer for a + /// descriptor-heap buffer alias decl (OpLoad of the saved index, then + /// OpUntypedAccessChainKHR + OpBufferPointerEXT). Returns nullptr if decl + /// is not a recorded heap buffer alias. Not a pure lookup -- it emits. + SpirvInstruction *emitDescriptorHeapBufferPointer(const VarDecl *decl, + SourceLocation loc); + + /// \brief Emits an OpUntypedImageTexelPointerEXT for a descriptor-heap image + /// alias decl (OpLoad of the saved index, then OpUntypedAccessChainKHR + /// feeding the texel pointer). Returns nullptr if decl is not a recorded heap + /// image alias. Symmetric with emitDescriptorHeapBufferPointer. + SpirvInstruction *emitDescriptorHeapImageTexelPointer( + const VarDecl *decl, SpirvInstruction *coordinate, + SpirvInstruction *sample, QualType resultType, SourceLocation loc); + + /// \brief Emits OpLoad of indexVar then OpUntypedAccessChainKHR into the + /// heap, yielding the per-descriptor pointer shared by the buffer/image alias + /// re-derivation paths above. + SpirvInstruction *emitDescriptorHeapAccessChain(const SpirvType *arrayType, + SpirvInstruction *heap, + SpirvVariable *indexVar, + SourceLocation loc); + + /// \brief Stores index (cast to uint when needed) into the alias indexVar, + /// shared by the image/buffer alias-assignment paths. + void storeDescriptorHeapIndex(SpirvVariable *indexVar, + SpirvInstruction *index, QualType indexType, + const Expr *srcExpr); + /// Returns an instruction that points to the alias counter variable with the /// entity represented by expr. /// @@ -1555,6 +1641,51 @@ class SpirvEmitter : public ASTConsumer { /// The SPIR-V function parameter for the current this object. SpirvInstruction *curThis; + /// Native descriptor heap image descriptors used to directly form image + /// atomics. The emitter is single-use per translation unit, so these + /// AST-pointer maps live for the emitter lifetime. + struct DescriptorHeapImageAccess { + SpirvInstruction *accessChain; + const SpirvType *imageType; + const SpirvType *arrayType; + SpirvInstruction *heap; + SpirvInstruction *index; + QualType indexType; + }; + struct DescriptorHeapImageAlias { + SpirvVariable *indexVar; + const SpirvType *imageType; + const SpirvType *arrayType; + SpirvInstruction *heap; + }; + struct DescriptorHeapBufferAccess { + const SpirvPointerType *bufferPointerType; + const SpirvType *arrayType; + SpirvInstruction *heap; + SpirvInstruction *index; + QualType indexType; + SpirvLayoutRule layoutRule; + }; + struct DescriptorHeapBufferAlias { + SpirvVariable *indexVar; + const SpirvPointerType *bufferPointerType; + const SpirvType *arrayType; + SpirvInstruction *heap; + SpirvLayoutRule layoutRule; + }; + llvm::DenseMap + descriptorHeapImageAccesses; + llvm::DenseMap + descriptorHeapImageAliasVars; + llvm::DenseMap + descriptorHeapBufferAccesses; + llvm::DenseMap + descriptorHeapBufferAliasVars; + + /// RWStructuredBuffer aliases loaded from ResourceDescriptorHeap have no + /// associated UAV counter descriptor in the native descriptor heap path. + llvm::DenseSet descriptorHeapUnsupportedCounters; + /// The source location of a push constant block we have previously seen. /// Invalid means no push constant blocks defined thus far. SourceLocation seenPushConstantAt; diff --git a/tools/clang/lib/SPIRV/SpirvInstruction.cpp b/tools/clang/lib/SPIRV/SpirvInstruction.cpp index ea985e5da4..153a2f9c66 100644 --- a/tools/clang/lib/SPIRV/SpirvInstruction.cpp +++ b/tools/clang/lib/SPIRV/SpirvInstruction.cpp @@ -968,11 +968,13 @@ SpirvImageTexelPointer::SpirvImageTexelPointer(QualType resultType, image(imageInst), coordinate(coordinateInst), sample(sampleInst) {} SpirvUntypedImageTexelPointerEXT::SpirvUntypedImageTexelPointerEXT( - QualType resultType, SourceLocation loc, SpirvInstruction *imageInst, - SpirvInstruction *coordinateInst, SpirvInstruction *sampleInst) + QualType resultType, SourceLocation loc, const SpirvType *spvImageType, + SpirvInstruction *imageInst, SpirvInstruction *coordinateInst, + SpirvInstruction *sampleInst) : SpirvInstruction(IK_UntypedImageTexelPointerEXT, spv::Op::OpUntypedImageTexelPointerEXT, resultType, loc), - image(imageInst), coordinate(coordinateInst), sample(sampleInst) {} + imageType(spvImageType), image(imageInst), coordinate(coordinateInst), + sample(sampleInst) {} SpirvLoad::SpirvLoad(QualType resultType, SourceLocation loc, SpirvInstruction *pointerInst, SourceRange range, diff --git a/tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl b/tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl deleted file mode 100644 index 1641d1f038..0000000000 --- a/tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl +++ /dev/null @@ -1,59 +0,0 @@ -// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -spirv %s | FileCheck %s - -// CHECK: OpCapability DescriptorHeapEXT -// CHECK: OpExtension "SPV_EXT_descriptor_heap" - -// CHECK-DAG: OpDecorate %[[ResourceHeap:[a-zA-Z0-9_]+]] BuiltIn ResourceHeapEXT -// CHECK-DAG: OpDecorate %[[SamplerHeap:[a-zA-Z0-9_]+]] BuiltIn SamplerHeapEXT - -// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant -// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown -// CHECK-DAG: %[[RWTex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 2 Rgba32f -// CHECK-DAG: %[[BufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 1 Rgba32f -// CHECK-DAG: %[[RWBufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 2 Rgba32f -// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler - -// CHECK-DAG: %[[RA_Tex2DType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]] -// CHECK-DAG: %[[RA_RWTex2DType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTex2DType]] -// CHECK-DAG: %[[RA_BufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferType]] -// CHECK-DAG: %[[RA_RWBufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufferType]] -// CHECK-DAG: %[[RA_SamplerType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]] - -// CHECK: %[[ResourceHeap]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant -// CHECK: %[[SamplerHeap]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant - -[numthreads(1, 1, 1)] -void main(uint3 tid : SV_DispatchThreadID) { - Texture2D myTex = ResourceDescriptorHeap[0]; - // CHECK: %[[TexIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_Tex2DType]] %[[ResourceHeap]] %uint_0 - // CHECK: %[[TexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexIndex]] - - RWTexture2D myRWTex = ResourceDescriptorHeap[1]; - // CHECK: %[[RWTexIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWTex2DType]] %[[ResourceHeap]] %uint_1 - // CHECK: %[[RWTexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWTex2DType]] %[[RWTexIndex]] - - Buffer myBuf = ResourceDescriptorHeap[2]; - // CHECK: %[[BufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_BufferType]] %[[ResourceHeap]] %uint_2 - // CHECK: %[[BufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[BufferType]] %[[BufIndex]] - - RWBuffer myRWBuf = ResourceDescriptorHeap[3]; - // CHECK: %[[RWBufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWBufferType]] %[[ResourceHeap]] %uint_3 - // CHECK: %[[RWBufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufferType]] %[[RWBufIndex]] - - SamplerState mySamp = SamplerDescriptorHeap[0]; - // CHECK: %[[SampIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_SamplerType]] %[[SamplerHeap]] %uint_0 - // CHECK: %[[SampHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampIndex]] - - // CHECK: %[[SampledImage:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[TexHandle]] %[[SampHandle]] - // CHECK: %[[TexResult:[a-zA-Z0-9_]+]] = OpImageSampleExplicitLod %v4float %[[SampledImage]] - float4 texVal = myTex.SampleLevel(mySamp, float2(0, 0), 0); - - // CHECK: %[[BufResult:[a-zA-Z0-9_]+]] = OpImageFetch %v4float %[[BufHandle]] - float4 bufVal = myBuf.Load(tid.x); - - // CHECK: OpImageWrite %[[RWTexHandle]] - myRWTex[tid.xy] = texVal; - - // CHECK: OpImageWrite %[[RWBufHandle]] - myRWBuf[tid.x] = bufVal; -} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl new file mode 100644 index 0000000000..89bf18acaf --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl @@ -0,0 +1,19 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DTEST_APPEND %s 2>&1 | FileCheck --check-prefix=APPEND %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck --check-prefix=CONSUME %s + +// Verifies: Append/Consume structured buffers via ResourceDescriptorHeap +// emit a hard error under SPV_EXT_descriptor_heap. + +// APPEND: append/consume structured buffers are not supported with SPV_EXT_descriptor_heap +// CONSUME: append/consume structured buffers are not supported with SPV_EXT_descriptor_heap + +[numthreads(1, 1, 1)] +void main() { +#ifdef TEST_APPEND + AppendStructuredBuffer output = ResourceDescriptorHeap[0]; + output.Append(1); +#else + ConsumeStructuredBuffer input = ResourceDescriptorHeap[0]; + uint val = input.Consume(); +#endif +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl new file mode 100644 index 0000000000..eb0b6b25ca --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl @@ -0,0 +1,57 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: StructuredBuffer / RWStructuredBuffer / ByteAddressBuffer +// share one StorageBuffer runtime array, yet each materializes its own +// typed OpBufferPointerEXT, ConstantBuffer uses a separate Uniform array, +// and reassignment re-indexes the shared array. +// +// StructuredBuffer -> shared OpTypeBufferEXT StorageBuffer array -> own typed OpBufferPointerEXT (%type_StructuredBuffer_*) +// RWStructuredBuffer -> shared OpTypeBufferEXT StorageBuffer array -> own typed OpBufferPointerEXT (%type_RWStructuredBuffer_*) +// ByteAddressBuffer -> shared OpTypeBufferEXT StorageBuffer array -> own typed OpBufferPointerEXT (%type_ByteAddressBuffer) +// ConstantBuffer -> separate OpTypeBufferEXT Uniform array -> own typed OpBufferPointerEXT (%type_ConstantBuffer_*) +// reassignment -> re-indexes shared StorageBuffer array (uint_4) -> same %type_StructuredBuffer_* pointer + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] +// CHECK-DAG: %[[UBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[UBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[UBufDesc]] +// Anchor each pointer type to its struct-type prefix to prevent FileCheck from +// binding all three StorageBuffer captures to the scalar %_ptr_StorageBuffer_uint. +// CHECK-DAG: %[[SBInputPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_StructuredBuffer_{{.*}} +// CHECK-DAG: %[[SBOutputPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_RWStructuredBuffer_{{.*}} +// CHECK-DAG: %[[SBBytesPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_ByteAddressBuffer{{$}} +// CHECK-DAG: %[[UConstPtr:[a-zA-Z0-9_]+]] = OpTypePointer Uniform %type_ConstantBuffer_{{.*}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + +struct Constants { + uint value; +}; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + StructuredBuffer input = ResourceDescriptorHeap[0]; + RWStructuredBuffer output = ResourceDescriptorHeap[1]; + ByteAddressBuffer inputBytes = ResourceDescriptorHeap[2]; + ConstantBuffer constants = ResourceDescriptorHeap[3]; + + // CHECK: %[[InputDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT %[[SBInputPtr]] %[[InputDesc]] + // CHECK: %[[OutputDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_1 + // CHECK: OpBufferPointerEXT %[[SBOutputPtr]] %[[OutputDesc]] + // CHECK: %[[InputBytesDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_2 + // CHECK: OpBufferPointerEXT %[[SBBytesPtr]] %[[InputBytesDesc]] + // CHECK: %[[ConstantsDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[UBufArray]] %[[ResourceHeap]] %uint_3 + // CHECK: OpBufferPointerEXT %[[UConstPtr]] %[[ConstantsDesc]] + output[tid.x] = input.Load(tid.x) + inputBytes.Load(tid.x * 4) + constants.value; + outputBytes.Store(tid.x * 4, output[tid.x]); + + // Reassignment: verify new descriptor (index 4) is used after reassign. + input = ResourceDescriptorHeap[4]; + // CHECK: %[[ReassignedDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_4 + // CHECK: OpBufferPointerEXT %[[SBInputPtr]] %[[ReassignedDesc]] + outputBytes.Store(tid.x * 4 + 4, input.Load(tid.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl new file mode 100644 index 0000000000..3db71dcc13 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl @@ -0,0 +1,78 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: ConstantBuffer reassignment from the descriptor heap re-indexes +// the Uniform array on every assignment and each member load uses the latest +// descriptor, in three forms that buffer.hlsl does not cover: +// +// dynamic runtime index -> %[[Idx]] (OpLoad %uint) + OpAccessChain %int_0 +// OpIAdd-computed index -> %[[IdxPlus2]] (OpIAdd) + OpAccessChain %int_1 +// reassignment inside a branch -> %[[IdxMinus2]] (OpUGreaterThan/OpBranchConditional/OpISub) + OpAccessChain %int_2 + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[BufferDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[BufferArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferDesc]] +// CHECK-DAG: %[[CBPtr:[a-zA-Z0-9_]+]] = OpTypePointer Uniform %type_ConstantBuffer_Constants + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +struct Constants +{ + uint a; + uint b; + uint c; +}; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) +{ + uint idx = tid.x; + uint cond = tid.y; + + ConstantBuffer constants = ResourceDescriptorHeap[idx]; + + // CHECK: %[[Idx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[Cond:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[InitDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[Idx]] + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[InitDesc]] + // CHECK: %[[ADesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[Idx]] + // CHECK: %[[APtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[ADesc]] + // CHECK: %[[AAccess:[a-zA-Z0-9_]+]] = OpAccessChain %{{[a-zA-Z0-9_]+}} %[[APtr]] %int_0 + // CHECK: %[[A:[a-zA-Z0-9_]+]] = OpLoad %uint %[[AAccess]] + uint value = constants.a; + + constants = ResourceDescriptorHeap[idx + 2]; + + // CHECK: %[[IdxPlus2:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[Idx]] %uint_2 + // CHECK: %[[AssignDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxPlus2]] + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[AssignDesc]] + // CHECK: %[[BDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxPlus2]] + // CHECK: %[[BPtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[BDesc]] + // CHECK: %[[BUseDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxPlus2]] + // CHECK: %[[BUsePtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[BUseDesc]] + // CHECK: %[[BAccess:[a-zA-Z0-9_]+]] = OpAccessChain %{{[a-zA-Z0-9_]+}} %[[BUsePtr]] %int_1 + // CHECK: %[[B:[a-zA-Z0-9_]+]] = OpLoad %uint %[[BAccess]] + // CHECK: %[[AB:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[A]] %[[B]] + value += constants.b; + + if (cond > 4) { + constants = ResourceDescriptorHeap[idx - 2]; + + // CHECK: %[[CondCmp:[a-zA-Z0-9_]+]] = OpUGreaterThan %bool %[[Cond]] %uint_4 + // CHECK: OpBranchConditional %[[CondCmp]] + // CHECK: %[[IdxMinus2:[a-zA-Z0-9_]+]] = OpISub %uint %[[Idx]] %uint_2 + // CHECK: %[[BranchDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxMinus2]] + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[BranchDesc]] + // CHECK: %[[CDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxMinus2]] + // CHECK: %[[CPtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CDesc]] + // CHECK: %[[CUseDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxMinus2]] + // CHECK: %[[CUsePtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CUseDesc]] + // CHECK: %[[CAccess:[a-zA-Z0-9_]+]] = OpAccessChain %{{[a-zA-Z0-9_]+}} %[[CUsePtr]] %int_2 + // CHECK: %[[C:[a-zA-Z0-9_]+]] = OpLoad %uint %[[CAccess]] + // CHECK: OpIAdd %uint %[[AB]] %[[C]] + value += constants.c; + } + + outputBytes.Store(0, value); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl new file mode 100644 index 0000000000..1a41f3ea44 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl @@ -0,0 +1,51 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: ConstantBuffer and TextureBuffer from the heap map to different element types, +// both carrying Block and supporting member access. +// +// ConstantBuffer -> OpTypeBufferEXT Uniform -> Block +// TextureBuffer -> OpTypeBufferEXT StorageBuffer -> Block + NonWritable members + +// CHECK-DAG: OpDecorate %type_ConstantBuffer_Data Block +// CHECK-DAG: OpDecorate %type_TextureBuffer_Data Block +// CHECK-DAG: OpMemberDecorate %type_TextureBuffer_Data 0 NonWritable +// CHECK-DAG: OpMemberDecorate %type_TextureBuffer_Data 1 NonWritable + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[CBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[CBDescArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CBDesc]] +// CHECK-DAG: %[[CBPtr:[a-zA-Z0-9_]+]] = OpTypePointer Uniform %type_ConstantBuffer_Data +// CHECK-DAG: %[[TBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[TBDescArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TBDesc]] +// CHECK-DAG: %[[TBPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_TextureBuffer_Data + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +struct Data { + uint a; + uint b[2]; +}; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + ConstantBuffer cb = ResourceDescriptorHeap[0]; + TextureBuffer tb = ResourceDescriptorHeap[1]; + + uint index = tid.x & 1; + + // CHECK: %[[CBDescPtr:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[CBDescArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[CBDescPtr]] + // CHECK: %[[TBDescPtr:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[TBDescArray]] %[[ResourceHeap]] %uint_1 + // CHECK: OpBufferPointerEXT %[[TBPtr]] %[[TBDescPtr]] + // CHECK: %[[CBDataA:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[CBDataA]] %int_0 + // CHECK: %[[CBDataB:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[CBDataB]] %int_1 %{{[a-zA-Z0-9_]+}} + // CHECK: %[[TBDataA:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[TBPtr]] %[[TBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[TBDataA]] %int_0 + // CHECK: %[[TBDataB:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[TBPtr]] %[[TBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[TBDataB]] %int_1 %int_1 + outputBytes.Store(0, cb.a + cb.b[index] + tb.a + tb.b[1]); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl new file mode 100644 index 0000000000..c0b406428c --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl @@ -0,0 +1,24 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DTEST_INCREMENT %s 2>&1 | FileCheck --check-prefix=INC %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck --check-prefix=DEC %s + +// Verifies: IncrementCounter/DecrementCounter on a +// heap-loaded RWStructuredBuffer is rejected with a hard error under +// SPV_EXT_descriptor_heap. + +// INC: counter operations on heap-loaded RWStructuredBuffer are not supported with SPV_EXT_descriptor_heap +// DEC: counter operations on heap-loaded RWStructuredBuffer are not supported with SPV_EXT_descriptor_heap + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main() { + RWStructuredBuffer buffer = ResourceDescriptorHeap[0]; + +#ifdef TEST_INCREMENT + uint value = buffer.IncrementCounter(); +#else + uint value = buffer.DecrementCounter(); +#endif + + outputBytes.Store(0, value); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl new file mode 100644 index 0000000000..3e26d746e4 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl @@ -0,0 +1,14 @@ +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck %s + +// Regression test for an unguarded cast in doCXXOperatorCallExpr: +// cast(parentMap->getParent(expr)) +// A descriptor-heap index whose result is discarded (used as a bare expression +// statement) has no parent cast to a concrete resource type, so the resource +// type cannot be determined. This must emit a diagnostic, not an ICE. + +// CHECK: error: {{.*}}ResourceDescriptorHeap/SamplerDescriptorHeap indexing must be used as a resource + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + ResourceDescriptorHeap[tid.x]; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl new file mode 100644 index 0000000000..3a72fa3482 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl @@ -0,0 +1,97 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fcgl -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: a heap-loaded resource passed by value to a user function lowers to +// pass-by-Function-pointer. +// +// heap handle -> OpStore into a Function-class var -> per-call OpLoad/OpStore/OpFunctionCall +// parameter slot -> OpFunctionParameter %[[*PtrType]] -> Function storage class + +// CHECK-DAG: OpName %[[ReadTex:[a-zA-Z0-9_]+]] "ReadTex" +// CHECK-DAG: OpName %[[ReadBuf:[a-zA-Z0-9_]+]] "ReadBuf" +// CHECK-DAG: OpName %[[WriteBuf:[a-zA-Z0-9_]+]] "WriteBuf" + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[TexType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[BufType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 1 Rgba32f +// CHECK-DAG: %[[RWBufType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 2 Rgba32f +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[TexPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[TexType]] +// CHECK-DAG: %[[BufPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[BufType]] +// CHECK-DAG: %[[RWBufPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[RWBufType]] +// CHECK-DAG: %[[SamplerPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[SamplerType]] +// CHECK-DAG: %[[RA_TexType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexType]] +// CHECK-DAG: %[[RA_BufType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufType]] +// CHECK-DAG: %[[RA_RWBufType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufType]] +// CHECK-DAG: %[[RA_SamplerType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]] + +float4 ReadTex(Texture2D tex, SamplerState samp); +float4 ReadBuf(Buffer buf, uint index); +void WriteBuf(RWBuffer buf, uint index, float4 value); + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2D tex = ResourceDescriptorHeap[0]; + Buffer buf = ResourceDescriptorHeap[1]; + RWBuffer outBuf = ResourceDescriptorHeap[2]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + // CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + + // CHECK: %[[TexDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_TexType]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[TexType]] %[[TexDesc]] + // CHECK: OpStore %[[TexVar:[a-zA-Z0-9_]+]] %[[TexHandle]] + // CHECK: %[[BufDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_BufType]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[BufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[BufType]] %[[BufDesc]] + // CHECK: OpStore %[[BufVar:[a-zA-Z0-9_]+]] %[[BufHandle]] + // CHECK: %[[RWBufDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWBufType]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[RWBufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufType]] %[[RWBufDesc]] + // CHECK: OpStore %[[RWBufVar:[a-zA-Z0-9_]+]] %[[RWBufHandle]] + // CHECK: %[[SamplerDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_SamplerType]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SamplerHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SamplerDesc]] + // CHECK: OpStore %[[SamplerVar:[a-zA-Z0-9_]+]] %[[SamplerHandle]] + + // CHECK: %[[TexArg:[a-zA-Z0-9_]+]] = OpLoad %[[TexType]] %[[TexVar]] + // CHECK: OpStore %[[TexParam:[a-zA-Z0-9_]+]] %[[TexArg]] + // CHECK: %[[SamplerArg:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SamplerVar]] + // CHECK: OpStore %[[SamplerParam:[a-zA-Z0-9_]+]] %[[SamplerArg]] + // CHECK: %[[TexValue:[a-zA-Z0-9_]+]] = OpFunctionCall %v4float %[[ReadTex]] %[[TexParam]] %[[SamplerParam]] + float4 value = ReadTex(tex, samp); + + // CHECK: %[[BufArg:[a-zA-Z0-9_]+]] = OpLoad %[[BufType]] %[[BufVar]] + // CHECK: OpStore %[[BufParam:[a-zA-Z0-9_]+]] %[[BufArg]] + // CHECK: %[[BufValue:[a-zA-Z0-9_]+]] = OpFunctionCall %v4float %[[ReadBuf]] %[[BufParam]] + value += ReadBuf(buf, tid.x); + + // CHECK: %[[RWBufArg:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufType]] %[[RWBufVar]] + // CHECK: OpStore %[[RWBufParam:[a-zA-Z0-9_]+]] %[[RWBufArg]] + // CHECK: OpFunctionCall %void %[[WriteBuf]] %[[RWBufParam]] + WriteBuf(outBuf, tid.x, value); + + outputBytes.Store(tid.x * 4, asuint(value.x)); +} + +// Callees emit after the entry point; each receives its resource by Function pointer. +float4 ReadTex(Texture2D tex, SamplerState samp) { + // CHECK: %[[ReadTex]] = OpFunction %v4float + // CHECK: OpFunctionParameter %[[TexPtrType]] + // CHECK: OpFunctionParameter %[[SamplerPtrType]] + // CHECK: OpSampledImage + return tex.SampleLevel(samp, float2(0.0, 0.0), 0.0); +} + +float4 ReadBuf(Buffer buf, uint index) { + // CHECK: %[[ReadBuf]] = OpFunction %v4float + // CHECK: OpFunctionParameter %[[BufPtrType]] + // CHECK: OpImageFetch + return buf.Load(index); +} + +void WriteBuf(RWBuffer buf, uint index, float4 value) { + // CHECK: %[[WriteBuf]] = OpFunction %void + // CHECK: OpFunctionParameter %[[RWBufPtrType]] + // CHECK: OpImageWrite + buf[index] = value; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl new file mode 100644 index 0000000000..9b1a720201 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl @@ -0,0 +1,42 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: GatherRed/Green/Blue/Alpha each emit a fresh OpSampledImage that +// reuses the once-loaded heap-sourced texture and sampler handles, selecting +// channel %int_0/1/2/3 via OpImageGather %v4float. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[SampledImgType:[a-zA-Z0-9_]+]] = OpTypeSampledImage %[[Tex2DType]] +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +// CHECK-DAG: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK-DAG: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // Tex and sampler are each loaded once from the heap; each Gather* creates a + // fresh OpSampledImage but reuses the same loaded image and sampler handles. + // CHECK: %[[TexChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexH:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexChain]] + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampH:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + + // CHECK: %[[SI0:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI0]] {{.*}} %int_0 + float4 r = tex.GatherRed(samp, uv); + // CHECK: %[[SI1:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI1]] {{.*}} %int_1 + float4 g = tex.GatherGreen(samp, uv); + // CHECK: %[[SI2:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI2]] {{.*}} %int_2 + float4 b = tex.GatherBlue(samp, uv); + // CHECK: %[[SI3:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI3]] {{.*}} %int_3 + float4 a = tex.GatherAlpha(samp, uv); + + return r + g + b + a; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl new file mode 100644 index 0000000000..f68e7b6b59 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl @@ -0,0 +1,35 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: heap StructuredBuffer/RWStructuredBuffer coexist with groupshared +// (Workgroup storage class) and GroupMemoryBarrierWithGroupSync. +// 1) groupshared backed by OpTypePointer Workgroup, distinct from heap descriptor pointers. +// 2) GroupMemoryBarrierWithGroupSync lowers to OpControlBarrier alongside heap accesses. +// 3) heap SB/RWSB accessed via OpUntypedAccessChainKHR + OpBufferPointerEXT in same entry point. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] + +// groupshared lives in Workgroup storage class. +// CHECK-DAG: %[[WorkgroupPtr:[a-zA-Z0-9_]+]] = OpTypePointer Workgroup +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +groupshared float4 shared_data[64]; + +[numthreads(64, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID, uint gi : SV_GroupIndex) { + StructuredBuffer input = ResourceDescriptorHeap[0]; + RWStructuredBuffer output = ResourceDescriptorHeap[1]; + + // CHECK-DAG: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK-DAG: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_1 + shared_data[gi] = input[tid.x]; + + // CHECK: OpControlBarrier + GroupMemoryBarrierWithGroupSync(); + + uint neighbor = (gi + 1) % 64; + + // CHECK: OpBufferPointerEXT + output[tid.x] = shared_data[gi] + shared_data[neighbor]; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl new file mode 100644 index 0000000000..a2390ee75e --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl @@ -0,0 +1,27 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: Texture.Load with a constant offset emits the ConstOffset +// image operand on OpImageFetch, contrasted against the no-offset fetch baseline. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} + +float4 main(float4 pos : SV_Position) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[0]; + + // CHECK: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap:[a-zA-Z0-9_]+]] %uint_0 + // CHECK: %[[Handle:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] + + int3 coord = int3(pos.xy, 0); + + // Load without offset. + // CHECK: OpImageFetch %v4float %[[Handle]] + float4 a = tex.Load(coord); + + // Load with constant offset — the ConstOffset image operand must appear. + // CHECK: OpImageFetch %v4float %[[Handle]] {{.*}}ConstOffset + float4 b = tex.Load(coord, int2(1, -1)); + + return a + b; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl new file mode 100644 index 0000000000..25089d9e43 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl @@ -0,0 +1,54 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: explicitly-bound resources coexist with descriptor-heap resources, and a bound image combines with a heap sampler. +// register(t0) Texture2D -> OpVariable %[[PtrTex]] UniformConstant + DescriptorSet/Binding -> explicitly-bound resource +// ResourceHeap/SamplerHeap -> OpUntypedVariableKHR UniformConstant (BuiltIn ResourceHeapEXT/SamplerHeapEXT) -> heap resources +// bound image + heap sampler -> OpSampledImage %[[BoundVal]] %[[SampH]] -> bound-image/heap-sampler combination + +// CHECK: OpCapability DescriptorHeapEXT +// CHECK-NOT: OpCapability UntypedPointersKHR +// CHECK: OpExtension "SPV_EXT_descriptor_heap" +// CHECK: OpExtension "SPV_KHR_untyped_pointers" + +// CHECK-DAG: OpDecorate %[[ResourceHeap:[a-zA-Z0-9_]+]] BuiltIn ResourceHeapEXT +// CHECK-DAG: OpDecorate %[[SamplerHeap:[a-zA-Z0-9_]+]] BuiltIn SamplerHeapEXT + +// CHECK-DAG: OpDecorate %[[BoundTex:[a-zA-Z0-9_]+]] DescriptorSet +// CHECK-DAG: OpDecorate %[[BoundTex]] Binding + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} +// CHECK-DAG: %[[PtrTex:[a-zA-Z0-9_]+]] = OpTypePointer UniformConstant %[[Tex2DType]] + +// CHECK: %[[BoundTex]] = OpVariable %[[PtrTex]] UniformConstant +// CHECK-DAG: %[[ResourceHeap]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK-DAG: %[[SamplerHeap]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[[vk::binding(0, 0)]] +Texture2D boundTex : register(t0); + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D heapTex = ResourceDescriptorHeap[1]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // CHECK: %[[HeapDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[HeapVal:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[HeapDesc]] + + // One heap sampler drives both samples, proving heap-sampler reuse across bound and heap images. + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampH:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + + // CHECK: %[[BoundVal:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[BoundTex]] + + // OWNED: bound image (OpVariable) combined with heap sampler. + // CHECK: OpSampledImage %{{.*}} %[[BoundVal]] %[[SampH]] + float4 a = boundTex.Sample(samp, uv); + + // CHECK: OpSampledImage %{{.*}} %[[HeapVal]] %[[SampH]] + float4 b = heapTex.Sample(samp, uv + 0.5); + + return a + b; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl new file mode 100644 index 0000000000..591fac84ff --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl @@ -0,0 +1,30 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: NonUniformResourceIndex on descriptor-heap accesses does NOT +// propagate NonUniform to the access-chain result or the loaded value. +// SPV_EXT_descriptor_heap deprecates the NonUniform decoration; drivers handle +// divergent indices natively and the decoration must be omitted. + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[RA_Tex2DType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]] +// CHECK-DAG: %[[RA_SamplerType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]] + +// CHECK-NOT: OpDecorate %{{.*}} NonUniform + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + +float4 main(uint idx : A) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[NonUniformResourceIndex(idx)]; + SamplerState samp = SamplerDescriptorHeap[NonUniformResourceIndex(idx + 1)]; + + // CHECK: %[[TexChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_Tex2DType]] %[[ResourceHeap]] %{{.*}} + // CHECK: %[[TexLoaded:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexChain]] + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_SamplerType]] %[[SamplerHeap]] %{{.*}} + // CHECK: %[[SampLoaded:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + // CHECK: %[[Combined:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[TexLoaded]] %[[SampLoaded]] + // CHECK: OpImageSampleExplicitLod %v4float %[[Combined]] + return tex.SampleLevel(samp, float2(0.0, 0.0), 0.0); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl new file mode 100644 index 0000000000..3e84ff294d --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl @@ -0,0 +1,21 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: an RWByteAddressBuffer sourced from the descriptor heap +// produces the typed StorageBuffer pointer %type_RWByteAddressBuffer +// and a read-WRITE OpBufferPointerEXT through it. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] +// CHECK-DAG: %[[RWBABPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_RWByteAddressBuffer +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[numthreads(64, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWByteAddressBuffer buf = ResourceDescriptorHeap[0]; + + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT %[[RWBABPtr]] %[[Desc]] + uint val = buf.Load(tid.x * 4); + buf.Store(tid.x * 4 + 256, val + 1); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl new file mode 100644 index 0000000000..6937f828ef --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl @@ -0,0 +1,53 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: InterlockedAdd on a heap-sourced RWTexture2D lowers +// to OpUntypedImageTexelPointerEXT (not OpImageTexelPointer) +// feeding OpAtomicIAdd, and reassignment targets the new descriptor. +// +// RWTexture2D heap access -> OpUntypedAccessChainKHR -> descriptor pointer in resource heap +// atomic texel pointer -> OpUntypedImageTexelPointerEXT -> EXT untyped image texel pointer +// InterlockedAdd -> OpAtomicIAdd -> image atomic on untyped texel pointer +// reassignment -> OpUntypedAccessChainKHR %uint_3 -> atomic uses new descriptor index + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[RWTexType:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 2 R32ui +// CHECK-DAG: %[[RWTexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexType]] +// CHECK-DAG: %[[UntypedImage:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR Image + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWTexture2D tex = ResourceDescriptorHeap[0]; + + uint original; + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_0 + // CHECK-NOT: OpImageTexelPointer + // CHECK: %[[TexelPtr:[a-zA-Z0-9_]+]] = OpUntypedImageTexelPointerEXT %[[UntypedImage]] %[[RWTexType]] %[[Desc]] + // CHECK: OpAtomicIAdd %uint %[[TexelPtr]] + InterlockedAdd(tex[tid.xy], 1, original); + + uint directOriginal; + // CHECK: %[[DirectDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_1 + // CHECK-NOT: OpImageTexelPointer + // CHECK: %[[DirectTexelPtr:[a-zA-Z0-9_]+]] = OpUntypedImageTexelPointerEXT %[[UntypedImage]] %[[RWTexType]] %[[DirectDesc]] + // CHECK: OpAtomicIAdd %uint %[[DirectTexelPtr]] + InterlockedAdd(((RWTexture2D)ResourceDescriptorHeap[1])[tid.xy], 2, + directOriginal); + + // Reassignment: atomic must use the NEW descriptor (index 3, not 2). + RWTexture2D reassigned = ResourceDescriptorHeap[2]; + reassigned = ResourceDescriptorHeap[3]; + uint reassignedOriginal; + // CHECK: %[[ReassignDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_3 + // CHECK-NOT: OpImageTexelPointer + // CHECK: %[[ReassignTexelPtr:[a-zA-Z0-9_]+]] = OpUntypedImageTexelPointerEXT %[[UntypedImage]] %[[RWTexType]] %[[ReassignDesc]] + // CHECK: OpAtomicIAdd %uint %[[ReassignTexelPtr]] + InterlockedAdd(reassigned[tid.xy], 3, reassignedOriginal); + + outputBytes.Store(0, original); + outputBytes.Store(4, directOriginal); + outputBytes.Store(8, reassignedOriginal); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl new file mode 100644 index 0000000000..87d2591318 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl @@ -0,0 +1,53 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: storage RW texture dimensionality lowers to distinct +// OpTypeImage dim/flags, each driving OpImageRead/OpImageWrite. +// +// RWTexture1D -> OpTypeImage %float 1D 2 0 0 2 Rgba32f -> storage +// RWTexture1DArray -> OpTypeImage %float 1D 2 1 0 2 Rgba32f -> storage +// RWTexture2DArray -> OpTypeImage %float 2D 2 1 0 2 Rgba32f -> storage +// RWTexture3D -> OpTypeImage %float 3D 2 0 0 2 Rgba32f -> storage + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[RW1DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 0 0 2 Rgba32f +// CHECK-DAG: %[[RW1DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 1 0 2 Rgba32f +// CHECK-DAG: %[[RW2DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 1 0 2 Rgba32f +// CHECK-DAG: %[[RW3DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 3D 2 0 0 2 Rgba32f + +// CHECK-DAG: %[[RA_RW1D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW1DType]]{{$}} +// CHECK-DAG: %[[RA_RW1DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW1DArrType]]{{$}} +// CHECK-DAG: %[[RA_RW2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW2DArrType]]{{$}} +// CHECK-DAG: %[[RA_RW3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW3DType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWTexture1D rwTex1d = ResourceDescriptorHeap[0]; + // CHECK: %[[RW1D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW1D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[RW1D:[a-zA-Z0-9_]+]] = OpLoad %[[RW1DType]] %[[RW1D_Desc]] + + RWTexture1DArray rwTex1dArr = ResourceDescriptorHeap[1]; + // CHECK: %[[RW1DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW1DArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[RW1DA:[a-zA-Z0-9_]+]] = OpLoad %[[RW1DArrType]] %[[RW1DA_Desc]] + + RWTexture2DArray rwTex2dArr = ResourceDescriptorHeap[2]; + // CHECK: %[[RW2DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW2DArr]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[RW2DA:[a-zA-Z0-9_]+]] = OpLoad %[[RW2DArrType]] %[[RW2DA_Desc]] + + RWTexture3D rwTex3d = ResourceDescriptorHeap[3]; + // CHECK: %[[RW3D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW3D]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[RW3D:[a-zA-Z0-9_]+]] = OpLoad %[[RW3DType]] %[[RW3D_Desc]] + + // CHECK: OpImageRead %v4float %[[RW1D]] + float4 v = rwTex1d[tid.x]; + + // CHECK: OpImageWrite %[[RW1DA]] + rwTex1dArr[uint2(tid.x, 0)] = v; + + // CHECK: OpImageWrite %[[RW2DA]] + rwTex2dArr[uint3(tid.xy, 0)] = v; + + // CHECK: OpImageWrite %[[RW3D]] + rwTex3d[tid.xyz] = v; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl new file mode 100644 index 0000000000..756dd53e2d --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl @@ -0,0 +1,40 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: SampleGrad lowers to OpImageSampleExplicitLod with the Grad image +// operand and SampleBias lowers to OpImageSampleImplicitLod with the Bias +// image operand, where the texture and sampler handles are loaded once from +// the descriptor heaps and reused across both samples (each sample a fresh +// OpSampledImage). + +// Capture anchors only (heap-source decorations owned by mixed-bound). +// CHECK-DAG: OpDecorate %[[ResourceHeap:[a-zA-Z0-9_]+]] BuiltIn ResourceHeapEXT +// CHECK-DAG: OpDecorate %[[SamplerHeap:[a-zA-Z0-9_]+]] BuiltIn SamplerHeapEXT + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[SampledImgType:[a-zA-Z0-9_]+]] = OpTypeSampledImage %[[Tex2DType]] +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // Tex and sampler each loaded once; each sample op creates its own OpSampledImage + // but both reuse the same loaded handles. + // CHECK: %[[TexChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexH:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexChain]] + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampH:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + + // CHECK: %[[SI0:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageSampleExplicitLod %v4float %[[SI0]] {{.*}} Grad + float4 a = tex.SampleGrad(samp, uv, ddx(uv), ddy(uv)); + + // CHECK: %[[SI1:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageSampleImplicitLod %v4float %[[SI1]] {{.*}} Bias + float4 b = tex.SampleBias(samp, uv, -1.0); + + return a + b; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl new file mode 100644 index 0000000000..f5a118715e --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl @@ -0,0 +1,30 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: a heap-sourced SamplerComparisonState combined with a heap-sourced +// Texture2D drives OpImageSampleDrefExplicitLod (SampleCmpLevelZero), +// with the comparison sampler loaded from the sampler heap and joined +// via OpSampledImage. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[TexType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[RA_Tex:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D depthTex = ResourceDescriptorHeap[0]; + // CHECK: %[[TexDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[TexType]] %[[TexDesc]] + + SamplerComparisonState shadowSamp = SamplerDescriptorHeap[0]; + // CHECK: %[[SampDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampDesc]] + + // CHECK: %[[Combined:[a-zA-Z0-9_]+]] = OpSampledImage %{{[a-zA-Z0-9_]+}} %[[TexHandle]] %[[SampHandle]] + // CHECK: %[[Shadow:[a-zA-Z0-9_]+]] = OpImageSampleDrefExplicitLod %float %[[Combined]] + float shadow = depthTex.SampleCmpLevelZero(shadowSamp, uv, 0.5); + return float4(shadow, shadow, shadow, 1.0); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl new file mode 100644 index 0000000000..e12cf41076 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl @@ -0,0 +1,64 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: UE bindless idiom — static const resources at global scope +// initialized from dynamic runtime uint indices ($Globals), across +// Texture2D / StructuredBuffer / RWTexture2D, plus a literal-index +// SamplerState. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[RWTex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 2 Rgba32f + +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} +// CHECK-DAG: %[[RA_SBBuf:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]]{{$}} +// CHECK-DAG: %[[RA_RWTex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTex2DType]]{{$}} + +// CHECK-DAG: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK-DAG: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +// Runtime uint indices (placed in $Globals cbuffer by DXC). +uint BindlessSRV_ColorTex; +uint BindlessSRV_DataBuf; +uint BindlessUAV_OutTex; + +// UE bindless pattern: static const from runtime heap index. +static const Texture2D ColorTex = ResourceDescriptorHeap[BindlessSRV_ColorTex]; +static const StructuredBuffer DataBuf = ResourceDescriptorHeap[BindlessSRV_DataBuf]; +static const RWTexture2D OutTex = ResourceDescriptorHeap[BindlessUAV_OutTex]; + +// Literal-index sampler (common UE pattern for global bilinear/point samplers). +static const SamplerState BilinearSamp = SamplerDescriptorHeap[2]; + +[numthreads(8, 8, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + // Heap indices are dynamic (loaded from $Globals), not literal constants. + // CHECK: %[[TexIdx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[TexDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %[[TexIdx]] + // CHECK: OpLoad %[[Tex2DType]] %[[TexDesc]] + + // CHECK: %[[BufIdx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_SBBuf]] %[[ResourceHeap]] %[[BufIdx]] + // CHECK: OpBufferPointerEXT + + // CHECK: %[[OutIdx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[OutDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTex2D]] %[[ResourceHeap]] %[[OutIdx]] + // CHECK: OpLoad %[[RWTex2DType]] %[[OutDesc]] + + // Literal sampler index. + // CHECK: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_2 + // CHECK: OpLoad %[[SamplerType]] + + float2 uv = float2(tid.xy) / 512.0; + + // CHECK: OpSampledImage + // CHECK: OpImageSampleExplicitLod + float4 color = ColorTex.SampleLevel(BilinearSamp, uv, 0); + + float4 data = DataBuf[tid.x]; + + // CHECK: OpImageWrite %{{[a-zA-Z0-9_]+}} + OutTex[tid.xy] = color + data; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl new file mode 100644 index 0000000000..bbf811e6ca --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl @@ -0,0 +1,28 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: BUFFER atomics on a heap-sourced RWStructuredBuffer +// lower through a heap access-chain + OpBufferPointerEXT into +// OpAtomicIAdd/OpAtomicCompareExchange on %uint. +// +// RWStructuredBuffer -> heap access-chain + OpBufferPointerEXT -> OpAtomicIAdd %uint +// RWStructuredBuffer -> heap access-chain + OpBufferPointerEXT -> OpAtomicCompareExchange %uint + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[numthreads(64, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWStructuredBuffer counter = ResourceDescriptorHeap[0]; + + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT + uint original; + // CHECK: OpAtomicIAdd %uint + InterlockedAdd(counter[0], 1, original); + + // CHECK: OpAtomicCompareExchange %uint + uint cmp; + InterlockedCompareExchange(counter[1], original, original + 1, cmp); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl new file mode 100644 index 0000000000..120830aa4c --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl @@ -0,0 +1,54 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: sampled (non-MS, non-cube) texture dimensionalities lower +// to the correct OpTypeImage dim flags and feed OpImageFetch. +// +// Texture1D -> OpTypeImage %float 1D 2 0 0 1 Unknown -> sampled +// Texture1DArray -> OpTypeImage %float 1D 2 1 0 1 Unknown -> sampled +// Texture2DArray -> OpTypeImage %float 2D 2 1 0 1 Unknown -> sampled +// Texture3D -> OpTypeImage %float 3D 2 0 0 1 Unknown -> sampled + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex1DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 0 0 1 Unknown +// CHECK-DAG: %[[Tex1DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 1 0 1 Unknown +// CHECK-DAG: %[[Tex2DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 1 0 1 Unknown +// CHECK-DAG: %[[Tex3DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 3D 2 0 0 1 Unknown + +// CHECK-DAG: %[[RA_Tex1D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex1DType]]{{$}} +// CHECK-DAG: %[[RA_Tex1DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex1DArrType]]{{$}} +// CHECK-DAG: %[[RA_Tex2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DArrType]]{{$}} +// CHECK-DAG: %[[RA_Tex3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex3DType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture1D tex1d = ResourceDescriptorHeap[0]; + // CHECK: %[[T1D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex1D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[T1D:[a-zA-Z0-9_]+]] = OpLoad %[[Tex1DType]] %[[T1D_Desc]] + + Texture1DArray tex1dArr = ResourceDescriptorHeap[1]; + // CHECK: %[[T1DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex1DArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[T1DA:[a-zA-Z0-9_]+]] = OpLoad %[[Tex1DArrType]] %[[T1DA_Desc]] + + Texture2DArray tex2dArr = ResourceDescriptorHeap[2]; + // CHECK: %[[T2DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2DArr]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[T2DA:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DArrType]] %[[T2DA_Desc]] + + Texture3D tex3d = ResourceDescriptorHeap[3]; + // CHECK: %[[T3D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex3D]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[T3D:[a-zA-Z0-9_]+]] = OpLoad %[[Tex3DType]] %[[T3D_Desc]] + + // CHECK: OpImageFetch %v4float %[[T1D]] + float4 v = tex1d.Load(int2(tid.x, 0)); + // CHECK: OpImageFetch %v4float %[[T1DA]] + v += tex1dArr.Load(int3(tid.x, 0, 0)); + // CHECK: OpImageFetch %v4float %[[T2DA]] + v += tex2dArr.Load(int4(tid.xy, 0, 0)); + // CHECK: OpImageFetch %v4float %[[T3D]] + v += tex3d.Load(int4(tid.xyz, 0)); + + output.Store(0, asuint(v.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl new file mode 100644 index 0000000000..edb2d78821 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl @@ -0,0 +1,33 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: multisampled image types (Texture2DMS `%float 2D 2 0 1 1` and Texture2DMSArray `2D 2 1 1 1`) +// sourced from the descriptor heap are loaded and drive OpImageFetch %v4float. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[MSType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 1 1 Unknown +// CHECK-DAG: %[[MSArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 1 1 1 Unknown + +// CHECK-DAG: %[[RA_MS:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSType]]{{$}} +// CHECK-DAG: %[[RA_MSArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSArrType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2DMS texMS = ResourceDescriptorHeap[0]; + // CHECK: %[[MS_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_MS]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[MS:[a-zA-Z0-9_]+]] = OpLoad %[[MSType]] %[[MS_Desc]] + + Texture2DMSArray texMSArr = ResourceDescriptorHeap[1]; + // CHECK: %[[MSArr_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_MSArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[MSArr:[a-zA-Z0-9_]+]] = OpLoad %[[MSArrType]] %[[MSArr_Desc]] + + // CHECK: OpImageFetch %v4float %[[MS]] + float4 v = texMS.Load(int2(tid.xy), 0); + // CHECK: OpImageFetch %v4float %[[MSArr]] + v += texMSArr.Load(int3(tid.xy, 0), 0); + + output.Store(0, asuint(v.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl new file mode 100644 index 0000000000..6b4297741e --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl @@ -0,0 +1,77 @@ +// RUN: %dxc -T ps_6_6 -E PSMain -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: reassigning both a Texture2D and a SamplerState across a +// dynamic index, a +2 index, and inside a conditional branch reloads +// the latest texture+sampler from BOTH the resource and sampler +// heaps per reassignment, producing a fresh +// OpSampledImage/OpImageSampleImplicitLod each time. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2D:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[Sampler:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[TexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2D]] +// CHECK-DAG: %[[SamplerArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Sampler]] +// CHECK-DAG: %[[SampledImage:[a-zA-Z0-9_]+]] = OpTypeSampledImage %[[Tex2D]] + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +struct PSInput +{ + float4 position : SV_Position; + float4 color : COLOR0; +}; + +float4 PSMain(PSInput input) : SV_Target0 +{ + uint texIdx = uint(input.color.x); + uint sampIdx = uint(input.color.y); + uint cond = uint(input.color.z); + + Texture2D myTexture = ResourceDescriptorHeap[texIdx]; + SamplerState samp = SamplerDescriptorHeap[sampIdx]; + + // CHECK: %[[TexIdx:[a-zA-Z0-9_]+]] = OpConvertFToU %uint + // CHECK: %[[SampIdx:[a-zA-Z0-9_]+]] = OpConvertFToU %uint + // CHECK: %[[Cond:[a-zA-Z0-9_]+]] = OpConvertFToU %uint + // CHECK: %[[TexDesc0:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[TexArray]] %[[ResourceHeap]] %[[TexIdx]] + // CHECK: %[[Tex0:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2D]] %[[TexDesc0]] + // CHECK: %[[SampDesc0:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SamplerArray]] %[[SamplerHeap]] %[[SampIdx]] + // CHECK: %[[Samp0:[a-zA-Z0-9_]+]] = OpLoad %[[Sampler]] %[[SampDesc0]] + // CHECK: %[[Sampled0:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImage]] %[[Tex0]] %[[Samp0]] + // CHECK: %[[Color0:[a-zA-Z0-9_]+]] = OpImageSampleImplicitLod %v4float %[[Sampled0]] + float4 color = myTexture.Sample(samp, float2(1,1)); + + myTexture = ResourceDescriptorHeap[texIdx + 2]; + samp = SamplerDescriptorHeap[sampIdx]; + + // CHECK: %[[TexIdxPlus2:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[TexIdx]] %uint_2 + // CHECK: %[[TexDesc1:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[TexArray]] %[[ResourceHeap]] %[[TexIdxPlus2]] + // CHECK: %[[Tex1:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2D]] %[[TexDesc1]] + // CHECK: %[[SampDesc1:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SamplerArray]] %[[SamplerHeap]] %[[SampIdx]] + // CHECK: %[[Samp1:[a-zA-Z0-9_]+]] = OpLoad %[[Sampler]] %[[SampDesc1]] + // CHECK: %[[Sampled1:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImage]] %[[Tex1]] %[[Samp1]] + // CHECK: %[[Color1:[a-zA-Z0-9_]+]] = OpImageSampleImplicitLod %v4float %[[Sampled1]] + // CHECK: %[[ColorSum:[a-zA-Z0-9_]+]] = OpFAdd %v4float %[[Color0]] %[[Color1]] + color += myTexture.Sample(samp, float2(2,2)); + + if (cond > 4) { + myTexture = ResourceDescriptorHeap[texIdx - 2]; + samp = SamplerDescriptorHeap[sampIdx + 2]; + + // CHECK: %[[CondCmp:[a-zA-Z0-9_]+]] = OpUGreaterThan %bool %[[Cond]] %uint_4 + // CHECK: OpBranchConditional %[[CondCmp]] + // CHECK: %[[TexIdxMinus2:[a-zA-Z0-9_]+]] = OpISub %uint %[[TexIdx]] %uint_2 + // CHECK: %[[TexDesc2:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[TexArray]] %[[ResourceHeap]] %[[TexIdxMinus2]] + // CHECK: %[[Tex2:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2D]] %[[TexDesc2]] + // CHECK: %[[SampIdxPlus2:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[SampIdx]] %uint_2 + // CHECK: %[[SampDesc2:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SamplerArray]] %[[SamplerHeap]] %[[SampIdxPlus2]] + // CHECK: %[[Samp2:[a-zA-Z0-9_]+]] = OpLoad %[[Sampler]] %[[SampDesc2]] + // CHECK: %[[Sampled2:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImage]] %[[Tex2]] %[[Samp2]] + // CHECK: %[[Color2:[a-zA-Z0-9_]+]] = OpImageSampleImplicitLod %v4float %[[Sampled2]] + // CHECK: OpFAdd %v4float %[[ColorSum]] %[[Color2]] + color += myTexture.Sample(samp, float2(2,2)); + } + + return color; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl new file mode 100644 index 0000000000..6d769383f6 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl @@ -0,0 +1,34 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: Buffer and RWBuffer from the heap lower to +// Buffer-dimension OpTypeImage (sampled vs storage), +// load a typed image handle, and drive OpImageFetch / OpImageWrite. +// +// Buffer -> OpTypeImage %float Buffer ... Sampled(1), handle OpLoad, OpImageFetch (Load) +// RWBuffer -> OpTypeImage %float Buffer ... Storage(2), handle OpLoad, OpImageWrite (store) + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[BufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 1 Rgba32f +// CHECK-DAG: %[[RWBufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 2 Rgba32f + +// CHECK-DAG: %[[RA_BufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferType]]{{$}} +// CHECK-DAG: %[[RA_RWBufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufferType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Buffer myBuf = ResourceDescriptorHeap[0]; + // CHECK: %[[BufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_BufferType]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[BufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[BufferType]] %[[BufIndex]] + + RWBuffer myRWBuf = ResourceDescriptorHeap[1]; + // CHECK: %[[RWBufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWBufferType]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[RWBufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufferType]] %[[RWBufIndex]] + + // CHECK: %[[BufResult:[a-zA-Z0-9_]+]] = OpImageFetch %v4float %[[BufHandle]] + float4 bufVal = myBuf.Load(tid.x); + + // CHECK: OpImageWrite %[[RWBufHandle]] + myRWBuf[tid.x] = bufVal; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl new file mode 100644 index 0000000000..237fed4aa9 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl @@ -0,0 +1,46 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: TextureCube and TextureCubeArray cube image types, +// each loaded from the resource heap and combined with a heap +// sampler to drive OpImageSampleExplicitLod. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[CubeType:[a-zA-Z0-9_]+]] = OpTypeImage %float Cube 2 0 0 1 Unknown +// CHECK-DAG: %[[CubeArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float Cube 2 1 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler + +// CHECK-DAG: %[[RA_Cube:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CubeType]]{{$}} +// CHECK-DAG: %[[RA_CubeArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CubeArrType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + TextureCube cube = ResourceDescriptorHeap[0]; + // CHECK: %[[CubeDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Cube]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[CubeHandle:[a-zA-Z0-9_]+]] = OpLoad %[[CubeType]] %[[CubeDesc]] + + TextureCubeArray cubeArr = ResourceDescriptorHeap[1]; + // CHECK: %[[CubeArrDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_CubeArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[CubeArrHandle:[a-zA-Z0-9_]+]] = OpLoad %[[CubeArrType]] %[[CubeArrDesc]] + + SamplerState samp = SamplerDescriptorHeap[0]; + // CHECK: %[[SampDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampDesc]] + + float3 dir = normalize(float3(tid)); + + // CHECK: %[[CubeSI:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[CubeHandle]] %[[SampHandle]] + // CHECK: OpImageSampleExplicitLod %v4float %[[CubeSI]] + float4 v = cube.SampleLevel(samp, dir, 0); + + // CHECK: %[[CubeArrSI:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[CubeArrHandle]] %[[SampHandle]] + // CHECK: OpImageSampleExplicitLod %v4float %[[CubeArrSI]] + v += cubeArr.SampleLevel(samp, float4(dir, 0), 0); + + output.Store(0, asuint(v.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl new file mode 100644 index 0000000000..4fb723c186 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl @@ -0,0 +1,59 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: each HLSL resource element type lowers to the +// correct OpTypeImage format and sampled-vs-storage mode +// when accessed through the descriptor heap. +// +// Texture2D -> OpTypeImage %uint 2D ... Unknown -> sampled +// RWTexture2D -> OpTypeImage %float 2D ... Rg32f -> storage +// RWTexture2D -> OpTypeImage %uint 2D ... Rg32ui -> storage +// RWTexture2D -> OpTypeImage %int 2D ... R32i -> storage + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant + +// CHECK-DAG: %[[TexUintType:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[RA_TexUint:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexUintType]]{{$}} + +// CHECK-DAG: %[[RWTexF2Type:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 2 Rg32f +// CHECK-DAG: %[[RA_RWTexF2:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexF2Type]]{{$}} + +// CHECK-DAG: %[[RWTexU2Type:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 2 Rg32ui +// CHECK-DAG: %[[RA_RWTexU2:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexU2Type]]{{$}} + +// CHECK-DAG: %[[RWTexIType:[a-zA-Z0-9_]+]] = OpTypeImage %int 2D 2 0 0 2 R32i +// CHECK-DAG: %[[RA_RWTexI:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexIType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2D texUint = ResourceDescriptorHeap[0]; + // CHECK: %[[TexUintChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_TexUint]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexUintH:[a-zA-Z0-9_]+]] = OpLoad %[[TexUintType]] %[[TexUintChain]] + + RWTexture2D rwTexF2 = ResourceDescriptorHeap[1]; + // CHECK: %[[RWTexF2Chain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTexF2]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[RWTexF2H:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexF2Type]] %[[RWTexF2Chain]] + + RWTexture2D rwTexU2 = ResourceDescriptorHeap[2]; + // CHECK: %[[RWTexU2Chain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTexU2]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[RWTexU2H:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexU2Type]] %[[RWTexU2Chain]] + + RWTexture2D rwTexI = ResourceDescriptorHeap[3]; + // CHECK: %[[RWTexIChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTexI]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[RWTexIH:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexIType]] %[[RWTexIChain]] + + // CHECK: OpImageFetch %v4uint %[[TexUintH]] + uint val = texUint.Load(int3(tid.xy, 0)).x; + + // CHECK: OpImageWrite %[[RWTexF2H]] + rwTexF2[tid.xy] = float2(val, val); + // CHECK: OpImageWrite %[[RWTexU2H]] + rwTexU2[tid.xy] = uint2(val, val); + // CHECK: OpImageWrite %[[RWTexIH]] + rwTexI[tid.xy] = int(val); + + output.Store(0, val); +} diff --git a/tools/clang/unittests/SPIRV/SpirvContextTest.cpp b/tools/clang/unittests/SPIRV/SpirvContextTest.cpp index a0f7a4b4c3..08d33e4892 100644 --- a/tools/clang/unittests/SPIRV/SpirvContextTest.cpp +++ b/tools/clang/unittests/SPIRV/SpirvContextTest.cpp @@ -324,6 +324,9 @@ TEST_F(SpirvContextTest, RuntimeArrayTypeUnique3) { EXPECT_NE(spvContext.getRuntimeArrayType(int32, 4), spvContext.getRuntimeArrayType(int32, llvm::None)); + + EXPECT_NE(spvContext.getRuntimeArrayType(int32, llvm::None), + spvContext.getRuntimeArrayType(int32, 32)); } TEST_F(SpirvContextTest, PointerTypeUnique1) { From b12b9ea5679e4b9e9d89421a6cf3e622a0e3bdf5 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:50 -0700 Subject: [PATCH 2/4] [SPIR-V] Add descriptor heap RaytracingAccelerationStructure support Extends the SPV_EXT_descriptor_heap native heap lowering to cover RaytracingAccelerationStructure resources loaded from ResourceDescriptorHeap. Acceleration structure descriptors are accessed via OpUntypedAccessChainKHR into a runtime array of OpTypeAccelerationStructureKHR, consistent with the image and sampler paths added in the previous commit. --- .../clang/include/clang/SPIRV/AstTypeProbe.h | 4 ++ tools/clang/lib/SPIRV/AstTypeProbe.cpp | 7 ++++ tools/clang/lib/SPIRV/SpirvEmitter.cpp | 10 +++++ ...riptorheap.ext.acceleration-structure.hlsl | 42 +++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl diff --git a/tools/clang/include/clang/SPIRV/AstTypeProbe.h b/tools/clang/include/clang/SPIRV/AstTypeProbe.h index 214df82d45..207d02a3b2 100644 --- a/tools/clang/include/clang/SPIRV/AstTypeProbe.h +++ b/tools/clang/include/clang/SPIRV/AstTypeProbe.h @@ -250,6 +250,10 @@ bool isBuffer(QualType type); /// \brief Returns true if the given type is the HLSL RWBuffer type. bool isRWBuffer(QualType type); +/// \brief Returns true if the given type is the HLSL +/// RaytracingAccelerationStructure type. +bool isRaytracingAccelerationStructure(QualType type); + /// \brief Returns true if the given type is an HLSL Texture type. bool isTexture(QualType); diff --git a/tools/clang/lib/SPIRV/AstTypeProbe.cpp b/tools/clang/lib/SPIRV/AstTypeProbe.cpp index c933a82c16..7a9f5e859c 100644 --- a/tools/clang/lib/SPIRV/AstTypeProbe.cpp +++ b/tools/clang/lib/SPIRV/AstTypeProbe.cpp @@ -917,6 +917,13 @@ bool isBuffer(QualType type) { return false; } +bool isRaytracingAccelerationStructure(QualType type) { + if (const auto *rt = type->getAs()) { + return rt->getDecl()->getName() == "RaytracingAccelerationStructure"; + } + return false; +} + bool isRWTexture(QualType type) { if (const auto *rt = type->getAs()) { const auto name = rt->getDecl()->getName(); diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index d967520fe2..28a906695b 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2039,6 +2039,16 @@ bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, return true; } + if (isRaytracingAccelerationStructure(decl->getType())) { + if (auto *initVal = loadIfGLValue(init)) + declIdMapper.registerFnVarAlias(decl, initVal); + else + emitError("cannot create descriptor heap acceleration structure alias " + "from initializer", + init->getExprLoc()); + return true; + } + return false; } diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl new file mode 100644 index 0000000000..7c5c9fdcb9 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl @@ -0,0 +1,42 @@ +// RUN: %dxc -T lib_6_6 -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 32 -fspv-extension=SPV_KHR_ray_tracing -fspv-extension=SPV_EXT_descriptor_heap -fspv-extension=SPV_KHR_untyped_pointers -spirv %s | FileCheck %s + +// Verifies: ResourceDescriptorHeap of a RaytracingAccelerationStructure +// lowers to an acceleration-structure runtime array, loads the accel +// handle from the heap, and emits OpTraceRayKHR under the ray-tracing +// capability/extension. + +// CHECK: OpCapability RayTracingKHR +// CHECK: OpExtension "SPV_KHR_ray_tracing" + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR +// CHECK-DAG: %[[ASArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +struct Payload { + float4 color; +}; + +struct Attribute { + float2 bary; +}; + +[shader("closesthit")] +void main(inout Payload payload, in Attribute attr) { + RaytracingAccelerationStructure scene = ResourceDescriptorHeap[3]; + + RayDesc ray; + ray.Origin = float3(0.0f, 0.0f, 0.0f); + ray.Direction = float3(0.0f, 0.0f, -1.0f); + ray.TMin = 0.0f; + ray.TMax = 1000.0f; + + Payload childPayload = { float4(attr.bary, 0.0f, 1.0f) }; + + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[ASArray]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[Scene:[a-zA-Z0-9_]+]] = OpLoad %[[Accel]] %[[Desc]] + // CHECK: OpTraceRayKHR %[[Scene]] + TraceRay(scene, 0x0, 0xff, 0, 1, 0, ray, childPayload); + + payload.color = childPayload.color; +} From 5e7b92867bcc06685a98d00d69085122edda562b Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:51 -0700 Subject: [PATCH 3/4] [SPIR-V] Add descriptor heap -fvk-resource-heap-stride / -fvk-sampler-heap-stride CLI flags Adds two new command-line flags that override the ArrayStride of the descriptor heap runtime arrays emitted by -fspv-use-descriptor-heap. -fvk-resource-heap-stride and -fvk-sampler-heap-stride sets the stride for ResourceDescriptorHeap SamplerDescriptorHeap arrays respectively. N and M must be a power of two in [8, 256]. When set, the CLI value takes the highest precedence. --- docs/SPIR-V.rst | 28 ++++++- include/dxc/Support/HLSLOptions.td | 14 ++++ include/dxc/Support/SPIRVOptions.h | 8 ++ lib/DxcSupport/HLSLOptions.cpp | 50 ++++++++++++- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 25 ++++++- ...sm6_6.descriptorheap.ext.array-stride.hlsl | 50 +++++++++++++ ...descriptorheap.ext.stride-cli-permute.hlsl | 74 +++++++++++++++++++ .../sm6_6.descriptorheap.ext.stride-cli.hlsl | 34 +++++++++ 8 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl 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/docs/SPIR-V.rst b/docs/SPIR-V.rst index 70f1f934c3..b59cff73c2 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2101,7 +2101,8 @@ 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 and decorates the array with a byte ``ArrayStride``, then uses +descriptor type, decorates the array with a byte ``ArrayStride`` (the stride is +configurable; see `Descriptor heap array stride`_ below), and uses ``OpUntypedAccessChainKHR`` followed by ``OpLoad``: .. code:: spirv @@ -2126,6 +2127,31 @@ example, ``ConstantBuffer`` uses ``Uniform`` and ``TextureBuffer`` uses %descriptor = OpUntypedAccessChainKHR %uptr_uc %buffer_array %resource_heap %index %buffer_ptr = OpBufferPointerEXT %_ptr_Uniform_type_BufferData %descriptor +Descriptor heap array stride +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``ArrayStride`` of each heap runtime array defaults to 64 bytes for the +resource heap and 32 bytes for the sampler heap. The stride +can be overridden, in increasing order of precedence: + +- ``[[vk::resource_heap_stride_constant_id(id)]]`` and + ``[[vk::sampler_heap_stride_constant_id(id)]]`` on a ``uint`` global emit the + stride as a specialization constant decorated ``ArrayStrideIdEXT`` (an ````) + instead of a literal, letting the application override it at pipeline creation + through ``VkSpecializationInfo``. The attribute initializer supplies the default + value and must be a power of two in [8, 256]. This attribute is mutually + exclusive with ``[[vk::constant_id]]`` on the same declaration. +- ``-fvk-resource-heap-stride `` and ``-fvk-sampler-heap-stride `` + emit a fixed literal ``OpDecorate ... ArrayStride N`` on the resource and + sampler heap arrays respectively. ``N`` must be a power of two in the inclusive + range [8, 256]. The command-line override has the **highest** precedence: when + set, the matching ``[[vk::*_heap_stride_constant_id]]`` attribute is ignored + (DXC emits a warning at the attribute and no ``ArrayStrideIdEXT`` is emitted for + that heap) and the literal stride is used. + +So the command-line literal takes precedence over the spec-constant attribute, +which in turn takes precedence over the built-in defaults. + For ``RWTexture`` resources loaded from ``ResourceDescriptorHeap``, interlocked operations that need a texel pointer use ``OpUntypedImageTexelPointerEXT``. The image descriptor pointer produced by ``OpUntypedAccessChainKHR`` is passed diff --git a/include/dxc/Support/HLSLOptions.td b/include/dxc/Support/HLSLOptions.td index 034214938c..a8ffee930d 100644 --- a/include/dxc/Support/HLSLOptions.td +++ b/include/dxc/Support/HLSLOptions.td @@ -454,6 +454,20 @@ 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 0253caba63..5cf6fe6345 100644 --- a/include/dxc/Support/SPIRVOptions.h +++ b/include/dxc/Support/SPIRVOptions.h @@ -110,6 +110,14 @@ 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 takes the + // highest precedence: it overrides any [[vk::*_heap_stride_constant_id]] + // spec-constant attribute (which is suppressed with a warning). + 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 1630243aab..72b17886f5 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,15 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, return 1; } + if (!handleHeapStride(Args, OPT_fvk_resource_heap_stride, + &opts.SpirvOptions.resourceHeapStride, + "-fvk-resource-heap-stride", errors) || + !handleHeapStride(Args, OPT_fvk_sampler_heap_stride, + &opts.SpirvOptions.samplerHeapStride, + "-fvk-sampler-heap-stride", errors)) { + return 1; + } + for (const Arg *A : Args.filtered(OPT_fspv_extension_EQ)) { opts.SpirvOptions.allowedExtensions.push_back(A->getValue()); } @@ -1316,7 +1362,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 28a906695b..c3a3e3ef02 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -9244,11 +9244,32 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { const SpirvType * SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, bool onSamplerHeap) { + // Precedence (highest first): command-line literal override > + // [[vk::*_heap_stride_constant_id]] spec-constant attribute > built-in + // default. + + // 1. A -fvk-resource-heap-stride / -fvk-sampler-heap-stride command-line + // override is a fixed literal stride, validated (power of 2 in [8, 256]) at + // option-parsing time in HLSLOptions.cpp. It has the highest precedence: when + // set, the matching stride attribute is suppressed (with a warning) in + // create{Resource,Sampler}HeapStrideConstant, so getSamplerHeapStride() / + // getResourceHeapStride() is already empty here. + const std::optional &cliStride = + onSamplerHeap ? spirvOptions.samplerHeapStride + : spirvOptions.resourceHeapStride; + if (cliStride.has_value()) + return spvContext.getRuntimeArrayType(elemType, *cliStride); + + // 3. Default ArrayStride (in bytes) for the descriptor-heap runtime arrays + // when no override (command line or attribute) is supplied. + // + // These bound the largest descriptor a heap entry can hold on the target HW. constexpr uint32_t kDefaultResourceHeapStride = 64; constexpr uint32_t kDefaultSamplerHeapStride = 32; - const uint32_t stride = + + const uint32_t defaultStride = onSamplerHeap ? kDefaultSamplerHeapStride : kDefaultResourceHeapStride; - return spvContext.getRuntimeArrayType(elemType, stride); + return spvContext.getRuntimeArrayType(elemType, defaultStride); } SpirvInstruction * diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl new file mode 100644 index 0000000000..5de38fed50 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl @@ -0,0 +1,50 @@ +// 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 + +// Verifies: each distinct descriptor element type yields exactly one OpTypeRuntimeArray, +// and each array gets the correct literal ArrayStride based on CLI. +// +// SB / RWSB / BAB -> OpTypeRuntimeArray %[[SBDesc]] (OpTypeBufferEXT StorageBuffer) -> ArrayStride 64 +// ConstantBuffer -> OpTypeRuntimeArray %[[UBDesc]] (OpTypeBufferEXT Uniform) -> ArrayStride 64 +// Texture2D -> OpTypeRuntimeArray %[[Tex2DType]] (OpTypeImage sampled) -> ArrayStride 64 +// RWTexture2D -> OpTypeRuntimeArray %[[RWTexType]] (OpTypeImage storage) -> ArrayStride 64 +// SamplerState -> OpTypeRuntimeArray %[[SamplerType]] (OpTypeSampler) -> ArrayStride 32 + +// CHECK-DAG: %[[SBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[UBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[RWTexType:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 2 R32ui +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler + +// CHECK-DAG: %[[SBArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBDesc]]{{$}} +// CHECK-DAG: %[[UBArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[UBDesc]]{{$}} +// CHECK-DAG: %[[Tex2DArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RWTexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexType]]{{$}} +// CHECK-DAG: %[[SamplerArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +// CHECK-DAG: OpDecorate %[[SBArray]] ArrayStride 64 +// CHECK-DAG: OpDecorate %[[UBArray]] ArrayStride 64 +// CHECK-DAG: OpDecorate %[[Tex2DArray]] ArrayStride 64 +// CHECK-DAG: OpDecorate %[[RWTexArray]] ArrayStride 64 +// CHECK-DAG: OpDecorate %[[SamplerArray]] ArrayStride 32 + +struct CBData { uint value; }; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + StructuredBuffer sb = ResourceDescriptorHeap[0]; + RWStructuredBuffer rwsb = ResourceDescriptorHeap[1]; + ByteAddressBuffer bab = ResourceDescriptorHeap[2]; + ConstantBuffer cb = ResourceDescriptorHeap[3]; + Texture2D tex = ResourceDescriptorHeap[4]; + RWTexture2D rwtex = ResourceDescriptorHeap[5]; + SamplerState samp = SamplerDescriptorHeap[0]; + + uint bufVal = sb.Load(tid.x) + bab.Load(tid.x * 4) + cb.value; + rwsb[tid.x] = bufVal; + float4 texVal = tex.SampleLevel(samp, float2(0, 0), 0); + uint rwOrig; + InterlockedAdd(rwtex[tid.xy], 1, rwOrig); + outputBytes.Store(0, rwsb.Load(tid.x) + asuint(texVal.r) + rwOrig); +} 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 0000000000..a76c9fa77a --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl @@ -0,0 +1,74 @@ +// 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 + +// 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' + +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 0000000000..340df5ed7c --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl @@ -0,0 +1,34 @@ +// 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 --check-prefix=CLI +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -spirv -DWITHATTR %s 2>&1 | FileCheck %s --check-prefix=WARN +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -spirv -DWITHATTR %s | FileCheck %s --check-prefix=OVERRIDE + +// Verifies: a command-line literal stride has the HIGHEST precedence. +// 1) -fvk-resource-heap-stride / -fvk-sampler-heap-stride emit a literal ArrayStride. +// 2) the CLI value BEATS a [[vk::*_heap_stride_constant_id]] attribute. +// 3) the overridden attribute is ignored (with a warning); no ArrayStrideIdEXT. + +// ---- Command-line literal strides, no attribute ---- +// CLI-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 +// CLI-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 +// CLI-NOT: ArrayStrideIdEXT + +// ---- Attribute present but overridden by the command line ---- +// WARN: warning: {{.*}}resource_heap_stride_constant_id{{.*}} is ignored because -fvk-resource-heap-stride + +// OVERRIDE: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 +// OVERRIDE-NOT: ArrayStrideIdEXT + +#ifdef WITHATTR +[[vk::resource_heap_stride_constant_id(2)]] const uint kResourceHeapStride = 64; +#endif + +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 901ceaedc16621bb99bc479f75bd44dca72cc4dc Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:52 -0700 Subject: [PATCH 4/4] [SPIR-V] Add descriptor heap stride specialization constant attribute Adds [[vk::resource_heap_stride_constant_id(id)]] and [[vk::sampler_heap_stride_constant_id(id)]] attributes that emit the descriptor heap ArrayStride as a SPIR-V specialization constant using ArrayStrideIdEXT, allowing applications to override the stride at pipeline creation time via VkSpecializationInfo without recompiling the shader. The attribute initializer supplies the default stride value and must be a power of two in [8, 256]. The CLI flags -fvk-resource-heap-stride and -fvk-sampler-heap-stride take higher precedence and suppress these attributes with a warning when both are specified. --- tools/clang/include/clang/Basic/Attr.td | 18 ++ .../clang/include/clang/SPIRV/SpirvContext.h | 6 +- tools/clang/include/clang/SPIRV/SpirvType.h | 12 +- tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 16 ++ tools/clang/lib/SPIRV/DeclResultIdMapper.h | 28 +++ tools/clang/lib/SPIRV/EmitVisitor.cpp | 12 +- tools/clang/lib/SPIRV/SpirvContext.cpp | 7 +- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 200 ++++++++++++++++++ tools/clang/lib/SPIRV/SpirvEmitter.h | 33 ++- tools/clang/lib/SPIRV/SpirvType.cpp | 1 + tools/clang/lib/Sema/SemaHLSL.cpp | 12 ++ ...torheap.ext.stride-spec-const-coexist.hlsl | 36 ++++ ...iptorheap.ext.stride-spec-const.error.hlsl | 72 +++++++ ....descriptorheap.ext.stride-spec-const.hlsl | 52 +++++ 14 files changed, 491 insertions(+), 14 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const-coexist.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.hlsl diff --git a/tools/clang/include/clang/Basic/Attr.td b/tools/clang/include/clang/Basic/Attr.td index 89c9a1b022..6eee762e14 100644 --- a/tools/clang/include/clang/Basic/Attr.td +++ b/tools/clang/include/clang/Basic/Attr.td @@ -1611,6 +1611,24 @@ def VKConstantId : InheritableAttr { let Documentation = [Undocumented]; } +def VKResourceHeapStrideConstantId : InheritableAttr { + let Spellings = [CXX11<"vk", "resource_heap_stride_constant_id">]; + let Subjects = + SubjectList<[ScalarGlobalVar], ErrorDiag, "ExpectedScalarGlobalVar">; + let Args = [IntArgument<"SpecConstId">]; + let LangOpts = [SPIRV]; + let Documentation = [Undocumented]; +} + +def VKSamplerHeapStrideConstantId : InheritableAttr { + let Spellings = [CXX11<"vk", "sampler_heap_stride_constant_id">]; + let Subjects = + SubjectList<[ScalarGlobalVar], ErrorDiag, "ExpectedScalarGlobalVar">; + let Args = [IntArgument<"SpecConstId">]; + let LangOpts = [SPIRV]; + let Documentation = [Undocumented]; +} + def VKPostDepthCoverage : InheritableAttr { let Spellings = [CXX11<"vk", "post_depth_coverage">]; let Subjects = SubjectList<[Function], ErrorDiag>; diff --git a/tools/clang/include/clang/SPIRV/SpirvContext.h b/tools/clang/include/clang/SPIRV/SpirvContext.h index 8a49f961ca..24cb8213c2 100644 --- a/tools/clang/include/clang/SPIRV/SpirvContext.h +++ b/tools/clang/include/clang/SPIRV/SpirvContext.h @@ -89,7 +89,8 @@ struct RuntimeArrayTypeMapInfo { static inline RuntimeArrayType *getTombstoneKey() { return nullptr; } static unsigned getHashValue(const RuntimeArrayType *Val) { return llvm::hash_combine(Val->getElementType(), - Val->getStride().hasValue()); + Val->getStride().hasValue(), + Val->getStrideSpecConst()); } static bool isEqual(const RuntimeArrayType *LHS, const RuntimeArrayType *RHS) { @@ -284,7 +285,8 @@ class SpirvContext { llvm::Optional arrayStride); const RuntimeArrayType * getRuntimeArrayType(const SpirvType *elemType, - llvm::Optional arrayStride); + llvm::Optional arrayStride, + SpirvInstruction *strideSpecConst = nullptr); const NodePayloadArrayType * getNodePayloadArrayType(const SpirvType *elemType, const ParmVarDecl *nodeDecl); diff --git a/tools/clang/include/clang/SPIRV/SpirvType.h b/tools/clang/include/clang/SPIRV/SpirvType.h index 654dad509f..c2a109b544 100644 --- a/tools/clang/include/clang/SPIRV/SpirvType.h +++ b/tools/clang/include/clang/SPIRV/SpirvType.h @@ -23,6 +23,7 @@ namespace clang { namespace spirv { class HybridType; +class SpirvInstruction; enum class StructInterfaceType : uint32_t { InternalStorage = 0, @@ -274,9 +275,10 @@ class ArrayType : public SpirvType { class RuntimeArrayType : public SpirvType { public: RuntimeArrayType(const SpirvType *elemType, - llvm::Optional arrayStride) - : SpirvType(TK_RuntimeArray), elementType(elemType), stride(arrayStride) { - } + llvm::Optional arrayStride, + SpirvInstruction *strideSpecConst = nullptr) + : SpirvType(TK_RuntimeArray), elementType(elemType), stride(arrayStride), + strideSpecConst(strideSpecConst) {} static bool classof(const SpirvType *t) { return t->getKind() == TK_RuntimeArray; @@ -286,12 +288,16 @@ class RuntimeArrayType : public SpirvType { const SpirvType *getElementType() const { return elementType; } llvm::Optional getStride() const { return stride; } + SpirvInstruction *getStrideSpecConst() const { return strideSpecConst; } private: const SpirvType *elementType; // Two runtime arrays with different ArrayStride decorations, are in fact two // different types. If no layout information is needed, use llvm::None. + // Ignored when strideSpecConst is non-null (the spec-const wins). llvm::Optional stride; + // When non-null, ArrayStrideIdEXT %id is emitted instead of ArrayStride N. + SpirvInstruction *strideSpecConst; }; class NodePayloadArrayType : public SpirvType { diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 62e5ff1e3e..aebe7de867 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -1889,6 +1889,22 @@ void DeclResultIdMapper::registerSpecConstant(const VarDecl *decl, SpirvInstruction *specConstant) { specConstant->setRValue(); registerVariableForDecl(decl, createDeclSpirvInfo(specConstant)); + if (decl->hasAttr()) { + resourceHeapStride = HeapStrideSpecConst{ + specConstant, + static_cast( + decl->getAttr() + ->getSpecConstId()), + decl}; + } else if (decl->hasAttr()) { + samplerHeapStride = HeapStrideSpecConst{ + specConstant, + static_cast(decl->getAttr() + ->getSpecConstId()), + decl}; + } else if (const auto *attr = decl->getAttr()) { + userSpecConstIdMap[attr->getSpecConstId()] = decl; + } } void DeclResultIdMapper::createCounterVar( diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.h b/tools/clang/lib/SPIRV/DeclResultIdMapper.h index d6ae75e8fa..3b729588ba 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.h +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.h @@ -296,6 +296,29 @@ class DeclResultIdMapper { /// |type|. SpirvVariableLike *createResourceHeap(const VarDecl *var, QualType type); + /// Records the [[vk::*_heap_stride_constant_id]] spec constant for one heap. + /// Present iff such an attribute was declared in the translation unit. + struct HeapStrideSpecConst { + SpirvInstruction *specConst; // the OpSpecConstant + uint32_t specId; // its SpecId + const VarDecl *decl; // declaring var (diagnostics) + }; + + /// Returns the resource/sampler heap stride spec constant, or None if no + /// [[vk::*_heap_stride_constant_id]] was declared. + const llvm::Optional &getResourceHeapStride() const { + return resourceHeapStride; + } + const llvm::Optional &getSamplerHeapStride() const { + return samplerHeapStride; + } + /// Returns the user [[vk::constant_id]] VarDecl that owns \p specId, or + /// nullptr if no user spec-const has claimed that ID. + const VarDecl *getUserSpecConstForId(uint32_t specId) const { + auto it = userSpecConstIdMap.find(specId); + return it != userSpecConstIdMap.end() ? it->second : nullptr; + } + /// \brief Creates an external-visible variable and returns its instruction. SpirvVariable *createExternVar(const VarDecl *var); @@ -1058,6 +1081,11 @@ class DeclResultIdMapper { SpirvUntypedVariableKHR *ResourceHeapVar = nullptr; SpirvUntypedVariableKHR *SamplerHeapVar = nullptr; + llvm::Optional resourceHeapStride; + llvm::Optional samplerHeapStride; + /// Maps SpecId -> VarDecl for user [[vk::constant_id]] declarations, used to + /// detect SpecId collisions with heap-stride attributes. + llvm::DenseMap userSpecConstIdMap; /// Mapping from {RW|Append|Consume}StructuredBuffers to their /// counter variables' (instr-ptr, is-alias-or-not) pairs diff --git a/tools/clang/lib/SPIRV/EmitVisitor.cpp b/tools/clang/lib/SPIRV/EmitVisitor.cpp index 75d081ee62..1253effadf 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.cpp +++ b/tools/clang/lib/SPIRV/EmitVisitor.cpp @@ -2695,9 +2695,15 @@ uint32_t EmitTypeHandler::emitType(const SpirvType *type) { curTypeInst.push_back(elemTypeId); finalizeTypeInstruction(); - auto stride = raType->getStride(); - if (stride.hasValue()) - emitDecoration(id, spv::Decoration::ArrayStride, {stride.getValue()}); + if (auto *sc = raType->getStrideSpecConst()) { + const uint32_t scId = getOrAssignResultId(sc); + emitDecoration(id, spv::Decoration::ArrayStrideIdEXT, {scId}, llvm::None, + /*usesIdParams=*/true); + } else { + auto stride = raType->getStride(); + if (stride.hasValue()) + emitDecoration(id, spv::Decoration::ArrayStride, {stride.getValue()}); + } } // NodePayloadArray types else if (const auto *npaType = dyn_cast(type)) { diff --git a/tools/clang/lib/SPIRV/SpirvContext.cpp b/tools/clang/lib/SPIRV/SpirvContext.cpp index a3397d74b4..49f899bf79 100644 --- a/tools/clang/lib/SPIRV/SpirvContext.cpp +++ b/tools/clang/lib/SPIRV/SpirvContext.cpp @@ -268,14 +268,15 @@ SpirvContext::getArrayType(const SpirvType *elemType, uint32_t elemCount, const RuntimeArrayType * SpirvContext::getRuntimeArrayType(const SpirvType *elemType, - llvm::Optional arrayStride) { - RuntimeArrayType type(elemType, arrayStride); + llvm::Optional arrayStride, + SpirvInstruction *strideSpecConst) { + RuntimeArrayType type(elemType, arrayStride, strideSpecConst); auto found = runtimeArrayTypes.find(&type); if (found != runtimeArrayTypes.end()) return *found; auto inserted = runtimeArrayTypes.insert( - new (this) RuntimeArrayType(elemType, arrayStride)); + new (this) RuntimeArrayType(elemType, arrayStride, strideSpecConst)); return *(inserted.first); } diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index c3a3e3ef02..0b9e312d79 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2141,6 +2141,16 @@ void SpirvEmitter::doVarDecl(const VarDecl *decl) { return; } + if (decl->hasAttr()) { + createResourceHeapStrideConstant(decl); + return; + } + + if (decl->hasAttr()) { + createSamplerHeapStrideConstant(decl); + return; + } + if (decl->hasAttr()) { // This is a VarDecl for PushConstant block. (void)declIdMapper.createPushConstant(decl); @@ -9185,6 +9195,20 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { bool *modeSlot; }; + if (varDecl->hasAttr()) { + emitError( + "[[vk::resource_heap_stride_constant_id]] and [[vk::constant_id]] " + "are mutually exclusive; remove one", + varDecl->getLocation()); + return; + } + if (varDecl->hasAttr()) { + emitError("[[vk::sampler_heap_stride_constant_id]] and [[vk::constant_id]] " + "are mutually exclusive; remove one", + varDecl->getLocation()); + return; + } + const QualType varType = varDecl->getType(); bool hasError = false; @@ -9260,6 +9284,15 @@ SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, if (cliStride.has_value()) return spvContext.getRuntimeArrayType(elemType, *cliStride); + // 2. A [[vk::*_heap_stride_constant_id]] spec-constant attribute emits + // ArrayStrideIdEXT (an ) so the app can override the stride at pipeline + // creation. + const auto &heapStride = onSamplerHeap ? declIdMapper.getSamplerHeapStride() + : declIdMapper.getResourceHeapStride(); + if (heapStride.hasValue()) + return spvContext.getRuntimeArrayType(elemType, llvm::None, + heapStride->specConst); + // 3. Default ArrayStride (in bytes) for the descriptor-heap runtime arrays // when no override (command line or attribute) is supplied. // @@ -9272,6 +9305,173 @@ SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, return spvContext.getRuntimeArrayType(elemType, defaultStride); } +bool SpirvEmitter::checkHeapStrideSpecIdConflict( + const VarDecl *varDecl, uint32_t specConstId, llvm::StringRef attrName, + const llvm::Optional + &otherStride) { + if (otherStride.hasValue() && otherStride->specId == specConstId) { + emitError( + "SpecId %0 conflict: [[vk::resource_heap_stride_constant_id]] and " + "[[vk::sampler_heap_stride_constant_id]] must use different SpecIds", + varDecl->getLocation()) + << specConstId; + return true; + } + if (const VarDecl *prev = declIdMapper.getUserSpecConstForId(specConstId)) { + emitError("SpecId %0 conflict: [[vk::constant_id]] on '%1' and " + "[[vk::%2]] share the same SpecId; each must be unique", + varDecl->getLocation()) + << specConstId << prev->getName() << attrName; + emitNote("[[vk::constant_id]] declaration with SpecId %0", + prev->getLocation()) + << specConstId; + return true; + } + return false; +} + +void SpirvEmitter::createResourceHeapStrideConstant(const VarDecl *varDecl) { + // Coexistence with [[vk::constant_id]] is already rejected: doVarDecl + // dispatches VKConstantId to createSpecConstant first, which emits the + // mutual-exclusion error before we get here. + assert(!varDecl->hasAttr() && + "VKConstantId must be handled by createSpecConstant"); + // -fvk-resource-heap-stride has higher precedence (see + // getDescriptorHeapRuntimeArrayType). When set, this attribute is ignored: + // skip emitting the spec constant so the module carries no dead, + // SpecId-decorated constant. + if (spirvOptions.resourceHeapStride.has_value()) { + emitWarning("[[vk::resource_heap_stride_constant_id]] is ignored because " + "-fvk-resource-heap-stride overrides the resource heap stride", + varDecl->getLocation()); + return; + } + if (const auto &prevStride = declIdMapper.getResourceHeapStride()) { + emitError("[[vk::resource_heap_stride_constant_id]] may only appear once " + "per translation unit; previous declaration here", + varDecl->getLocation()); + emitNote("previous [[vk::resource_heap_stride_constant_id]] declaration", + prevStride->decl->getLocation()); + return; + } + const uint32_t specConstId = + varDecl->getAttr()->getSpecConstId(); + if (checkHeapStrideSpecIdConflict(varDecl, specConstId, + "resource_heap_stride_constant_id", + declIdMapper.getSamplerHeapStride())) + return; + createDescriptorHeapStrideConstant(varDecl, specConstId, + "resource_heap_stride_constant_id"); +} + +void SpirvEmitter::createSamplerHeapStrideConstant(const VarDecl *varDecl) { + // Coexistence with [[vk::constant_id]] is already rejected: doVarDecl + // dispatches VKConstantId to createSpecConstant first, which emits the + // mutual-exclusion error before we get here. + assert(!varDecl->hasAttr() && + "VKConstantId must be handled by createSpecConstant"); + // -fvk-sampler-heap-stride has higher precedence (see + // getDescriptorHeapRuntimeArrayType). When set, this attribute is ignored: + // skip emitting the spec constant so the module carries no dead, + // SpecId-decorated constant. + if (spirvOptions.samplerHeapStride.has_value()) { + emitWarning("[[vk::sampler_heap_stride_constant_id]] is ignored because " + "-fvk-sampler-heap-stride overrides the sampler heap stride", + varDecl->getLocation()); + return; + } + if (const auto &prevStride = declIdMapper.getSamplerHeapStride()) { + emitError("[[vk::sampler_heap_stride_constant_id]] may only appear once " + "per translation unit; previous declaration here", + varDecl->getLocation()); + emitNote("previous [[vk::sampler_heap_stride_constant_id]] declaration", + prevStride->decl->getLocation()); + return; + } + const uint32_t specConstId = + varDecl->getAttr()->getSpecConstId(); + if (checkHeapStrideSpecIdConflict(varDecl, specConstId, + "sampler_heap_stride_constant_id", + declIdMapper.getResourceHeapStride())) + return; + createDescriptorHeapStrideConstant(varDecl, specConstId, + "sampler_heap_stride_constant_id"); +} + +void SpirvEmitter::createDescriptorHeapStrideConstant( + const VarDecl *varDecl, uint32_t specConstId, llvm::StringRef attrName) { + class SpecConstantEnvRAII { + public: + SpecConstantEnvRAII(bool *mode) : modeSlot(mode) { *modeSlot = true; } + ~SpecConstantEnvRAII() { *modeSlot = false; } + + private: + bool *modeSlot; + }; + + bool hasError = false; + + if (!spirvOptions.useDescriptorHeap) { + emitError("[[vk::%0]] requires -fspv-use-descriptor-heap; without it the " + "attribute has no effect", + varDecl->getLocation()) + << attrName; + hasError = true; + } + + // Global-scalar placement is already enforced by the ScalarGlobalVar + // attribute subject (Attr.td), identical to [[vk::constant_id]]; no re-check + // needed here. We only narrow scalar -> uint, which the subject does not + // constrain. + const auto *builtinType = varDecl->getType()->getAs(); + if (!builtinType || builtinType->getKind() != BuiltinType::UInt) { + emitError("[[vk::%0]] variable must be 'uint'; got '%1'", + varDecl->getLocStart()) + << attrName + << varDecl->getType().getUnqualifiedType().getAsString( + astContext.getPrintingPolicy()); + hasError = true; + } + + const auto *init = varDecl->getInit(); + if (!init) { + emitError("[[vk::%0]] variable requires an initializer (pipeline " + "specialization constant default)", + varDecl->getLocation()) + << attrName; + hasError = true; + } else if (!isAcceptedSpecConstantInit(init, astContext)) { + emitError("unsupported [[vk::%0]] initializer", init->getLocStart()) + << attrName; + hasError = true; + } else { + llvm::APSInt val; + if (init->EvaluateAsInt(val, astContext)) { + const uint64_t stride = val.getZExtValue(); + if ((stride & (stride - 1)) != 0 || stride < 8 || stride > 256) { + emitError("[[vk::%0]] default value %1 is invalid; must be a power of " + "2 between 8 and 256 (inclusive)", + init->getLocStart()) + << attrName << static_cast(stride); + hasError = true; + } + } + } + + if (hasError) + return; + + SpecConstantEnvRAII specConstantEnvRAII(&isSpecConstantMode); + + auto *specConstant = + constEvaluator.tryToEvaluateAsConst(init, isSpecConstantMode); + + spvBuilder.decorateSpecId(specConstant, specConstId, varDecl->getLocation()); + + specConstant->setDebugName(varDecl->getName()); + declIdMapper.registerSpecConstant(varDecl, specConstant); +} + SpirvInstruction * SpirvEmitter::processMatrixBinaryOp(const Expr *lhs, const Expr *rhs, const BinaryOperatorKind opcode, diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 76c174eab4..96361aa99a 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -399,9 +399,36 @@ 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 the default ArrayStride (64 bytes for the resource heap, - /// 32 bytes for the sampler heap). + /// Translates the given varDecl into a descriptor heap stride specialization + /// constant. The registered spec constant is later emitted as an + /// ArrayStrideIdEXT decoration on every resource/sampler heap runtime array. + void createResourceHeapStrideConstant(const VarDecl *varDecl); + void createSamplerHeapStrideConstant(const VarDecl *varDecl); + + /// Shared implementation for createResourceHeapStrideConstant and + /// createSamplerHeapStrideConstant. Validates the varDecl, emits an + /// OpSpecConstant decorated with specConstId, and registers it. + /// attrName is used verbatim in diagnostics (e.g. + /// "resource_heap_stride_constant_id"). + void createDescriptorHeapStrideConstant(const VarDecl *varDecl, + uint32_t specConstId, + llvm::StringRef attrName); + + /// Emits a diagnostic and returns true if specConstId collides with the + /// other heap-stride attribute (otherStride) or a user [[vk::constant_id]]. + /// attrName names the attribute being declared. Caller must return early + /// when this returns true. Shared by + /// create{Resource,Sampler}HeapStrideConstant. + bool checkHeapStrideSpecIdConflict( + const VarDecl *varDecl, uint32_t specConstId, llvm::StringRef attrName, + const llvm::Optional + &otherStride); + + /// Returns the OpTypeRuntimeArray for a descriptor-heap array of elemType. + /// If a stride spec constant was declared for the relevant heap + /// (sampler heap when onSamplerHeap, otherwise resource heap), the array + /// carries an ArrayStrideIdEXT decoration; otherwise it uses the literals + /// kDefaultSamplerHeapStride / kDefaultResourceHeapStride. const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, bool onSamplerHeap); diff --git a/tools/clang/lib/SPIRV/SpirvType.cpp b/tools/clang/lib/SPIRV/SpirvType.cpp index caa4f6d52b..537a542de2 100644 --- a/tools/clang/lib/SPIRV/SpirvType.cpp +++ b/tools/clang/lib/SPIRV/SpirvType.cpp @@ -168,6 +168,7 @@ bool ArrayType::operator==(const ArrayType &that) const { bool RuntimeArrayType::operator==(const RuntimeArrayType &that) const { return elementType == that.elementType && + strideSpecConst == that.strideSpecConst && stride.hasValue() == that.stride.hasValue() && (!stride.hasValue() || stride.getValue() == that.stride.getValue()); } diff --git a/tools/clang/lib/Sema/SemaHLSL.cpp b/tools/clang/lib/Sema/SemaHLSL.cpp index 2067ebaf26..c08802bbc1 100644 --- a/tools/clang/lib/Sema/SemaHLSL.cpp +++ b/tools/clang/lib/Sema/SemaHLSL.cpp @@ -14989,6 +14989,16 @@ void hlsl::HandleDeclAttributeForHLSL(Sema &S, Decl *D, const AttributeList &A, VKConstantIdAttr(A.getRange(), S.Context, ValidateAttributeIntArg(S, A), A.getAttributeSpellingListIndex()); break; + case AttributeList::AT_VKResourceHeapStrideConstantId: + declAttr = ::new (S.Context) VKResourceHeapStrideConstantIdAttr( + A.getRange(), S.Context, ValidateAttributeIntArg(S, A), + A.getAttributeSpellingListIndex()); + break; + case AttributeList::AT_VKSamplerHeapStrideConstantId: + declAttr = ::new (S.Context) VKSamplerHeapStrideConstantIdAttr( + A.getRange(), S.Context, ValidateAttributeIntArg(S, A), + A.getAttributeSpellingListIndex()); + break; case AttributeList::AT_VKPostDepthCoverage: declAttr = ::new (S.Context) VKPostDepthCoverageAttr( A.getRange(), S.Context, A.getAttributeSpellingListIndex()); @@ -16769,6 +16779,8 @@ bool hlsl::IsHLSLAttr(clang::attr::Kind AttrKind) { case clang::attr::VKBinding: case clang::attr::VKBuiltIn: case clang::attr::VKConstantId: + case clang::attr::VKResourceHeapStrideConstantId: + case clang::attr::VKSamplerHeapStrideConstantId: case clang::attr::VKCounterBinding: case clang::attr::VKIndex: case clang::attr::VKInputAttachmentIndex: diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const-coexist.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const-coexist.hlsl new file mode 100644 index 0000000000..cdfdea672c --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const-coexist.hlsl @@ -0,0 +1,36 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: Heap-stride spec constants coexist with a user [[vk::constant_id]] without +// SpecId collision. All three emit distinct OpSpecConstant instructions, and +// the user spec constant remains usable in shader arithmetic (OpSpecConstantOp). + +// CHECK-DAG: OpDecorate %[[USC:[a-zA-Z0-9_]+]] SpecId 0 +// CHECK-DAG: OpDecorate %[[RSC:[a-zA-Z0-9_]+]] SpecId 5 +// CHECK-DAG: OpDecorate %[[SSC:[a-zA-Z0-9_]+]] SpecId 6 +// CHECK-DAG: %[[USC]] = OpSpecConstant %uint 16 +// CHECK-DAG: %[[RSC]] = OpSpecConstant %uint 64 +// CHECK-DAG: %[[SSC]] = OpSpecConstant %uint 32 + +// Heap arrays use their respective stride spec constants +// CHECK-DAG: OpDecorateId %{{[a-zA-Z0-9_]+}} ArrayStrideIdEXT %[[RSC]] +// CHECK-DAG: OpDecorateId %{{[a-zA-Z0-9_]+}} ArrayStrideIdEXT %[[SSC]] + +// The user spec constant still participates in constant folding +// CHECK: OpSpecConstantOp %uint IMul %[[USC]] + +[[vk::constant_id(0)]] const uint TILE_SIZE = 16; +[[vk::resource_heap_stride_constant_id(5)]] const uint kResourceHeapStride = 64; +[[vk::sampler_heap_stride_constant_id(6)]] const uint kSamplerHeapStride = 32; + +RWByteAddressBuffer outputBytes : register(u0); + +static const uint kTileArea = TILE_SIZE * TILE_SIZE; // forces OpSpecConstantOp + +[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) + kTileArea); + outputBytes.Store(4, (uint)tex.SampleLevel(samp, float2(0, 0), 0).r); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.error.hlsl new file mode 100644 index 0000000000..3eb0b631bc --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.error.hlsl @@ -0,0 +1,72 @@ +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-target-env=vulkan1.3 -spirv -DNOHEAPFLAG %s 2>&1 | FileCheck %s --check-prefix=NOHEAPFLAG +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCOMBINED %s 2>&1 | FileCheck %s --check-prefix=COMBINED +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DSPECID_USER %s 2>&1 | FileCheck %s --check-prefix=SPECID_USER +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DSPECID_RS %s 2>&1 | FileCheck %s --check-prefix=SPECID_RS +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DDUPLICATE %s 2>&1 | FileCheck %s --check-prefix=DUPLICATE +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DNODEFAULT %s 2>&1 | FileCheck %s --check-prefix=NODEFAULT +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DNOTPOW2 %s 2>&1 | FileCheck %s --check-prefix=NOTPOW2 +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DNONUINT %s 2>&1 | FileCheck %s --check-prefix=NONUINT +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DNOTGLOBAL %s 2>&1 | FileCheck %s --check-prefix=NOTGLOBAL + +// Verifies: every misuse of the descriptor-heap stride spec-constant attributes emits its specific diagnostic. +// Diagnostics for the descriptor-heap stride spec-constant attributes. Each RUN +// activates exactly one case via -D because DXC stops codegen after the first +// error. The NOHEAPFLAG case intentionally omits -fspv-use-descriptor-heap. + +// NOHEAPFLAG: error: {{.*resource_heap_stride_constant_id.*}} requires -fspv-use-descriptor-heap; without it the attribute has no effect +// COMBINED: error: {{.*resource_heap_stride_constant_id.*}} and {{.*constant_id.*}} are mutually exclusive; remove one +// SPECID_USER: error: SpecId {{[0-9]+}} conflict: {{.*constant_id.*}}share the same SpecId +// SPECID_RS: error: SpecId 3 conflict: {{.*resource_heap_stride_constant_id.*}} and {{.*sampler_heap_stride_constant_id.*}} must use different SpecIds +// DUPLICATE: error: {{.*resource_heap_stride_constant_id.*}} may only appear once per translation unit; previous declaration here +// NODEFAULT: error: {{.*resource_heap_stride_constant_id.*}} variable requires an initializer (pipeline specialization constant default) +// NOTPOW2: error: {{.*resource_heap_stride_constant_id.*}} default value 48 is invalid; must be a power of 2 between 8 and 256 (inclusive) +// NONUINT: error: {{.*resource_heap_stride_constant_id.*}} variable must be 'uint'; got 'float' +// NOTGLOBAL: error: {{.*resource_heap_stride_constant_id.*}} attribute only applies to global variables of scalar type + +#ifdef NOHEAPFLAG +[[vk::resource_heap_stride_constant_id(0)]] const uint kStride = 64; +#endif + +#ifdef COMBINED +[[vk::constant_id(2)]] +[[vk::resource_heap_stride_constant_id(2)]] const uint kStride = 64; +#endif + +#ifdef SPECID_USER +[[vk::constant_id(5)]] const uint TILE_SIZE = 16; +[[vk::resource_heap_stride_constant_id(5)]] const uint kStride = 64; +#endif + +#ifdef SPECID_RS +[[vk::resource_heap_stride_constant_id(3)]] const uint kResourceStride = 64; +[[vk::sampler_heap_stride_constant_id(3)]] const uint kSamplerStride = 32; +#endif + +#ifdef DUPLICATE +[[vk::resource_heap_stride_constant_id(0)]] const uint kStride1 = 64; +[[vk::resource_heap_stride_constant_id(1)]] const uint kStride2 = 32; +#endif + +#ifdef NODEFAULT +[[vk::resource_heap_stride_constant_id(0)]] const uint kStride; +#endif + +#ifdef NOTPOW2 +[[vk::resource_heap_stride_constant_id(0)]] const uint kStride = 48; +#endif + +#ifdef NONUINT +[[vk::resource_heap_stride_constant_id(0)]] const float kStride = 64.0f; +#endif + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main() { +#ifdef NOTGLOBAL + [[vk::resource_heap_stride_constant_id(0)]] const uint kLocal = 64; + outputBytes.Store(0, kLocal); +#else + outputBytes.Store(0, 0); +#endif +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.hlsl new file mode 100644 index 0000000000..6b90caf9e4 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-spec-const.hlsl @@ -0,0 +1,52 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s --check-prefix=NOLITERAL + +// Verifies: stride-constant-id attributes turn each heap's ArrayStride into a spec-constant . +// +// resource_heap_stride_constant_id(2) -> OpSpecConstant %uint 64 + SpecId 2 +// sampler_heap_stride_constant_id(3) -> OpSpecConstant %uint 32 + SpecId 3 +// resource spec const -> OpDecorateId ArrayStrideIdEXT on every resource-heap array +// sampler spec const -> OpDecorateId ArrayStrideIdEXT on sampler-heap array +// both heaps overridden -> no literal ArrayStride survives on any heap runtime array + +// ---- One spec constant per heap, with the requested SpecId and default ---- +// CHECK-DAG: OpDecorate %[[RSC:[a-zA-Z0-9_]+]] SpecId 2 +// CHECK-DAG: OpDecorate %[[SSC:[a-zA-Z0-9_]+]] SpecId 3 +// CHECK-DAG: %[[RSC]] = OpSpecConstant %uint 64 +// CHECK-DAG: %[[SSC]] = OpSpecConstant %uint 32 + +// ---- ArrayStrideIdEXT references the spec-constant , never a literal ---- +// Resource heap: StructuredBuffer/ByteAddressBuffer array, ConstantBuffer array, image array. +// CHECK-DAG: OpDecorateId %[[SBArr:[a-zA-Z0-9_]+]] ArrayStrideIdEXT %[[RSC]] +// CHECK-DAG: OpDecorateId %[[UBArr:[a-zA-Z0-9_]+]] ArrayStrideIdEXT %[[RSC]] +// CHECK-DAG: OpDecorateId %[[TexArr:[a-zA-Z0-9_]+]] ArrayStrideIdEXT %[[RSC]] +// Sampler heap. +// CHECK-DAG: OpDecorateId %[[SampArr:[a-zA-Z0-9_]+]] ArrayStrideIdEXT %[[SSC]] + +// ---- Both heaps overridden => no literal ArrayStride on any heap runtime array ---- +// The NOLITERAL run (second RUN line) uses only a NOT directive, so FileCheck scans +// the entire output. "_runtimearr_type" infix targets only heap runtime arrays; +// "_runtimearr_uint ArrayStride 4" (RWByteAddressBuffer internal array) does not +// match and is not a false positive. The positive CHECK-DAGs above guard the +// primary regression (ArrayStrideIdEXT present); this guards double-decoration. +// NOLITERAL-NOT: _runtimearr_type{{[a-zA-Z0-9_]+}} ArrayStride {{[0-9]+}} + +struct CBData { uint value; }; + +[[vk::resource_heap_stride_constant_id(2)]] const uint kResourceHeapStride = 64; +[[vk::sampler_heap_stride_constant_id(3)]] const uint kSamplerHeapStride = 32; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + StructuredBuffer sb = ResourceDescriptorHeap[0]; + ByteAddressBuffer bab = ResourceDescriptorHeap[1]; + ConstantBuffer cb = ResourceDescriptorHeap[2]; + Texture2D tex = ResourceDescriptorHeap[3]; + SamplerState samp = SamplerDescriptorHeap[0]; + + uint v = sb.Load(tid.x) + bab.Load(tid.x * 4) + cb.value; + outputBytes.Store(0, v); + outputBytes.Store(4, (uint)tex.SampleLevel(samp, float2(0, 0), 0).r); +}