From 2647b5d51d68739d2fbdbee0ec7b640d1e4ad1c4 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Mon, 10 Aug 2026 14:19:43 -0500 Subject: [PATCH 01/11] [SPIRV] Cleanup string matching in SPIRV generator This cleans up a bunch of places in the SPIRV codegenerator where we were relying on string matching of data types, which is extremely fragile. We've played a bit of wack-a-mole on fixing one-off issues here and there as we find them, but this is a more complete fix. There's still some cases with ROV types and a few other stray bits here and there, but this cleans up most of the SPIRV code. Assisted by Copilot --- tools/clang/include/clang/AST/HlslTypes.h | 8 + tools/clang/include/clang/Basic/Attr.td | 10 + tools/clang/lib/AST/HlslTypes.cpp | 12 + tools/clang/lib/SPIRV/AstTypeProbe.cpp | 306 ++++++------------ tools/clang/lib/SPIRV/LowerTypeVisitor.cpp | 171 ++++++---- tools/clang/lib/Sema/SemaHLSL.cpp | 6 + ...ser-defined-type.builtin-name.buffers.hlsl | 32 ++ ...er-defined-type.builtin-name.rayquery.hlsl | 26 ++ ...er-defined-type.builtin-name.textures.hlsl | 48 +++ 9 files changed, 336 insertions(+), 283 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.rayquery.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl diff --git a/tools/clang/include/clang/AST/HlslTypes.h b/tools/clang/include/clang/AST/HlslTypes.h index cc72c51048..3524f567dd 100644 --- a/tools/clang/include/clang/AST/HlslTypes.h +++ b/tools/clang/include/clang/AST/HlslTypes.h @@ -520,6 +520,14 @@ bool IsHLSLRayQueryType(clang::QualType type); bool GetHLSLNodeIORecordType(const clang::ParmVarDecl *parmDecl, NodeFlags &nodeKind); +/// Returns the DXIL ResourceKind for this type, or ResourceKind::Invalid if +/// not an HLSL resource type. +DXIL::ResourceKind GetHLSLResourceKind(clang::QualType type); + +/// Returns the DXIL ResourceClass for this type, or ResourceClass::Invalid if +/// not an HLSL resource type. +DXIL::ResourceClass GetHLSLResourceClass(clang::QualType type); + bool IsArrayConstantStringType(const clang::QualType type); bool IsPointerStringType(const clang::QualType type); bool IsStringType(const clang::QualType type); diff --git a/tools/clang/include/clang/Basic/Attr.td b/tools/clang/include/clang/Basic/Attr.td index 21b57c8934..31e0feaa93 100644 --- a/tools/clang/include/clang/Basic/Attr.td +++ b/tools/clang/include/clang/Basic/Attr.td @@ -1205,6 +1205,16 @@ def HLSLHitObject : InheritableAttr { let Documentation = [Undocumented]; } +// Vulkan SubpassInput / SubpassInputMS Attribute +// Marks the built-in SubpassInput and SubpassInputMS record types as +// distinct from any user-defined type with the same unqualified name. +def HLSLVkSubpassInput : InheritableAttr { + let Spellings = []; // No spellings! + let Args = [BoolArgument<"IsMultiSampled">]; + let Subjects = SubjectList<[CXXRecord]>; + let Documentation = [Undocumented]; +} + // HLSL Dynamic Resource Attribute // Marks the builtin `.Resource` / `.Sampler` placeholder record types used // for descriptor heap indexing (ResourceDescriptorHeap[i] / diff --git a/tools/clang/lib/AST/HlslTypes.cpp b/tools/clang/lib/AST/HlslTypes.cpp index eeed7fe8bf..da26d5b72f 100644 --- a/tools/clang/lib/AST/HlslTypes.cpp +++ b/tools/clang/lib/AST/HlslTypes.cpp @@ -615,6 +615,18 @@ bool IsHLSLStructuredBufferType(clang::QualType type) { return false; } +DXIL::ResourceKind GetHLSLResourceKind(clang::QualType type) { + if (const HLSLResourceAttr *Attr = getAttr(type)) + return Attr->getResKind(); + return DXIL::ResourceKind::Invalid; +} + +DXIL::ResourceClass GetHLSLResourceClass(clang::QualType type) { + if (const HLSLResourceAttr *Attr = getAttr(type)) + return Attr->getResClass(); + return DXIL::ResourceClass::Invalid; +} + bool IsHLSLSubobjectType(clang::QualType type) { DXIL::SubobjectKind kind; DXIL::HitGroupType hgType; diff --git a/tools/clang/lib/SPIRV/AstTypeProbe.cpp b/tools/clang/lib/SPIRV/AstTypeProbe.cpp index a871bd3577..09460b7317 100644 --- a/tools/clang/lib/SPIRV/AstTypeProbe.cpp +++ b/tools/clang/lib/SPIRV/AstTypeProbe.cpp @@ -297,30 +297,24 @@ bool isMxNMatrix(QualType type, QualType *elemType, uint32_t *numRows, } bool isInputPatch(QualType type) { - if (const auto *rt = type->getAs()) - return rt->getDecl()->getName() == "InputPatch"; - - return false; + return hlsl::IsHLSLInputPatchType(type); } bool isOutputPatch(QualType type) { - if (const auto *rt = type->getAs()) - return rt->getDecl()->getName() == "OutputPatch"; - - return false; + return hlsl::IsHLSLOutputPatchType(type); } bool isSubpassInput(QualType type) { - if (const auto *rt = type->getAs()) - return rt->getDecl()->getName() == "SubpassInput"; - + if (const HLSLVkSubpassInputAttr *Attr = + getAttr(type)) + return !Attr->getIsMultiSampled(); return false; } bool isSubpassInputMS(QualType type) { - if (const auto *rt = type->getAs()) - return rt->getDecl()->getName() == "SubpassInputMS"; - + if (const HLSLVkSubpassInputAttr *Attr = + getAttr(type)) + return Attr->getIsMultiSampled(); return false; } @@ -339,22 +333,14 @@ bool isConstantBuffer(clang::QualType type) { // Strip outer arrayness first while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - if (const RecordType *RT = type->getAs()) { - StringRef name = RT->getDecl()->getName(); - return name == "ConstantBuffer"; - } - return false; + return hlsl::GetHLSLResourceKind(type) == hlsl::DXIL::ResourceKind::CBuffer; } bool isTextureBuffer(clang::QualType type) { // Strip outer arrayness first while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - if (const RecordType *RT = type->getAs()) { - StringRef name = RT->getDecl()->getName(); - return name == "TextureBuffer"; - } - return false; + return hlsl::GetHLSLResourceKind(type) == hlsl::DXIL::ResourceKind::TBuffer; } bool isConstantTextureBuffer(QualType type) { @@ -882,67 +868,45 @@ bool isStructuredBuffer(QualType type) { const auto *recordType = type->getAs(); if (!recordType) return false; + if (!hlsl::IsHLSLStructuredBufferType(type)) + return false; + // Exclude AppendStructuredBuffer and ConsumeStructuredBuffer, which share + // the same ResourceKind but have distinct access semantics. const auto name = recordType->getDecl()->getName(); - return name == "StructuredBuffer" || name == "RWStructuredBuffer" || - name == "RasterizerOrderedStructuredBuffer"; + return name != "AppendStructuredBuffer" && name != "ConsumeStructuredBuffer"; } bool isNonWritableStructuredBuffer(QualType type) { - const auto *recordType = type->getAs(); - if (!recordType) - return false; - const auto name = recordType->getDecl()->getName(); - return name == "StructuredBuffer"; + return hlsl::IsHLSLStructuredBufferType(type) && + hlsl::GetHLSLResourceClass(type) == hlsl::DXIL::ResourceClass::SRV; } bool isByteAddressBuffer(QualType type) { - if (const auto *rt = type->getAs()) { - return rt->getDecl()->getName() == "ByteAddressBuffer"; - } - return false; + return hlsl::GetHLSLResourceKind(type) == + hlsl::DXIL::ResourceKind::RawBuffer && + hlsl::GetHLSLResourceClass(type) == hlsl::DXIL::ResourceClass::SRV; } bool isRWBuffer(QualType type) { - if (const auto *rt = type->getAs()) { - const auto name = rt->getDecl()->getName(); - return name == "RWBuffer" || name == "RasterizerOrderedBuffer"; - } - return false; + return hlsl::GetHLSLResourceKind(type) == + hlsl::DXIL::ResourceKind::TypedBuffer && + hlsl::GetHLSLResourceClass(type) == hlsl::DXIL::ResourceClass::UAV; } bool isBuffer(QualType type) { - if (const auto *rt = type->getAs()) { - return rt->getDecl()->getName() == "Buffer"; - } - return false; + return hlsl::GetHLSLResourceKind(type) == + hlsl::DXIL::ResourceKind::TypedBuffer && + hlsl::GetHLSLResourceClass(type) == hlsl::DXIL::ResourceClass::SRV; } bool isRWTexture(QualType type) { - if (const auto *rt = type->getAs()) { - const auto name = rt->getDecl()->getName(); - if (name == "RWTexture1D" || name == "RWTexture1DArray" || - name == "RWTexture2D" || name == "RWTexture2DArray" || - name == "RWTexture3D" || name == "RasterizerOrderedTexture1D" || - name == "RasterizerOrderedTexture1DArray" || - name == "RasterizerOrderedTexture2D" || - name == "RasterizerOrderedTexture2DArray" || - name == "RasterizerOrderedTexture3D") - return true; - } - return false; + return hlsl::DXIL::IsAnyTexture(hlsl::GetHLSLResourceKind(type)) && + hlsl::GetHLSLResourceClass(type) == hlsl::DXIL::ResourceClass::UAV; } bool isTexture(QualType type) { - if (const auto *rt = type->getAs()) { - const auto name = rt->getDecl()->getName(); - if (name == "Texture1D" || name == "Texture1DArray" || - name == "Texture2D" || name == "Texture2DArray" || - name == "Texture2DMS" || name == "Texture2DMSArray" || - name == "TextureCube" || name == "TextureCubeArray" || - name == "Texture3D") - return true; - } - return false; + return hlsl::DXIL::IsAnyTexture(hlsl::GetHLSLResourceKind(type)) && + hlsl::GetHLSLResourceClass(type) == hlsl::DXIL::ResourceClass::SRV; } bool isSampledTexture(QualType type) { @@ -954,12 +918,9 @@ bool isSampledTexture(QualType type) { } bool isTextureMS(QualType type) { - if (const auto *rt = type->getAs()) { - const auto name = rt->getDecl()->getName(); - if (name == "Texture2DMS" || name == "Texture2DMSArray") - return true; - } - return false; + auto resKind = hlsl::GetHLSLResourceKind(type); + return resKind == hlsl::DXIL::ResourceKind::Texture2DMS || + resKind == hlsl::DXIL::ResourceKind::Texture2DMSArray; } bool isSampledTextureMS(QualType type) { @@ -975,58 +936,51 @@ bool isSampledTextureMS(QualType type) { } bool isSampler(QualType type) { - if (const auto *rt = type->getAs()) { - const auto name = rt->getDecl()->getName(); - if (name == "SamplerState" || name == "SamplerComparisonState") - return true; - } - return false; + return hlsl::GetHLSLResourceKind(type) == hlsl::DXIL::ResourceKind::Sampler; } bool isRWByteAddressBuffer(QualType type) { - if (const auto *rt = type->getAs()) { - const auto name = rt->getDecl()->getName(); - return name == "RWByteAddressBuffer" || - name == "RasterizerOrderedByteAddressBuffer"; - } - return false; + return hlsl::GetHLSLResourceKind(type) == + hlsl::DXIL::ResourceKind::RawBuffer && + hlsl::GetHLSLResourceClass(type) == hlsl::DXIL::ResourceClass::UAV; } bool isAppendStructuredBuffer(QualType type) { // Strip outer arrayness first while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - - const auto *recordType = type->getAs(); - if (!recordType) + if (!hlsl::IsHLSLStructuredBufferType(type)) return false; - const auto name = recordType->getDecl()->getName(); - return name == "AppendStructuredBuffer"; + const auto *recordType = type->getAs(); + return recordType && + recordType->getDecl()->getName() == "AppendStructuredBuffer"; } bool isConsumeStructuredBuffer(QualType type) { // Strip outer arrayness first while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - - const auto *recordType = type->getAs(); - if (!recordType) + if (!hlsl::IsHLSLStructuredBufferType(type)) return false; - const auto name = recordType->getDecl()->getName(); - return name == "ConsumeStructuredBuffer"; + const auto *recordType = type->getAs(); + return recordType && + recordType->getDecl()->getName() == "ConsumeStructuredBuffer"; } bool isRWStructuredBuffer(QualType type) { // Strip outer arrayness first while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - - if (const RecordType *recordType = type->getAs()) { - StringRef name = recordType->getDecl()->getName(); - return name == "RWStructuredBuffer" || - name == "RasterizerOrderedStructuredBuffer"; - } - return false; + if (!hlsl::IsHLSLStructuredBufferType(type)) + return false; + if (hlsl::GetHLSLResourceClass(type) != hlsl::DXIL::ResourceClass::UAV) + return false; + // Exclude AppendStructuredBuffer and ConsumeStructuredBuffer. + const auto *recordType = type->getAs(); + if (!recordType) + return false; + const auto name = recordType->getDecl()->getName(); + return name != "AppendStructuredBuffer" && name != "ConsumeStructuredBuffer"; } bool isRWAppendConsumeSBuffer(QualType type) { @@ -1059,33 +1013,27 @@ bool isAKindOfStructuredOrByteBuffer(QualType type) { while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - if (const RecordType *recordType = type->getAs()) { - StringRef name = recordType->getDecl()->getName(); - return name == "StructuredBuffer" || name == "RWStructuredBuffer" || - name == "RasterizerOrderedStructuredBuffer" || - name == "ByteAddressBuffer" || name == "RWByteAddressBuffer" || - name == "RasterizerOrderedByteAddressBuffer" || - name == "AppendStructuredBuffer" || - name == "ConsumeStructuredBuffer" || name == ".Resource" || - name == ".Sampler"; - } - return false; + // Use attribute-based check to avoid false positives with user-defined types + // that share names with HLSL built-in types. + auto resKind = hlsl::GetHLSLResourceKind(type); + if (resKind == hlsl::DXIL::ResourceKind::StructuredBuffer || + resKind == hlsl::DXIL::ResourceKind::RawBuffer) + return true; + + // Dynamic resource placeholder types (.Resource and .Sampler) also behave + // like structured buffers for layout purposes. + return hlsl::IsHLSLDynamicResourceType(type) || + hlsl::IsHLSLDynamicSamplerType(type); } bool isOrContainsAKindOfStructuredOrByteBuffer(QualType type) { while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - if (const RecordType *recordType = type->getAs()) { - StringRef name = recordType->getDecl()->getName(); - if (name == "StructuredBuffer" || name == "RWStructuredBuffer" || - name == "RasterizerOrderedStructuredBuffer" || - name == "ByteAddressBuffer" || name == "RWByteAddressBuffer" || - name == "RasterizerOrderedByteAddressBuffer" || - name == "AppendStructuredBuffer" || name == "ConsumeStructuredBuffer" || - name == ".Resource" || name == ".Sampler") - return true; + if (isAKindOfStructuredOrByteBuffer(type)) + return true; + if (const RecordType *recordType = type->getAs()) { for (const auto *field : recordType->getDecl()->fields()) { if (isOrContainsAKindOfStructuredOrByteBuffer(field->getType())) return true; @@ -1103,51 +1051,10 @@ bool isOrContainsAKindOfStructuredOrByteBuffer(QualType type) { } bool isOpaqueType(QualType type) { - if (const auto *recordType = type->getAs()) { - const auto name = recordType->getDecl()->getName(); - - if (name == "Texture1D" || name == "RWTexture1D" || - name == "RasterizerOrderedTexture1D") - return true; - if (name == "Texture2D" || name == "RWTexture2D" || - name == "RasterizerOrderedTexture2D") - return true; - if (name == "Texture2DMS" || name == "RWTexture2DMS") - return true; - if (name == "Texture3D" || name == "RWTexture3D" || - name == "RasterizerOrderedTexture3D") - return true; - if (name == "TextureCube" || name == "RWTextureCube") - return true; - - if (name == "Texture1DArray" || name == "RWTexture1DArray" || - name == "RasterizerOrderedTexture1DArray") - return true; - if (name == "Texture2DArray" || name == "RWTexture2DArray" || - name == "RasterizerOrderedTexture2DArray") - return true; - if (name == "Texture2DMSArray" || name == "RWTexture2DMSArray") - return true; - if (name == "TextureCubeArray" || name == "RWTextureCubeArray") - return true; - - if (name == "Buffer" || name == "RWBuffer" || - name == "RasterizerOrderedBuffer") - return true; - - if (name == "SamplerState" || name == "SamplerComparisonState") - return true; - - if (name == "RaytracingAccelerationStructure") - return true; - - if (name == "SubpassInput") - return true; - } - - if (hlsl::IsHLSLRayQueryType(type)) + if (hlsl::IsHLSLResourceType(type) || hlsl::IsHLSLRayQueryType(type)) + return true; + if (isSubpassInput(type)) return true; - return false; } @@ -1159,33 +1066,14 @@ std::string getHlslResourceTypeName(QualType type) { while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); - if (const RecordType *recordType = type->getAs()) { - StringRef name = recordType->getDecl()->getName(); - if (name == "StructuredBuffer" || name == "RWStructuredBuffer" || - name == "RasterizerOrderedStructuredBuffer" || - name == "ByteAddressBuffer" || name == "RWByteAddressBuffer" || - name == "RasterizerOrderedByteAddressBuffer" || - name == "AppendStructuredBuffer" || name == "ConsumeStructuredBuffer" || - name == "Texture1D" || name == "Texture2D" || name == "Texture3D" || - name == "TextureCube" || name == "Texture1DArray" || - name == "Texture2DArray" || name == "Texture2DMS" || - name == "Texture2DMSArray" || name == "TextureCubeArray" || - name == "RWTexture1D" || name == "RWTexture2D" || - name == "RWTexture3D" || name == "RWTexture1DArray" || - name == "RWTexture2DArray" || name == "RasterizerOrderedTexture1D" || - name == "RasterizerOrderedTexture1DArray" || - name == "RasterizerOrderedTexture2D" || - name == "RasterizerOrderedTexture2DArray" || - name == "RasterizerOrderedTexture3D" || name == "Buffer" || - name == "RWBuffer" || name == "RasterizerOrderedBuffer" || - name == "SubpassInput" || name == "SubpassInputMS" || - name == "InputPatch" || name == "OutputPatch" || - name == "ConstantBuffer" || name == "TextureBuffer" || - name == "RaytracingAccelerationStructure") { - // Get resource type name with template params. Operation is safe because - // type has already been null checked. - return type.getLocalUnqualifiedType().getAsString(); - } + // Use attribute-based checks to identify HLSL resource types. + if (hlsl::IsHLSLResourceType(type) || hlsl::IsHLSLRayQueryType(type) || + hlsl::IsHLSLInputPatchType(type) || hlsl::IsHLSLOutputPatchType(type) || + hlsl::IsHLSLStreamOutputType(type) || isSubpassInput(type) || + isSubpassInputMS(type)) { + // Get resource type name with template params. Operation is safe because + // type has already been null checked. + return type.getLocalUnqualifiedType().getAsString(); } return ""; @@ -1250,21 +1138,11 @@ bool isRelaxedPrecisionType(QualType type, const SpirvCodeGenOptions &opts) { } // Images with RelaxedPrecision sampled type. - if (const auto *recordType = type->getAs()) { - const llvm::StringRef name = recordType->getDecl()->getName(); - if (name == "Texture1D" || name == "Texture2D" || name == "Texture3D" || - name == "TextureCube" || name == "Texture1DArray" || - name == "Texture2DArray" || name == "Texture2DMS" || - name == "Texture2DMSArray" || name == "TextureCubeArray" || - name == "RWTexture1D" || name == "RWTexture2D" || - name == "RWTexture3D" || name == "RWTexture1DArray" || - name == "RWTexture2DArray" || name == "RasterizerOrderedTexture1D" || - name == "RasterizerOrderedTexture1DArray" || - name == "RasterizerOrderedTexture2D" || - name == "RasterizerOrderedTexture2DArray" || - name == "RasterizerOrderedTexture3D" || name == "Buffer" || - name == "RWBuffer" || name == "RasterizerOrderedBuffer" || - name == "SubpassInput" || name == "SubpassInputMS") { + { + auto resKind = hlsl::GetHLSLResourceKind(type); + if (hlsl::DXIL::IsAnyTexture(resKind) || + resKind == hlsl::DXIL::ResourceKind::TypedBuffer || + isSubpassInput(type) || isSubpassInputMS(type)) { const auto sampledType = hlsl::GetHLSLResourceResultType(type); return isRelaxedPrecisionType(sampledType, opts); } @@ -1286,16 +1164,14 @@ bool isRasterizerOrderedView(QualType type) { while (type->isArrayType()) type = type->getAsArrayTypeUnsafe()->getElementType(); + // ROV types are UAV resources; only built-in HLSL types have HLSLResourceAttr + // so this check prevents false positives from user-defined types. + if (hlsl::GetHLSLResourceClass(type) != hlsl::DXIL::ResourceClass::UAV) + return false; + if (const RecordType *recordType = type->getAs()) { StringRef name = recordType->getDecl()->getName(); - return name == "RasterizerOrderedBuffer" || - name == "RasterizerOrderedByteAddressBuffer" || - name == "RasterizerOrderedStructuredBuffer" || - name == "RasterizerOrderedTexture1D" || - name == "RasterizerOrderedTexture1DArray" || - name == "RasterizerOrderedTexture2D" || - name == "RasterizerOrderedTexture2DArray" || - name == "RasterizerOrderedTexture3D"; + return name.startswith("RasterizerOrdered"); } return false; } diff --git a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp index b330d40d85..1bf83fd41c 100644 --- a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp +++ b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp @@ -901,76 +901,112 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, return lowerVkTypeInVkNamespace(type, name, rule, isRowMajor, srcLoc); } - // TODO: avoid string comparison once hlsl::IsHLSLResouceType() does that. + const hlsl::DXIL::ResourceKind resKind = hlsl::GetHLSLResourceKind(type); + const hlsl::DXIL::ResourceClass resClass = hlsl::GetHLSLResourceClass(type); { // Texture types spv::Dim dim = {}; - bool isArray = {}; - if ((dim = spv::Dim::Dim1D, isArray = false, name == "Texture1D") || - (dim = spv::Dim::Dim2D, isArray = false, name == "Texture2D") || - (dim = spv::Dim::Dim3D, isArray = false, name == "Texture3D") || - (dim = spv::Dim::Cube, isArray = false, name == "TextureCube") || - (dim = spv::Dim::Dim1D, isArray = true, name == "Texture1DArray") || - (dim = spv::Dim::Dim2D, isArray = true, name == "Texture2DArray") || - (dim = spv::Dim::Dim2D, isArray = false, name == "Texture2DMS") || - (dim = spv::Dim::Dim2D, isArray = true, name == "Texture2DMSArray") || - // There is no Texture3DArray - (dim = spv::Dim::Cube, isArray = true, name == "TextureCubeArray")) { - const bool isMS = (name == "Texture2DMS" || name == "Texture2DMSArray"); - const auto sampledType = hlsl::GetHLSLResourceResultType(type); - auto loweredType = - lowerType(getElementType(astContext, sampledType), rule, - /*isRowMajor*/ llvm::None, srcLoc); - // Bool does not have a defined size in SPIR-V, so it cannot be - // used in the external interface. - if (loweredType == spvContext.getBoolType()) { - loweredType = spvContext.getUIntType(32); - } - return spvContext.getImageType( - loweredType, dim, ImageType::WithDepth::Unknown, isArray, isMS, - ImageType::WithSampler::Yes, spv::ImageFormat::Unknown); + bool isArray = false; + bool isTexture = false; + switch (resKind) { + case hlsl::DXIL::ResourceKind::Texture1D: + dim = spv::Dim::Dim1D; + isArray = false; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::Texture2D: + dim = spv::Dim::Dim2D; + isArray = false; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::Texture2DMS: + dim = spv::Dim::Dim2D; + isArray = false; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::Texture3D: + dim = spv::Dim::Dim3D; + isArray = false; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::TextureCube: + dim = spv::Dim::Cube; + isArray = false; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::Texture1DArray: + dim = spv::Dim::Dim1D; + isArray = true; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::Texture2DArray: + dim = spv::Dim::Dim2D; + isArray = true; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::Texture2DMSArray: + dim = spv::Dim::Dim2D; + isArray = true; + isTexture = true; + break; + case hlsl::DXIL::ResourceKind::TextureCubeArray: + dim = spv::Dim::Cube; + isArray = true; + isTexture = true; + break; + default: + break; } - // There is no RWTexture3DArray - if ((dim = spv::Dim::Dim1D, isArray = false, - name == "RWTexture1D" || name == "RasterizerOrderedTexture1D") || - (dim = spv::Dim::Dim2D, isArray = false, - name == "RWTexture2D" || name == "RasterizerOrderedTexture2D") || - (dim = spv::Dim::Dim3D, isArray = false, - name == "RWTexture3D" || name == "RasterizerOrderedTexture3D") || - (dim = spv::Dim::Dim1D, isArray = true, - name == "RWTexture1DArray" || - name == "RasterizerOrderedTexture1DArray") || - (dim = spv::Dim::Dim2D, isArray = true, - name == "RWTexture2DArray" || - name == "RasterizerOrderedTexture2DArray")) { - const auto sampledType = hlsl::GetHLSLResourceResultType(type); - const auto format = - translateSampledTypeToImageFormat(sampledType, srcLoc); - return spvContext.getImageType( - lowerType(getElementType(astContext, sampledType), rule, - /*isRowMajor*/ llvm::None, srcLoc), - dim, ImageType::WithDepth::Unknown, isArray, - /*isMultiSampled=*/false, /*sampled=*/ImageType::WithSampler::No, - format); + if (isTexture) { + const bool isMS = + resKind == hlsl::DXIL::ResourceKind::Texture2DMS || + resKind == hlsl::DXIL::ResourceKind::Texture2DMSArray; + + if (resClass == hlsl::DXIL::ResourceClass::SRV) { + const auto sampledType = hlsl::GetHLSLResourceResultType(type); + auto loweredType = + lowerType(getElementType(astContext, sampledType), rule, + /*isRowMajor*/ llvm::None, srcLoc); + // Bool does not have a defined size in SPIR-V, so it cannot be + // used in the external interface. + if (loweredType == spvContext.getBoolType()) { + loweredType = spvContext.getUIntType(32); + } + return spvContext.getImageType( + loweredType, dim, ImageType::WithDepth::Unknown, isArray, isMS, + ImageType::WithSampler::Yes, spv::ImageFormat::Unknown); + } + + // UAV textures (RWTexture* and RasterizerOrderedTexture*) - no + // Texture3DArray in DXIL/HLSL. + if (resClass == hlsl::DXIL::ResourceClass::UAV) { + const auto sampledType = hlsl::GetHLSLResourceResultType(type); + const auto format = + translateSampledTypeToImageFormat(sampledType, srcLoc); + return spvContext.getImageType( + lowerType(getElementType(astContext, sampledType), rule, + /*isRowMajor*/ llvm::None, srcLoc), + dim, ImageType::WithDepth::Unknown, isArray, + /*isMultiSampled=*/isMS, /*sampled=*/ImageType::WithSampler::No, + format); + } } } // Sampler types - if (name == "SamplerState" || name == "SamplerComparisonState") { + if (resKind == hlsl::DXIL::ResourceKind::Sampler) { return spvContext.getSamplerType(); } - if (name == "RaytracingAccelerationStructure") { + if (resKind == hlsl::DXIL::ResourceKind::RTAccelerationStructure) { return spvContext.getAccelerationStructureTypeNV(); } if (hlsl::IsHLSLRayQueryType(type)) return spvContext.getRayQueryTypeKHR(); - if (name == "StructuredBuffer" || name == "RWStructuredBuffer" || - name == "RasterizerOrderedStructuredBuffer" || - name == "AppendStructuredBuffer" || name == "ConsumeStructuredBuffer") { + if (resKind == hlsl::DXIL::ResourceKind::StructuredBuffer) { // StructureBuffer will be translated into an OpTypeStruct with one // field, which is an OpTypeRuntimeArray of OpTypeStruct (S). @@ -1005,7 +1041,7 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, // The stride of the runtime array is the size of the struct. const auto *raType = spvContext.getRuntimeArrayType(structType, arrayStride); - const bool isReadOnly = (name == "StructuredBuffer"); + const bool isReadOnly = resClass == hlsl::DXIL::ResourceClass::SRV; // Attach matrix stride decorations if this is a *StructuredBuffer. llvm::Optional matrixStride = llvm::None; @@ -1029,10 +1065,11 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, return valType; } - if (name == "ConstantBuffer" || name == "TextureBuffer") { + if (resKind == hlsl::DXIL::ResourceKind::CBuffer || + resKind == hlsl::DXIL::ResourceKind::TBuffer) { // ConstantBuffer and TextureBuffer are lowered as T - const bool forTBuffer = name == "TextureBuffer"; + const bool forTBuffer = resKind == hlsl::DXIL::ResourceKind::TBuffer; if (rule == SpirvLayoutRule::Void) { rule = forTBuffer ? getCodeGenOptions().tBufferLayoutRule @@ -1067,10 +1104,9 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, } // ByteAddressBuffer and RWByteAddressBuffer types. - if (name == "ByteAddressBuffer" || name == "RWByteAddressBuffer" || - name == "RasterizerOrderedByteAddressBuffer") { + if (resKind == hlsl::DXIL::ResourceKind::RawBuffer) { const auto *bufferType = spvContext.getByteAddressBufferType( - /*isRW*/ name != "ByteAddressBuffer"); + /*isRW*/ resClass != hlsl::DXIL::ResourceClass::SRV); if (rule == SpirvLayoutRule::Void) { // All byte address buffers are in the Uniform storage class. return spvContext.getPointerType(bufferType, spv::StorageClass::Uniform); @@ -1079,8 +1115,7 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, } // Buffer and RWBuffer types - if (name == "Buffer" || name == "RWBuffer" || - name == "RasterizerOrderedBuffer") { + if (resKind == hlsl::DXIL::ResourceKind::TypedBuffer) { const auto sampledType = hlsl::GetHLSLResourceResultType(type); const auto format = translateSampledTypeToImageFormat(sampledType, srcLoc); return spvContext.getImageType( @@ -1088,13 +1123,14 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, /*isRowMajor*/ llvm::None, srcLoc), spv::Dim::Buffer, ImageType::WithDepth::Unknown, /*isArrayed=*/false, /*isMultiSampled=*/false, - /*sampled*/ name == "Buffer" ? ImageType::WithSampler::Yes - : ImageType::WithSampler::No, + /*sampled*/ resClass == hlsl::DXIL::ResourceClass::SRV + ? ImageType::WithSampler::Yes + : ImageType::WithSampler::No, format); } // InputPatch - if (name == "InputPatch") { + if (hlsl::IsHLSLInputPatchType(type)) { const auto elemType = hlsl::GetHLSLInputPatchElementType(type); const auto elemCount = hlsl::GetHLSLInputPatchCount(type); return spvContext.getArrayType( @@ -1102,7 +1138,7 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, /*ArrayStride*/ llvm::None); } // OutputPatch - if (name == "OutputPatch") { + if (hlsl::IsHLSLOutputPatchType(type)) { const auto elemType = hlsl::GetHLSLOutputPatchElementType(type); const auto elemCount = hlsl::GetHLSLOutputPatchCount(type); return spvContext.getArrayType( @@ -1110,20 +1146,19 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, /*ArrayStride*/ llvm::None); } // Output stream objects (TriangleStream, LineStream, and PointStream) - if (name == "TriangleStream" || name == "LineStream" || - name == "PointStream") { + if (hlsl::IsHLSLStreamOutputType(type)) { return lowerType(hlsl::GetHLSLResourceResultType(type), rule, /*isRowMajor*/ llvm::None, srcLoc); } - if (name == "SubpassInput" || name == "SubpassInputMS") { + if (isSubpassInput(type) || isSubpassInputMS(type)) { const auto sampledType = hlsl::GetHLSLResourceResultType(type); return spvContext.getImageType( lowerType(getElementType(astContext, sampledType), rule, /*isRowMajor*/ llvm::None, srcLoc), spv::Dim::SubpassData, ImageType::WithDepth::Unknown, /*isArrayed=*/false, - /*isMultipleSampled=*/name == "SubpassInputMS", + /*isMultipleSampled=*/isSubpassInputMS(type), ImageType::WithSampler::No, spv::ImageFormat::Unknown); } diff --git a/tools/clang/lib/Sema/SemaHLSL.cpp b/tools/clang/lib/Sema/SemaHLSL.cpp index f818cbcb46..f09b5a12ea 100644 --- a/tools/clang/lib/Sema/SemaHLSL.cpp +++ b/tools/clang/lib/Sema/SemaHLSL.cpp @@ -4242,6 +4242,12 @@ class HLSLExternalSource : public ExternalSemaSource { } m_objectTypeDecls[i] = recordDecl; m_objectTypeDeclsMap[i] = std::make_pair(recordDecl, i); +#ifdef ENABLE_SPIRV_CODEGEN + if (kind == AR_OBJECT_VK_SUBPASS_INPUT || + kind == AR_OBJECT_VK_SUBPASS_INPUT_MS) + recordDecl->addAttr(HLSLVkSubpassInputAttr::CreateImplicit( + *m_context, kind == AR_OBJECT_VK_SUBPASS_INPUT_MS)); +#endif // ENABLE_SPIRV_CODEGEN } // Create an alias for SamplerState. 'sampler' is very commonly used. diff --git a/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl new file mode 100644 index 0000000000..1f43f8f4bd --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl @@ -0,0 +1,32 @@ +// RUN: %dxc -E main -T cs_6_5 -fspv-target-env=vulkan1.2 -spirv %s | FileCheck %s + +// Verify that user-defined types in namespaces with the same names as HLSL +// built-in buffer types are NOT misidentified as HLSL resource types. The +// user-defined types should compile correctly without errors. + +// CHECK: OpCapability Shader +// CHECK: OpStore + +namespace myns { + struct StructuredBuffer { float data; }; + struct RWStructuredBuffer { float data; }; + struct AppendStructuredBuffer { float data; }; + struct ConsumeStructuredBuffer { float data; }; + struct ByteAddressBuffer { int raw; }; + struct RWByteAddressBuffer { int raw; }; + struct ConstantBuffer { float v; }; + struct TextureBuffer { float v; }; +} + +RWStructuredBuffer output : register(u0); + +[numthreads(1,1,1)] +void main() { + myns::StructuredBuffer sb; + sb.data = 1.0; + myns::ByteAddressBuffer bab; + bab.raw = 42; + myns::ConstantBuffer cb; + cb.v = 3.0; + output[0] = sb.data + cb.v; +} diff --git a/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.rayquery.hlsl b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.rayquery.hlsl new file mode 100644 index 0000000000..e6fa4e7d71 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.rayquery.hlsl @@ -0,0 +1,26 @@ +// RUN: %dxc -E main -T cs_6_5 -fspv-target-env=vulkan1.2 -spirv %s | FileCheck %s + +// Verify that a user-defined type named RayQuery in a namespace is NOT +// misidentified as the built-in RayQuery type. The user-defined type should +// be lowered as a plain struct, not as the OpTypeRayQueryKHR SPIR-V type. + +// CHECK-NOT: OpCapability RayQueryKHR +// CHECK-NOT: OpTypeRayQueryKHR + +namespace myns { + struct RayQuery { + float x; + float y; + }; +} + +RWStructuredBuffer output : register(u0); + +[numthreads(1,1,1)] +void main() { + myns::RayQuery q; + q.x = 1.0; + q.y = 2.0; + // CHECK: OpStore + output[0] = q.x + q.y; +} diff --git a/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl new file mode 100644 index 0000000000..4853991926 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl @@ -0,0 +1,48 @@ +// RUN: %dxc -E main -T cs_6_5 -fspv-target-env=vulkan1.2 -spirv %s | FileCheck %s + +// Verify that user-defined types in namespaces with the same names as HLSL +// built-in resource types are NOT misidentified as HLSL resource types. Each +// user-defined type should be lowered as a plain struct. + +// CHECK-NOT: OpTypeSampledImage +// CHECK-NOT: OpTypeImage + +// CHECK: %float_11 = OpConstant %float 11 +// CHECK: OpStore {{.*}} %float_11 + +namespace myns { + struct Texture1D { float value; }; + struct Texture2D { float r; float g; }; + struct Texture3D { float r; float g; float b; }; + struct TextureCube { float x; }; + struct Texture1DArray { float v; }; + struct Texture2DArray { float v; }; + struct Texture2DMS { float v; }; + struct Texture2DMSArray { float v; }; + struct TextureCubeArray { float v; }; + struct RWTexture1D { float rw; }; + struct RWTexture2D { float rw; }; + struct RWTexture3D { float rw; }; + struct RasterizerOrderedTexture2D { float rov; }; + struct SamplerState { int id; }; + struct SamplerComparisonState { int id; }; + struct RaytracingAccelerationStructure { float x; }; + struct Buffer { float data; }; + struct RWBuffer { float data; }; + struct RasterizerOrderedBuffer { float data; }; +} + +RWStructuredBuffer output : register(u0); + +[numthreads(1,1,1)] +void main() { + myns::Texture1D t1; + t1.value = 1.0; + myns::Texture2D t2; + t2.r = 2.0; t2.g = 3.0; + myns::SamplerState ss; + ss.id = 0; + myns::Buffer buf; + buf.data = 5.0; + output[0] = t1.value + t2.r + t2.g + buf.data; +} From 434bf48587fd93df9266174162d8bc8e5ef4cff6 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Mon, 10 Aug 2026 14:31:13 -0500 Subject: [PATCH 02/11] clang-format --- tools/clang/lib/SPIRV/AstTypeProbe.cpp | 8 ++------ tools/clang/lib/SPIRV/LowerTypeVisitor.cpp | 5 ++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tools/clang/lib/SPIRV/AstTypeProbe.cpp b/tools/clang/lib/SPIRV/AstTypeProbe.cpp index 09460b7317..57c030742c 100644 --- a/tools/clang/lib/SPIRV/AstTypeProbe.cpp +++ b/tools/clang/lib/SPIRV/AstTypeProbe.cpp @@ -296,13 +296,9 @@ bool isMxNMatrix(QualType type, QualType *elemType, uint32_t *numRows, return false; } -bool isInputPatch(QualType type) { - return hlsl::IsHLSLInputPatchType(type); -} +bool isInputPatch(QualType type) { return hlsl::IsHLSLInputPatchType(type); } -bool isOutputPatch(QualType type) { - return hlsl::IsHLSLOutputPatchType(type); -} +bool isOutputPatch(QualType type) { return hlsl::IsHLSLOutputPatchType(type); } bool isSubpassInput(QualType type) { if (const HLSLVkSubpassInputAttr *Attr = diff --git a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp index 1bf83fd41c..89f20e3471 100644 --- a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp +++ b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp @@ -959,9 +959,8 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, } if (isTexture) { - const bool isMS = - resKind == hlsl::DXIL::ResourceKind::Texture2DMS || - resKind == hlsl::DXIL::ResourceKind::Texture2DMSArray; + const bool isMS = resKind == hlsl::DXIL::ResourceKind::Texture2DMS || + resKind == hlsl::DXIL::ResourceKind::Texture2DMSArray; if (resClass == hlsl::DXIL::ResourceClass::SRV) { const auto sampledType = hlsl::GetHLSLResourceResultType(type); From cb792402f832f8fa7a79b53f880fae1004e4ae50 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Mon, 10 Aug 2026 15:12:36 -0500 Subject: [PATCH 03/11] Add release note --- docs/ReleaseNotes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 19c0d75174..200e1c5310 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -30,6 +30,8 @@ line upon naming the release. Refer to previous for appropriate section names. - SPIR-V: Fixed an invalid `OpSelect` being generated when optimizing for SPIR-V 1.3 and earlier [#8603](https://github.com/microsoft/DirectXShaderCompiler/issues/8603). +- SPIR-V: Fixed user-defined types whose names match built-in resource types + being incorrectly lowered as resources. #### HLSL Language From cffd97ff6b39c2e8dc98c3d077ff3a50568c3ae6 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Mon, 10 Aug 2026 15:17:09 -0500 Subject: [PATCH 04/11] Expanding test coverage and fixing a bug in the implementation --- tools/clang/lib/SPIRV/AstTypeProbe.cpp | 7 ++- ...ser-defined-type.builtin-name.buffers.hlsl | 15 +++++- ...er-defined-type.builtin-name.textures.hlsl | 52 +++++++++++++++++-- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/tools/clang/lib/SPIRV/AstTypeProbe.cpp b/tools/clang/lib/SPIRV/AstTypeProbe.cpp index 57c030742c..ebbfe0611a 100644 --- a/tools/clang/lib/SPIRV/AstTypeProbe.cpp +++ b/tools/clang/lib/SPIRV/AstTypeProbe.cpp @@ -1047,7 +1047,12 @@ bool isOrContainsAKindOfStructuredOrByteBuffer(QualType type) { } bool isOpaqueType(QualType type) { - if (hlsl::IsHLSLResourceType(type) || hlsl::IsHLSLRayQueryType(type)) + const auto resKind = hlsl::GetHLSLResourceKind(type); + if (hlsl::DXIL::IsAnyTexture(resKind) || + resKind == hlsl::DXIL::ResourceKind::TypedBuffer || + resKind == hlsl::DXIL::ResourceKind::Sampler || + resKind == hlsl::DXIL::ResourceKind::RTAccelerationStructure || + hlsl::IsHLSLRayQueryType(type)) return true; if (isSubpassInput(type)) return true; diff --git a/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl index 1f43f8f4bd..225b43caa5 100644 --- a/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl +++ b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.buffers.hlsl @@ -24,9 +24,20 @@ RWStructuredBuffer output : register(u0); void main() { myns::StructuredBuffer sb; sb.data = 1.0; + myns::RWStructuredBuffer rwsb; + rwsb.data = 2.0; + myns::AppendStructuredBuffer asb; + asb.data = 3.0; + myns::ConsumeStructuredBuffer csb; + csb.data = 4.0; myns::ByteAddressBuffer bab; bab.raw = 42; + myns::RWByteAddressBuffer rwbab; + rwbab.raw = 43; myns::ConstantBuffer cb; - cb.v = 3.0; - output[0] = sb.data + cb.v; + cb.v = 5.0; + myns::TextureBuffer tb; + tb.v = 6.0; + output[0] = sb.data + rwsb.data + asb.data + csb.data + bab.raw + + rwbab.raw + cb.v + tb.v; } diff --git a/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl index 4853991926..ecfacb2fa5 100644 --- a/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl +++ b/tools/clang/test/CodeGenSPIRV/user-defined-type.builtin-name.textures.hlsl @@ -6,9 +6,11 @@ // CHECK-NOT: OpTypeSampledImage // CHECK-NOT: OpTypeImage +// CHECK-NOT: OpTypeSampler +// CHECK-NOT: OpTypeAccelerationStructure -// CHECK: %float_11 = OpConstant %float 11 -// CHECK: OpStore {{.*}} %float_11 +// CHECK: %float_300 = OpConstant %float 300 +// CHECK: OpStore {{.*}} %float_300 namespace myns { struct Texture1D { float value; }; @@ -30,6 +32,8 @@ namespace myns { struct Buffer { float data; }; struct RWBuffer { float data; }; struct RasterizerOrderedBuffer { float data; }; + struct SubpassInput { float value; }; + struct SubpassInputMS { float value; }; } RWStructuredBuffer output : register(u0); @@ -40,9 +44,47 @@ void main() { t1.value = 1.0; myns::Texture2D t2; t2.r = 2.0; t2.g = 3.0; + myns::Texture3D t3; + t3.r = 4.0; t3.g = 5.0; t3.b = 6.0; + myns::TextureCube tc; + tc.x = 7.0; + myns::Texture1DArray t1a; + t1a.v = 8.0; + myns::Texture2DArray t2a; + t2a.v = 9.0; + myns::Texture2DMS t2ms; + t2ms.v = 10.0; + myns::Texture2DMSArray t2msa; + t2msa.v = 11.0; + myns::TextureCubeArray tca; + tca.v = 12.0; + myns::RWTexture1D rwt1; + rwt1.rw = 13.0; + myns::RWTexture2D rwt2; + rwt2.rw = 14.0; + myns::RWTexture3D rwt3; + rwt3.rw = 15.0; + myns::RasterizerOrderedTexture2D rovt2; + rovt2.rov = 16.0; myns::SamplerState ss; - ss.id = 0; + ss.id = 17; + myns::SamplerComparisonState scs; + scs.id = 18; + myns::RaytracingAccelerationStructure rtas; + rtas.x = 19.0; myns::Buffer buf; - buf.data = 5.0; - output[0] = t1.value + t2.r + t2.g + buf.data; + buf.data = 20.0; + myns::RWBuffer rwbuf; + rwbuf.data = 21.0; + myns::RasterizerOrderedBuffer rovbuf; + rovbuf.data = 22.0; + myns::SubpassInput subpass; + subpass.value = 23.0; + myns::SubpassInputMS subpassms; + subpassms.value = 24.0; + output[0] = t1.value + t2.r + t2.g + t3.r + t3.g + t3.b + tc.x + + t1a.v + t2a.v + t2ms.v + t2msa.v + tca.v + rwt1.rw + + rwt2.rw + rwt3.rw + rovt2.rov + ss.id + scs.id + rtas.x + + buf.data + rwbuf.data + rovbuf.data + subpass.value + + subpassms.value; } From 0868fb3d224c8ad607fe5a97f743af1ea687b64f Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Wed, 19 Aug 2026 13:43:35 -0500 Subject: [PATCH 05/11] Update release notes --- docs/ReleaseNotes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 200e1c5310..deb7daaf67 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -30,8 +30,8 @@ line upon naming the release. Refer to previous for appropriate section names. - SPIR-V: Fixed an invalid `OpSelect` being generated when optimizing for SPIR-V 1.3 and earlier [#8603](https://github.com/microsoft/DirectXShaderCompiler/issues/8603). -- SPIR-V: Fixed user-defined types whose names match built-in resource types - being incorrectly lowered as resources. +- SPIR-V: Fixed user-defined types whose names match built-in types being + incorrectly lowered as the built-in type. #### HLSL Language From 465c3db69dce681724f9959b6c0926f424e9d670 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Wed, 19 Aug 2026 13:58:59 -0500 Subject: [PATCH 06/11] Updates based on review feedback --- docs/ReleaseNotes.md | 6 ++++++ tools/clang/lib/SPIRV/LowerTypeVisitor.cpp | 3 +++ .../vk.sampledtexture2d.half4.error.hlsl | 6 ++++++ tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl | 10 ++++++++++ 4 files changed, 25 insertions(+) create mode 100644 tools/clang/test/CodeGenSPIRV/SampledTexture/SampledTexture2D/vk.sampledtexture2d.half4.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index deb7daaf67..9ce52a9783 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -32,6 +32,12 @@ line upon naming the release. Refer to previous for appropriate section names. [#8603](https://github.com/microsoft/DirectXShaderCompiler/issues/8603). - SPIR-V: Fixed user-defined types whose names match built-in types being incorrectly lowered as the built-in type. +- SPIR-V: 16-bit sampled types on `vk::SampledTexture` are rejected as required + by VUID-StandaloneSpirv-OpTypeImage-04656 + [#8755](https://github.com/microsoft/DirectXShaderCompiler/pull/8755). +- SPIR-V: Added support for multisampled UAV types fixing miscompiles in + previous releases + [#8755](https://github.com/microsoft/DirectXShaderCompiler/pull/8755) #### HLSL Language diff --git a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp index 89f20e3471..2d243ff3ba 100644 --- a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp +++ b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp @@ -980,6 +980,9 @@ LowerTypeVisitor::lowerResourceType(QualType type, SpirvLayoutRule rule, // UAV textures (RWTexture* and RasterizerOrderedTexture*) - no // Texture3DArray in DXIL/HLSL. if (resClass == hlsl::DXIL::ResourceClass::UAV) { + if (isMS) + spvBuilder.requireCapability(spv::Capability::StorageImageMultisample, + srcLoc); const auto sampledType = hlsl::GetHLSLResourceResultType(type); const auto format = translateSampledTypeToImageFormat(sampledType, srcLoc); diff --git a/tools/clang/test/CodeGenSPIRV/SampledTexture/SampledTexture2D/vk.sampledtexture2d.half4.error.hlsl b/tools/clang/test/CodeGenSPIRV/SampledTexture/SampledTexture2D/vk.sampledtexture2d.half4.error.hlsl new file mode 100644 index 0000000000..fab4c00598 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/SampledTexture/SampledTexture2D/vk.sampledtexture2d.half4.error.hlsl @@ -0,0 +1,6 @@ +// RUN: not %dxc -T ps_6_7 -fcgl %s -spirv -enable-16bit-types 2>&1 | FileCheck %s + +// CHECK: error: The sampled type for textures cannot be a floating point type smaller than 32-bits when targeting a Vulkan environment. +vk::SampledTexture2D tex2d; + +void main() : SV_Target {} diff --git a/tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl b/tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl new file mode 100644 index 0000000000..0b8c48e3ab --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl @@ -0,0 +1,10 @@ +// RUN: %dxc -T ps_6_7 -fcgl %s -spirv | FileCheck %s + +// CHECK: OpCapability StorageImageMultisample +// CHECK-DAG: OpTypeImage %float 2D 2 0 1 2 Rgba32f +// CHECK-DAG: OpTypeImage %uint 2D 2 1 1 2 R32ui + +RWTexture2DMS texture2dms; +RWTexture2DMSArray texture2dmsArray; + +void main() : SV_Target {} From 6ebdacd9d572bbd7036aca098904dd6d24806162 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Tue, 8 Sep 2026 15:27:40 -0500 Subject: [PATCH 07/11] Cleanup release notes (remove PR links) --- docs/ReleaseNotes.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 9ce52a9783..29ee4f2cf0 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -33,11 +33,9 @@ line upon naming the release. Refer to previous for appropriate section names. - SPIR-V: Fixed user-defined types whose names match built-in types being incorrectly lowered as the built-in type. - SPIR-V: 16-bit sampled types on `vk::SampledTexture` are rejected as required - by VUID-StandaloneSpirv-OpTypeImage-04656 - [#8755](https://github.com/microsoft/DirectXShaderCompiler/pull/8755). + by VUID-StandaloneSpirv-OpTypeImage-04656. - SPIR-V: Added support for multisampled UAV types fixing miscompiles in - previous releases - [#8755](https://github.com/microsoft/DirectXShaderCompiler/pull/8755) + previous releases. #### HLSL Language From fabadfdc396df13b948b5b1c9a02d14a202d45b3 Mon Sep 17 00:00:00 2001 From: Chris B Date: Wed, 23 Sep 2026 19:56:01 -0500 Subject: [PATCH 08/11] Address PR feedback on RWMS textures --- .../clang/include/clang/SPIRV/SpirvBuilder.h | 3 +- tools/clang/lib/SPIRV/CapabilityVisitor.cpp | 3 ++ tools/clang/lib/SPIRV/SpirvBuilder.cpp | 14 ++++++---- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 28 ++++++++++++++----- .../test/CodeGenSPIRV/type.rwtexture2dms.hlsl | 16 +++++++++-- 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index 2e697a94e0..0b1cd82184 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -341,7 +341,8 @@ class SpirvBuilder { /// \brief Creates SPIR-V instructions for writing to the given image. void createImageWrite(QualType imageType, SpirvInstruction *image, SpirvInstruction *coord, SpirvInstruction *texel, - SourceLocation loc, SourceRange range = {}); + SpirvInstruction *sample, SourceLocation loc, + SourceRange range = {}); /// \brief Creates SPIR-V instructions for gathering the given image. /// diff --git a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp index 9f012d24e8..3c703ab970 100644 --- a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp +++ b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp @@ -130,6 +130,9 @@ void CapabilityVisitor::addCapabilityForType(const SpirvType *type, } // Image types else if (const auto *imageType = dyn_cast(type)) { + if (imageType->isArrayedImage() && imageType->isMSImage()) + addCapability(spv::Capability::ImageMSArray); + switch (imageType->getDimension()) { case spv::Dim::Buffer: { addCapability(spv::Capability::SampledBuffer); diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index 216025c984..95220b6a80 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -708,15 +708,19 @@ SpirvInstruction *SpirvBuilder::createImageFetchOrRead( void SpirvBuilder::createImageWrite(QualType imageType, SpirvInstruction *image, SpirvInstruction *coord, - SpirvInstruction *texel, SourceLocation loc, - SourceRange range) { + SpirvInstruction *texel, + SpirvInstruction *sample, + SourceLocation loc, SourceRange range) { assert(insertPoint && "null insert point"); + const auto mask = composeImageOperandsMask( + /*bias*/ nullptr, /*lod*/ nullptr, std::make_pair(nullptr, nullptr), + /*constOffset*/ nullptr, /*varOffset*/ nullptr, + /*constOffsets*/ nullptr, sample, /*minLod*/ nullptr); auto *writeInst = new (context) SpirvImageOp( - spv::Op::OpImageWrite, imageType, loc, image, coord, - spv::ImageOperandsMask::MaskNone, + spv::Op::OpImageWrite, imageType, loc, image, coord, mask, /*dref*/ nullptr, /*bias*/ nullptr, /*lod*/ nullptr, /*gradDx*/ nullptr, /*gradDy*/ nullptr, /*constOffset*/ nullptr, /*varOffset*/ nullptr, - /*constOffsets*/ nullptr, /*sample*/ nullptr, /*minLod*/ nullptr, + /*constOffsets*/ nullptr, sample, /*minLod*/ nullptr, /*component*/ nullptr, texel, range); insertPoint->addInstruction(writeInst); } diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index a8c987288f..8b28f9f889 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -6546,10 +6546,13 @@ SpirvEmitter::processBufferTextureLoad(const CXXMemberCallExpr *expr) { auto loc = expr->getExprLoc(); auto range = expr->getSourceRange(); - if (isBuffer(objectType) || isRWBuffer(objectType) || isRWTexture(objectType)) + if (isBuffer(objectType) || isRWBuffer(objectType) || + isRWTexture(objectType)) { + auto *sample = textureMS ? doExpr(expr->getArg(1)) : nullptr; return processBufferTextureLoad(object, doExpr(locationArg), - /*constOffset*/ nullptr, /*lod*/ nullptr, + /*constOffset*/ nullptr, /*lod*/ sample, /*residencyCode*/ status, loc, range); + } // Subtract 1 for status (if it exists), and 1 for sampleIndex (if it exists), // and 1 for location. @@ -6627,7 +6630,8 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, // For Textures, regular indexing (operator[]) uses slice 0. if (isBufferTextureIndexing(expr, &baseExpr, &indexExpr)) { auto *lod = (isTexture(baseExpr->getType()) || - isSampledTexture(baseExpr->getType())) + isSampledTexture(baseExpr->getType()) || + isTextureMS(baseExpr->getType())) ? spvBuilder.getConstantInt(astContext.UnsignedIntTy, llvm::APInt(32, 0)) : nullptr; @@ -7955,7 +7959,8 @@ bool SpirvEmitter::isTextureMipsSampleIndexing(const CXXOperatorCallExpr *expr, const Expr *object = memberExpr->getBase(); const auto objectType = object->getType(); - if (!isTexture(objectType) && !isSampledTexture(objectType)) + if (!isTexture(objectType) && !isRWTexture(objectType) && + !isSampledTexture(objectType)) return false; if (base) @@ -8448,11 +8453,20 @@ SpirvInstruction *SpirvEmitter::tryToAssignToRWBufferRWTexture( const Expr *lhs, SpirvInstruction *rhs, SourceRange range) { const Expr *baseExpr = nullptr; const Expr *indexExpr = nullptr; + const Expr *sampleExpr = nullptr; const auto lhsExpr = dyn_cast(lhs); - if (isBufferTextureIndexing(lhsExpr, &baseExpr, &indexExpr)) { + if (isTextureMipsSampleIndexing(lhsExpr, &baseExpr, &indexExpr, + &sampleExpr) || + isBufferTextureIndexing(lhsExpr, &baseExpr, &indexExpr)) { auto *loc = doExpr(indexExpr, range); const QualType imageType = baseExpr->getType(); auto *baseInfo = doExpr(baseExpr, range); + auto *sample = + sampleExpr ? doExpr(sampleExpr, range) + : (isTextureMS(imageType) + ? spvBuilder.getConstantInt(astContext.UnsignedIntTy, + llvm::APInt(32, 0)) + : nullptr); const bool rasterizerOrder = isRasterizerOrderedView(imageType); if (rasterizerOrder) { @@ -8465,8 +8479,8 @@ SpirvInstruction *SpirvEmitter::tryToAssignToRWBufferRWTexture( if (baseInfo->isLValue()) image = spvBuilder.createLoad(imageType, baseInfo, baseExpr->getExprLoc(), range); - spvBuilder.createImageWrite(imageType, image, loc, rhs, lhs->getExprLoc(), - range); + spvBuilder.createImageWrite(imageType, image, loc, rhs, sample, + lhs->getExprLoc(), range); if (rasterizerOrder) { spvBuilder.createEndInvocationInterlockEXT(baseExpr->getExprLoc(), range); diff --git a/tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl b/tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl index 0b8c48e3ab..ddc9b505fd 100644 --- a/tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl +++ b/tools/clang/test/CodeGenSPIRV/type.rwtexture2dms.hlsl @@ -1,10 +1,22 @@ // RUN: %dxc -T ps_6_7 -fcgl %s -spirv | FileCheck %s -// CHECK: OpCapability StorageImageMultisample +// CHECK-DAG: OpCapability ImageMSArray +// CHECK-DAG: OpCapability StorageImageMultisample // CHECK-DAG: OpTypeImage %float 2D 2 0 1 2 Rgba32f // CHECK-DAG: OpTypeImage %uint 2D 2 1 1 2 R32ui +// CHECK: OpImageRead %v4float {{%[0-9]+}} {{%[0-9]+}} Sample {{%[0-9]+}} +// CHECK: OpImageRead %v4uint {{%[0-9]+}} {{%[0-9]+}} Sample {{%[0-9]+}} +// CHECK: OpImageWrite {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}} Sample {{%[0-9]+}} +// CHECK: OpImageWrite {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}} Sample {{%[0-9]+}} RWTexture2DMS texture2dms; RWTexture2DMSArray texture2dmsArray; -void main() : SV_Target {} +float4 main(uint2 coord : TEXCOORD0, uint sample : TEXCOORD1) : SV_Target { + float4 value = texture2dms.Load(coord, sample); + value += texture2dmsArray.Load(uint3(coord, 0), sample); + + texture2dms.sample[sample][coord] = value; + texture2dmsArray.sample[sample][uint3(coord, 0)] = value.x; + return value; +} From f99db47f4dec50491bbb8457dc11aabd7a6adaed Mon Sep 17 00:00:00 2001 From: Chris B Date: Thu, 8 Oct 2026 14:05:01 -0600 Subject: [PATCH 09/11] Fix SubPassInputMS --- tools/clang/lib/SPIRV/AstTypeProbe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/clang/lib/SPIRV/AstTypeProbe.cpp b/tools/clang/lib/SPIRV/AstTypeProbe.cpp index ebbfe0611a..d112d1863d 100644 --- a/tools/clang/lib/SPIRV/AstTypeProbe.cpp +++ b/tools/clang/lib/SPIRV/AstTypeProbe.cpp @@ -1054,7 +1054,7 @@ bool isOpaqueType(QualType type) { resKind == hlsl::DXIL::ResourceKind::RTAccelerationStructure || hlsl::IsHLSLRayQueryType(type)) return true; - if (isSubpassInput(type)) + if (isSubpassInput(type) || isSubpassInputMS(type)) return true; return false; } From fb90761921f4e69f84b51ef479079f1f21c7308c Mon Sep 17 00:00:00 2001 From: Chris B Date: Thu, 8 Oct 2026 14:34:13 -0600 Subject: [PATCH 10/11] Add test --- tools/clang/test/CodeGenSPIRV/vk.subpass-input.hlsl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/clang/test/CodeGenSPIRV/vk.subpass-input.hlsl b/tools/clang/test/CodeGenSPIRV/vk.subpass-input.hlsl index 354c7bc52a..67659bfa15 100644 --- a/tools/clang/test/CodeGenSPIRV/vk.subpass-input.hlsl +++ b/tools/clang/test/CodeGenSPIRV/vk.subpass-input.hlsl @@ -40,6 +40,10 @@ // CHECK: %SIMS_u1 = OpVariable %_ptr_UniformConstant_type_subpass_image_2 UniformConstant [[vk::input_attachment_index(13)]] SubpassInputMS SIMS_u1; +struct SubpassInputMSBundle { + SubpassInputMS input; +}; + float4 ReadSourceFromTile(SubpassInput spi) { return spi.SubpassLoad() ; } @@ -89,5 +93,9 @@ float4 main() : SV_Target { // CHECK-NEXT: OpStore %v13 [[val_4]] uint v13 = SIMS_u1.SubpassLoad(4); - return v0.x + v1.y + v2.x + v3 + v4.x + v10.x + v11.y + v12.x + v13; + SubpassInputMSBundle bundle; + bundle.input = SIMS_f3; + float3 v14 = bundle.input.SubpassLoad(2); + + return v0.x + v1.y + v2.x + v3 + v4.x + v10.x + v11.y + v12.x + v13 + v14.z; } From 791e06620e6eccad215440ff2af6f37620589115 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Thu, 8 Oct 2026 16:51:32 -0600 Subject: [PATCH 11/11] Don't add ImageMSArray capability for writable textures --- tools/clang/lib/SPIRV/CapabilityVisitor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp index 3c703ab970..6487af027e 100644 --- a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp +++ b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp @@ -130,7 +130,8 @@ void CapabilityVisitor::addCapabilityForType(const SpirvType *type, } // Image types else if (const auto *imageType = dyn_cast(type)) { - if (imageType->isArrayedImage() && imageType->isMSImage()) + if (imageType->isArrayedImage() && imageType->isMSImage() && + imageType->withSampler() == ImageType::WithSampler::No) addCapability(spv::Capability::ImageMSArray); switch (imageType->getDimension()) {