[clang] [llvm] [HLSL] Add Texture2DMS (PR #211972)
Deric C. via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 18 18:57:32 PDT 2026
https://github.com/Icohedron updated https://github.com/llvm/llvm-project/pull/211972
>From 676c00bafc02a2ef6eece6b6a596f1bfa77d0246 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Thu, 23 Jul 2026 17:49:44 -0700
Subject: [PATCH 1/6] [HLSL] Add Texture2DMS
Declares Texture2DMS<element_type, int sample_count = 0> with
Load(location, sampleIndex[, offset]) and operator[], plus the
resource.load.ms intrinsic for DirectX and SPIR-V.
The sample count is a non-type template parameter, so the resource handle
type now takes part in template instantiation, and lowers to the
dx.MSTexture sample-count operand. A sample count of 0 means the count
comes from the bound resource at runtime.
---
clang/include/clang/AST/ASTContext.h | 3 +-
clang/include/clang/AST/TypeBase.h | 42 +++----
clang/include/clang/AST/TypeProperties.td | 6 +-
clang/include/clang/Basic/Builtins.td | 6 +
clang/include/clang/Sema/SemaHLSL.h | 3 +-
clang/lib/AST/ASTContext.cpp | 3 +-
clang/lib/AST/ASTImporter.cpp | 3 +-
clang/lib/AST/Type.cpp | 36 ++++++
clang/lib/CodeGen/CGHLSLBuiltins.cpp | 19 +++
clang/lib/CodeGen/CGHLSLRuntime.h | 1 +
clang/lib/CodeGen/Targets/DirectX.cpp | 29 ++++-
clang/lib/CodeGen/Targets/SPIR.cpp | 2 +-
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 119 ++++++++++++++++--
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 12 +-
clang/lib/Sema/HLSLExternalSemaSource.cpp | 31 +++++
clang/lib/Sema/SemaHLSL.cpp | 49 +++++++-
clang/lib/Sema/TreeTransform.h | 16 ++-
.../AST/HLSL/MultiSampledTextures-AST.hlsl | 97 ++++++++++++++
.../resources/MultiSampledTextures-Load.hlsl | 64 ++++++++++
...pledTextures-default-explicit-binding.hlsl | 31 +++++
.../MultiSampledTextures-default.hlsl | 27 ++++
.../resources/MultiSampledTextures-pch.hlsl | 23 ++++
.../resources/Textures-Subscript.hlsl | 55 ++++----
.../Resources/MultiSampledTextures-Sema.hlsl | 55 ++++++++
llvm/include/llvm/IR/IntrinsicsDirectX.td | 6 +
llvm/include/llvm/IR/IntrinsicsSPIRV.td | 6 +
26 files changed, 669 insertions(+), 75 deletions(-)
create mode 100644 clang/test/AST/HLSL/MultiSampledTextures-AST.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/MultiSampledTextures-Load.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/MultiSampledTextures-default-explicit-binding.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/MultiSampledTextures-default.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl
create mode 100644 clang/test/SemaHLSL/Resources/MultiSampledTextures-Sema.hlsl
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 7ed6509c3c16c..5a4a0840c7919 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -296,7 +296,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
DependentBitIntTypes;
mutable llvm::FoldingSet<BTFTagAttributedType> BTFTagAttributedTypes;
mutable llvm::FoldingSet<OverflowBehaviorType> OverflowBehaviorTypes;
- llvm::FoldingSet<HLSLAttributedResourceType> HLSLAttributedResourceTypes;
+ mutable llvm::ContextualFoldingSet<HLSLAttributedResourceType, ASTContext &>
+ HLSLAttributedResourceTypes;
llvm::FoldingSet<HLSLInlineSpirvType> HLSLInlineSpirvTypes;
mutable llvm::FoldingSet<CountAttributedType> CountAttributedTypes;
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 00b6f1b20ad8e..370f129ce02ac 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -6850,14 +6850,18 @@ class HLSLAttributedResourceType : public Type, public llvm::FoldingSetNode {
LLVM_PREFERRED_TYPE(bool)
uint8_t IsMultiSampled : 1;
+ /// The N in Texture2DMS<T, N>; null for every other resource.
+ Expr *SampleCountExpr;
+
Attributes(llvm::dxil::ResourceClass ResourceClass,
llvm::dxil::ResourceDimension ResourceDimension,
bool IsROV = false, bool RawBuffer = false,
bool IsCounter = false, bool IsArray = false,
- bool IsMultiSampled = false)
+ bool IsMultiSampled = false, Expr *SampleCountExpr = nullptr)
: ResourceClass(ResourceClass), ResourceDimension(ResourceDimension),
IsROV(IsROV), RawBuffer(RawBuffer), IsCounter(IsCounter),
- IsArray(IsArray), IsMultiSampled(IsMultiSampled) {}
+ IsArray(IsArray), IsMultiSampled(IsMultiSampled),
+ SampleCountExpr(SampleCountExpr) {}
Attributes(llvm::dxil::ResourceClass ResourceClass)
: Attributes(ResourceClass, llvm::dxil::ResourceDimension::Unknown) {}
@@ -6870,10 +6874,10 @@ class HLSLAttributedResourceType : public Type, public llvm::FoldingSetNode {
friend bool operator==(const Attributes &LHS, const Attributes &RHS) {
return std::tie(LHS.ResourceClass, LHS.ResourceDimension, LHS.IsROV,
LHS.RawBuffer, LHS.IsCounter, LHS.IsArray,
- LHS.IsMultiSampled) ==
+ LHS.IsMultiSampled, LHS.SampleCountExpr) ==
std::tie(RHS.ResourceClass, RHS.ResourceDimension, RHS.IsROV,
RHS.RawBuffer, RHS.IsCounter, RHS.IsArray,
- RHS.IsMultiSampled);
+ RHS.IsMultiSampled, RHS.SampleCountExpr);
}
friend bool operator!=(const Attributes &LHS, const Attributes &RHS) {
return !(LHS == RHS);
@@ -6888,16 +6892,17 @@ class HLSLAttributedResourceType : public Type, public llvm::FoldingSetNode {
const Attributes Attrs;
HLSLAttributedResourceType(QualType Wrapped, QualType Contained,
- const Attributes &Attrs)
- : Type(HLSLAttributedResource, QualType(),
- Contained.isNull() ? TypeDependence::None
- : Contained->getDependence()),
- WrappedType(Wrapped), ContainedType(Contained), Attrs(Attrs) {}
+ const Attributes &Attrs);
+
+ /// WrappedType is always __hlsl_resource_t, so it never contributes.
+ static TypeDependence computeDependence(QualType Contained,
+ const Attributes &Attrs);
public:
QualType getWrappedType() const { return WrappedType; }
QualType getContainedType() const { return ContainedType; }
bool hasContainedType() const { return !ContainedType.isNull(); }
+ Expr *getSampleCountExpr() const { return Attrs.SampleCountExpr; }
const Attributes &getAttrs() const { return Attrs; }
bool isRaw() const { return Attrs.RawBuffer; }
bool isStructured() const { return !ContainedType->isChar8Type(); }
@@ -6905,22 +6910,13 @@ class HLSLAttributedResourceType : public Type, public llvm::FoldingSetNode {
bool isSugared() const { return false; }
QualType desugar() const { return QualType(this, 0); }
- void Profile(llvm::FoldingSetNodeID &ID) {
- Profile(ID, WrappedType, ContainedType, Attrs);
+ void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Ctx) {
+ Profile(ID, Ctx, WrappedType, ContainedType, Attrs);
}
- static void Profile(llvm::FoldingSetNodeID &ID, QualType Wrapped,
- QualType Contained, const Attributes &Attrs) {
- ID.AddPointer(Wrapped.getAsOpaquePtr());
- ID.AddPointer(Contained.getAsOpaquePtr());
- ID.AddInteger(static_cast<uint32_t>(Attrs.ResourceClass));
- ID.AddInteger(static_cast<uint32_t>(Attrs.ResourceDimension));
- ID.AddBoolean(Attrs.IsROV);
- ID.AddBoolean(Attrs.RawBuffer);
- ID.AddBoolean(Attrs.IsCounter);
- ID.AddBoolean(Attrs.IsArray);
- ID.AddBoolean(Attrs.IsMultiSampled);
- }
+ static void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Ctx,
+ QualType Wrapped, QualType Contained,
+ const Attributes &Attrs);
static bool classof(const Type *T) {
return T->getTypeClass() == HLSLAttributedResource;
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index f202b889286bf..97eacae1a80b7 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -690,11 +690,15 @@ let Class = HLSLAttributedResourceType in {
def : Property<"isMultiSampled", Bool> {
let Read = [{ node->getAttrs().IsMultiSampled }];
}
+ def : Property<"sampleCountExpr", Optional<ExprRef>> {
+ let Read = [{ makeOptionalFromPointer(node->getSampleCountExpr()) }];
+ }
def : Creator<[{
HLSLAttributedResourceType::Attributes attrs(
static_cast<llvm::dxil::ResourceClass>(resClass),
static_cast<llvm::dxil::ResourceDimension>(resDimension), isROV,
- rawBuffer, isCounter, isArray, isMultiSampled);
+ rawBuffer, isCounter, isArray, isMultiSampled,
+ makePointerFromOptional(sampleCountExpr));
return ctx.getHLSLAttributedResourceType(wrappedTy, containedTy, attrs);
}]>;
}
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 344a712ddc585..3239938aff9b3 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -5335,6 +5335,12 @@ def HLSLResourceLoadLevel : LangBuiltin<"HLSL_LANG"> {
let Prototype = "void(...)";
}
+def HLSLResourceLoadMS : LangBuiltin<"HLSL_LANG"> {
+ let Spellings = ["__builtin_hlsl_resource_load_ms"];
+ let Attributes = [NoThrow];
+ let Prototype = "void(...)";
+}
+
def HLSLResourceSample : LangBuiltin<"HLSL_LANG"> {
let Spellings = ["__builtin_hlsl_resource_sample"];
let Attributes = [NoThrow];
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 8928524e49783..fd9df822cd632 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -60,7 +60,8 @@ using llvm::dxil::ResourceClass;
// longer need to create builtin buffer types in HLSLExternalSemaSource.
bool CreateHLSLAttributedResourceType(
Sema &S, QualType Wrapped, ArrayRef<const Attr *> AttrList,
- QualType &ResType, HLSLAttributedResourceLocInfo *LocInfo = nullptr);
+ QualType &ResType, HLSLAttributedResourceLocInfo *LocInfo = nullptr,
+ Expr *SampleCountExpr = nullptr);
enum class BindingType : uint8_t { NotAssigned, Explicit, Implicit };
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 2228811546c0f..a5b775a60f401 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -938,6 +938,7 @@ ASTContext::ASTContext(LangOptions &LOpts, SourceManager &SM,
DependentTypeOfExprTypes(this_()), DependentDecltypeTypes(this_()),
DependentPackIndexingTypes(this_()), TemplateSpecializationTypes(this_()),
AttributedTypes(this_()), DependentBitIntTypes(this_()),
+ HLSLAttributedResourceTypes(this_()),
SubstTemplateTemplateParmPacks(this_()), DeducedTemplates(this_()),
ArrayParameterTypes(this_()), CanonTemplateTemplateParms(this_()),
SourceMgr(SM), LangOpts(LOpts),
@@ -5874,7 +5875,7 @@ QualType ASTContext::getHLSLAttributedResourceType(
const HLSLAttributedResourceType::Attributes &Attrs) {
llvm::FoldingSetNodeID ID;
- HLSLAttributedResourceType::Profile(ID, Wrapped, Contained, Attrs);
+ HLSLAttributedResourceType::Profile(ID, *this, Wrapped, Contained, Attrs);
void *InsertPos = nullptr;
HLSLAttributedResourceType *Ty =
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index db7d223d56af3..8c0c314df97f0 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -2036,9 +2036,10 @@ ExpectedType clang::ASTNodeImporter::VisitOverflowBehaviorType(
ExpectedType clang::ASTNodeImporter::VisitHLSLAttributedResourceType(
const clang::HLSLAttributedResourceType *T) {
Error Err = Error::success();
- const HLSLAttributedResourceType::Attributes &ToAttrs = T->getAttrs();
+ HLSLAttributedResourceType::Attributes ToAttrs = T->getAttrs();
QualType ToWrappedType = importChecked(Err, T->getWrappedType());
QualType ToContainedType = importChecked(Err, T->getContainedType());
+ ToAttrs.SampleCountExpr = importChecked(Err, T->getSampleCountExpr());
if (Err)
return std::move(Err);
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 42d148715bc40..db3c94e13b7bc 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5952,6 +5952,42 @@ std::string FunctionEffectWithCondition::description() const {
return Result;
}
+TypeDependence
+HLSLAttributedResourceType::computeDependence(QualType Contained,
+ const Attributes &Attrs) {
+ TypeDependence Deps = TypeDependence::None;
+ if (!Contained.isNull())
+ Deps |= Contained->getDependence();
+ if (Attrs.SampleCountExpr)
+ Deps |= toTypeDependence(Attrs.SampleCountExpr->getDependence());
+ return Deps;
+}
+
+HLSLAttributedResourceType::HLSLAttributedResourceType(QualType Wrapped,
+ QualType Contained,
+ const Attributes &Attrs)
+ : Type(HLSLAttributedResource, QualType(),
+ computeDependence(Contained, Attrs)),
+ WrappedType(Wrapped), ContainedType(Contained), Attrs(Attrs) {}
+
+void HLSLAttributedResourceType::Profile(llvm::FoldingSetNodeID &ID,
+ const ASTContext &Ctx,
+ QualType Wrapped, QualType Contained,
+ const Attributes &Attrs) {
+ ID.AddPointer(Wrapped.getAsOpaquePtr());
+ ID.AddPointer(Contained.getAsOpaquePtr());
+ ID.AddInteger(static_cast<uint32_t>(Attrs.ResourceClass));
+ ID.AddInteger(static_cast<uint32_t>(Attrs.ResourceDimension));
+ ID.AddBoolean(Attrs.IsROV);
+ ID.AddBoolean(Attrs.RawBuffer);
+ ID.AddBoolean(Attrs.IsCounter);
+ ID.AddBoolean(Attrs.IsArray);
+ ID.AddBoolean(Attrs.IsMultiSampled);
+ ID.AddBoolean(Attrs.SampleCountExpr != nullptr);
+ if (Attrs.SampleCountExpr)
+ Attrs.SampleCountExpr->Profile(ID, Ctx, /*Canonical=*/true);
+}
+
const HLSLAttributedResourceType *
HLSLAttributedResourceType::findHandleTypeOnResource(const Type *RT) {
// If the type RT is an HLSL resource class, the first field must
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index 1bda113143e07..86f2c3be1d065 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -776,6 +776,25 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
return Builder.CreateIntrinsic(
RetTy, CGM.getHLSLRuntime().getLoadLevelIntrinsic(), Args);
}
+ case Builtin::BI__builtin_hlsl_resource_load_ms: {
+ Value *HandleOp = EmitScalarExpr(E->getArg(0));
+ Value *CoordOp = EmitScalarExpr(E->getArg(1));
+ Value *SampleOp = EmitScalarExpr(E->getArg(2));
+ if (SampleOp->getType() != Builder.getInt32Ty())
+ SampleOp = Builder.CreateIntCast(SampleOp, Builder.getInt32Ty(),
+ /*isSigned=*/true);
+ const HLSLAttributedResourceType *RT = getRequiredHandleType(E, 0);
+
+ SmallVector<Value *, 4> Args;
+ Args.push_back(HandleOp);
+ Args.push_back(CoordOp);
+ Args.push_back(SampleOp);
+ Args.push_back(emitHlslOffset(*this, E, 3, getOffsetType(CGM, RT)));
+
+ llvm::Type *RetTy = ConvertType(E->getType());
+ return Builder.CreateIntrinsic(
+ RetTy, CGM.getHLSLRuntime().getLoadMSIntrinsic(), Args);
+ }
case Builtin::BI__builtin_hlsl_resource_sample_cmp: {
Value *HandleOp = EmitScalarExpr(E->getArg(0));
Value *SamplerOp = EmitScalarExpr(E->getArg(1));
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 0664eef464f98..4b8e4b6a96a24 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -210,6 +210,7 @@ class CGHLSLRuntime {
GENERATE_HLSL_INTRINSIC_FUNCTION(GetDimensionsLevelsXY,
resource_getdimensions_levels_xy)
GENERATE_HLSL_INTRINSIC_FUNCTION(LoadLevel, resource_load_level)
+ GENERATE_HLSL_INTRINSIC_FUNCTION(LoadMS, resource_load_ms)
GENERATE_HLSL_INTRINSIC_FUNCTION(CalculateLod, resource_calculate_lod)
GENERATE_HLSL_INTRINSIC_FUNCTION(CalculateLodUnclamped,
resource_calculate_lod_unclamped)
diff --git a/clang/lib/CodeGen/Targets/DirectX.cpp b/clang/lib/CodeGen/Targets/DirectX.cpp
index f483a7b867016..29998f6dfb16f 100644
--- a/clang/lib/CodeGen/Targets/DirectX.cpp
+++ b/clang/lib/CodeGen/Targets/DirectX.cpp
@@ -71,15 +71,30 @@ llvm::Type *DirectXTargetCodeGenInfo::getHLSLType(
ResAttrs.ResourceDimension != llvm::dxil::ResourceDimension::Unknown;
assert((!IsRawBuffer || !IsTexture) && "A resource cannot be both a raw "
"buffer and a texture.");
+ bool IsMultiSampledTexture = IsTexture && ResAttrs.IsMultiSampled;
llvm::StringRef TypeName = "dx.TypedBuffer";
if (IsRawBuffer)
TypeName = "dx.RawBuffer";
+ else if (IsMultiSampledTexture)
+ TypeName = "dx.MSTexture";
else if (IsTexture)
TypeName = "dx.Texture";
- SmallVector<unsigned, 4> Ints = {/*IsWriteable*/ ResAttrs.ResourceClass ==
- llvm::dxil::ResourceClass::UAV,
- /*IsROV*/ ResAttrs.IsROV};
+ // The second int operand is overloaded: dx.Texture holds IsROV there,
+ // dx.MSTexture holds the sample count. A sample count of 0 means the count
+ // comes from the bound resource at runtime, which is also what a
+ // Texture2DMS<T> written without an explicit N lowers to.
+ unsigned SampleCount = 0;
+ if (const Expr *SCE = ResAttrs.SampleCountExpr) {
+ std::optional<llvm::APSInt> Count =
+ SCE->getIntegerConstantExpr(CGM.getContext());
+ if (Count && Count->isNonNegative())
+ SampleCount = Count->getZExtValue();
+ }
+ SmallVector<unsigned, 4> Ints = {
+ /*IsWriteable*/ ResAttrs.ResourceClass ==
+ llvm::dxil::ResourceClass::UAV,
+ IsMultiSampledTexture ? SampleCount : /*IsROV*/ ResAttrs.IsROV};
if (!IsRawBuffer) {
const clang::Type *ElemType = ContainedTy->getUnqualifiedDesugaredType();
if (ElemType->isVectorType())
@@ -97,10 +112,12 @@ llvm::Type *DirectXTargetCodeGenInfo::getHLSLType(
RK = llvm::dxil::ResourceKind::Texture1D;
break;
case llvm::dxil::ResourceDimension::Dim2D:
- if (ResAttrs.IsArray)
- RK = llvm::dxil::ResourceKind::Texture2DArray;
+ if (ResAttrs.IsMultiSampled)
+ RK = ResAttrs.IsArray ? llvm::dxil::ResourceKind::Texture2DMSArray
+ : llvm::dxil::ResourceKind::Texture2DMS;
else
- RK = llvm::dxil::ResourceKind::Texture2D;
+ RK = ResAttrs.IsArray ? llvm::dxil::ResourceKind::Texture2DArray
+ : llvm::dxil::ResourceKind::Texture2D;
break;
case llvm::dxil::ResourceDimension::Dim3D:
RK = llvm::dxil::ResourceKind::Texture3D;
diff --git a/clang/lib/CodeGen/Targets/SPIR.cpp b/clang/lib/CodeGen/Targets/SPIR.cpp
index c8f7c03474be9..dba7d4fa73923 100644
--- a/clang/lib/CodeGen/Targets/SPIR.cpp
+++ b/clang/lib/CodeGen/Targets/SPIR.cpp
@@ -936,7 +936,7 @@ llvm::Type *CommonSPIRTargetCodeGenInfo::getSPIRVImageTypeFromHLSLResource(
IntParams[2] = static_cast<unsigned>(attributes.IsArray);
// MS
- IntParams[3] = 0;
+ IntParams[3] = static_cast<unsigned>(attributes.IsMultiSampled);
// Sampled
IntParams[4] =
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index f8018729b4644..abe97d34ea72c 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -110,6 +110,10 @@ struct TemplateParameterListBuilder {
TemplateParameterListBuilder &
addTypeParameter(StringRef Name, QualType DefaultValue = QualType());
+ TemplateParameterListBuilder &
+ addNonTypeParameter(StringRef Name, QualType Ty,
+ Expr *DefaultValue = nullptr);
+
ConceptSpecializationExpr *
constructConceptSpecializationExpr(Sema &S, ConceptDecl *CD);
@@ -295,6 +299,28 @@ TemplateParameterListBuilder::addTypeParameter(StringRef Name,
return *this;
}
+TemplateParameterListBuilder &
+TemplateParameterListBuilder::addNonTypeParameter(StringRef Name, QualType Ty,
+ Expr *DefaultValue) {
+ assert(!Builder.Record->isCompleteDefinition() &&
+ "record is already complete");
+ ASTContext &AST = Builder.SemaRef.getASTContext();
+ unsigned Position = static_cast<unsigned>(Params.size());
+ auto *Decl = NonTypeTemplateParmDecl::Create(
+ AST, Builder.Record->getDeclContext(), SourceLocation(), SourceLocation(),
+ /* TemplateDepth */ 0, Position,
+ &AST.Idents.get(Name, tok::TokenKind::identifier), Ty,
+ /* ParameterPack */ false, AST.getTrivialTypeSourceInfo(Ty));
+ if (DefaultValue)
+ Decl->setDefaultArgument(
+ AST, Builder.SemaRef.getTrivialTemplateArgumentLoc(
+ TemplateArgument(DefaultValue, /*IsCanonical=*/false), Ty,
+ SourceLocation()));
+
+ Params.emplace_back(Decl);
+ return *this;
+}
+
// The concept specialization expression (CSE) constructed in
// constructConceptSpecializationExpr is constructed so that it
// matches the CSE that is constructed when parsing the below C++ code:
@@ -1039,18 +1065,18 @@ BuiltinTypeDeclBuilder::addBufferHandles(ResourceClass RC, bool IsROV,
AccessSpecifier Access) {
QualType ElementTy = getHandleElementType();
addHandleMember(RC, ResourceDimension::Unknown, IsROV, RawBuffer,
- /*IsArray=*/false, ElementTy, Access);
+ /*IsArray=*/false, ElementTy, /*IsMultiSampled=*/false,
+ Access);
if (HasCounter)
addCounterHandleMember(RC, IsROV, RawBuffer, ElementTy, Access);
return *this;
}
-BuiltinTypeDeclBuilder &
-BuiltinTypeDeclBuilder::addTextureHandle(ResourceClass RC, bool IsROV,
- bool IsArray, ResourceDimension RD,
- AccessSpecifier Access) {
+BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addTextureHandle(
+ ResourceClass RC, bool IsROV, bool IsArray, ResourceDimension RD,
+ bool IsMultiSampled, AccessSpecifier Access) {
addHandleMember(RC, RD, IsROV, /*RawBuffer=*/false, IsArray,
- getHandleElementType(), Access);
+ getHandleElementType(), IsMultiSampled, Access);
return *this;
}
@@ -1112,9 +1138,11 @@ CXXRecordDecl *BuiltinTypeDeclBuilder::addPrivateNestedRecord(StringRef Name) {
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addHandleMember(
ResourceClass RC, ResourceDimension RD, bool IsROV, bool RawBuffer,
- bool IsArray, QualType ElementTy, AccessSpecifier Access) {
+ bool IsArray, QualType ElementTy, bool IsMultiSampled,
+ AccessSpecifier Access) {
return addResourceMember("__handle", RC, RD, IsROV, RawBuffer,
- /*IsCounter=*/false, IsArray, ElementTy, Access);
+ /*IsCounter=*/false, IsArray, ElementTy,
+ IsMultiSampled, Access);
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addCounterHandleMember(
@@ -1122,13 +1150,14 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addCounterHandleMember(
AccessSpecifier Access) {
return addResourceMember("__counter_handle", RC, ResourceDimension::Unknown,
IsROV, RawBuffer, /*IsCounter=*/true,
- /*IsArray=*/false, ElementTy, Access);
+ /*IsArray=*/false, ElementTy,
+ /*IsMultiSampled=*/false, Access);
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addResourceMember(
StringRef MemberName, ResourceClass RC, ResourceDimension RD, bool IsROV,
bool RawBuffer, bool IsCounter, bool IsArray, QualType ElementTy,
- AccessSpecifier Access) {
+ bool IsMultiSampled, AccessSpecifier Access) {
assert(!Record->isCompleteDefinition() && "record is already complete");
ASTContext &Ctx = SemaRef.getASTContext();
@@ -1154,9 +1183,20 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addResourceMember(
Attrs.push_back(HLSLIsCounterAttr::CreateImplicit(Ctx));
if (IsArray)
Attrs.push_back(HLSLIsArrayAttr::CreateImplicit(Ctx));
+ Expr *SampleCountExpr = nullptr;
+ if (IsMultiSampled) {
+ Attrs.push_back(HLSLIsMultiSampledAttr::CreateImplicit(Ctx));
+ ClassTemplateDecl *CTD = Record->getDescribedClassTemplate();
+ assert(CTD && "multisampled texture must be a class template");
+ auto *NTTP = cast<NonTypeTemplateParmDecl>(
+ CTD->getTemplateParameters()->getParam(1));
+ SampleCountExpr = SemaRef.BuildDeclRefExpr(NTTP, NTTP->getType(),
+ VK_PRValue, SourceLocation());
+ }
if (CreateHLSLAttributedResourceType(SemaRef, Ctx.HLSLResourceTy, Attrs,
- AttributedResTy))
+ AttributedResTy, /*LocInfo=*/nullptr,
+ SampleCountExpr))
addMemberVariable(MemberName, AttributedResTy, {}, Access);
return *this;
}
@@ -1466,7 +1506,7 @@ CXXRecordDecl *BuiltinTypeDeclBuilder::addMipsSliceType(ResourceDimension Dim,
.addHandleMember(getResourceAttrs().ResourceClass, Dim,
getResourceAttrs().IsROV, /*RawBuffer=*/false,
getResourceAttrs().IsArray, ReturnType,
- AccessSpecifier::AS_public)
+ /*IsMultiSampled=*/false, AccessSpecifier::AS_public)
.addMemberVariable("__level", IntTy, {}, AccessSpecifier::AS_public)
.addDefaultHandleConstructor(AccessSpecifier::AS_protected)
.addCopyConstructor(AccessSpecifier::AS_protected)
@@ -1510,7 +1550,7 @@ CXXRecordDecl *BuiltinTypeDeclBuilder::addMipsType(ResourceDimension Dim,
.addHandleMember(getResourceAttrs().ResourceClass, Dim,
getResourceAttrs().IsROV, /*RawBuffer=*/false,
getResourceAttrs().IsArray, ReturnType,
- AccessSpecifier::AS_public)
+ /*IsMultiSampled=*/false, AccessSpecifier::AS_public)
.addDefaultHandleConstructor(AccessSpecifier::AS_protected)
.addCopyConstructor(AccessSpecifier::AS_protected)
.addCopyAssignmentOperator(AccessSpecifier::AS_protected);
@@ -1589,6 +1629,39 @@ BuiltinTypeDeclBuilder::addTextureLoadMethods(ResourceDimension Dim,
.finalize();
}
+BuiltinTypeDeclBuilder &
+BuiltinTypeDeclBuilder::addTextureLoadMSMethods(ResourceDimension Dim,
+ bool IsArray) {
+ assert(!Record->isCompleteDefinition() && "record is already complete");
+ ASTContext &AST = Record->getASTContext();
+ uint32_t OffsetSize = getResourceDimensions(Dim);
+ // Multisampled textures use a plain location (no mip/LOD component).
+ uint32_t CoordSize = OffsetSize + (IsArray ? 1 : 0);
+ QualType IntTy = AST.IntTy;
+ QualType OffsetTy = AST.getExtVectorType(IntTy, OffsetSize);
+ QualType LocationTy = AST.getExtVectorType(IntTy, CoordSize);
+ QualType ReturnType = getHandleElementType();
+
+ using PH = BuiltinTypeMethodBuilder::PlaceHolder;
+
+ // T Load(int2 location, int sampleIndex)
+ BuiltinTypeMethodBuilder(*this, "Load", ReturnType)
+ .addParam("Location", LocationTy)
+ .addParam("SampleIndex", IntTy)
+ .callBuiltin("__builtin_hlsl_resource_load_ms", ReturnType, PH::Handle,
+ PH::_0, PH::_1)
+ .finalize();
+
+ // T Load(int2 location, int sampleIndex, int2 offset)
+ return BuiltinTypeMethodBuilder(*this, "Load", ReturnType)
+ .addParam("Location", LocationTy)
+ .addParam("SampleIndex", IntTy)
+ .addParam("Offset", OffsetTy)
+ .callBuiltin("__builtin_hlsl_resource_load_ms", ReturnType, PH::Handle,
+ PH::_0, PH::_1, PH::_2)
+ .finalize();
+}
+
BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addByteAddressBufferLoadMethods() {
assert(!Record->isCompleteDefinition() && "record is already complete");
@@ -2237,6 +2310,26 @@ BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
return Builder.finalizeTemplateArgs(CD);
}
+BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addMSTextureTemplateParams(
+ StringRef ElementName, StringRef SampleCountName, ConceptDecl *CD) {
+ if (Record->isCompleteDefinition()) {
+ assert(Template && "existing record it not a template");
+ assert(Template->getTemplateParameters()->size() == 2 &&
+ "template param count mismatch");
+ return *this;
+ }
+
+ ASTContext &AST = SemaRef.getASTContext();
+ TemplateParameterListBuilder Builder = TemplateParameterListBuilder(*this);
+ // No default element type (`Texture2DMS` and `Texture2DMS<>` are errors).
+ // A sample count of 0 means the count comes from the bound resource rather
+ // than denoting zero samples.
+ Builder.addTypeParameter(ElementName);
+ Builder.addNonTypeParameter(SampleCountName, AST.IntTy,
+ getConstantIntExpr(0));
+ return Builder.finalizeTemplateArgs(CD);
+}
+
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addIncrementCounterMethod() {
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
QualType UnsignedIntTy = SemaRef.getASTContext().UnsignedIntTy;
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index 09cf1fceca116..fdcf8a92c396b 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -68,6 +68,12 @@ class BuiltinTypeDeclBuilder {
BuiltinTypeDeclBuilder &
addSimpleTemplateParams(ArrayRef<StringRef> Names,
ArrayRef<QualType> DefaultTypes, ConceptDecl *CD);
+ // Adds `<typename element_type, int sample_count = 0>` for multisampled
+ // textures, with \p CD constraining the element type. Unlike the other
+ // textures the element type has no default argument.
+ BuiltinTypeDeclBuilder &addMSTextureTemplateParams(StringRef ElementName,
+ StringRef SampleCountName,
+ ConceptDecl *CD);
CXXRecordDecl *finalizeForwardDeclaration() { return Record; }
BuiltinTypeDeclBuilder &completeDefinition();
@@ -81,7 +87,7 @@ class BuiltinTypeDeclBuilder {
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &
addTextureHandle(ResourceClass RC, bool IsROV, bool IsArray,
- ResourceDimension RD,
+ ResourceDimension RD, bool IsMultiSampled = false,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &addSamplerHandle();
BuiltinTypeDeclBuilder &addConstantBufferConversionToType();
@@ -104,6 +110,8 @@ class BuiltinTypeDeclBuilder {
BuiltinTypeDeclBuilder &addLoadMethods();
BuiltinTypeDeclBuilder &addTextureLoadMethods(ResourceDimension Dim,
bool IsArray = false);
+ BuiltinTypeDeclBuilder &addTextureLoadMSMethods(ResourceDimension Dim,
+ bool IsArray = false);
BuiltinTypeDeclBuilder &addByteAddressBufferLoadMethods();
BuiltinTypeDeclBuilder &addByteAddressBufferStoreMethods();
BuiltinTypeDeclBuilder &addSampleMethods(ResourceDimension Dim,
@@ -150,6 +158,7 @@ class BuiltinTypeDeclBuilder {
addResourceMember(StringRef MemberName, ResourceClass RC,
ResourceDimension RD, bool IsROV, bool RawBuffer,
bool IsCounter, bool IsArray, QualType ElementTy,
+ bool IsMultiSampled = false,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &addFriend(CXXRecordDecl *Friend);
CXXRecordDecl *addPrivateNestedRecord(StringRef Name);
@@ -158,6 +167,7 @@ class BuiltinTypeDeclBuilder {
BuiltinTypeDeclBuilder &
addHandleMember(ResourceClass RC, ResourceDimension RD, bool IsROV,
bool RawBuffer, bool IsArray, QualType ElementTy,
+ bool IsMultiSampled = false,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &
addCounterHandleMember(ResourceClass RC, bool IsROV, bool RawBuffer,
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index 7578f20a27f18..8ce9b2976fe7d 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -297,6 +297,24 @@ static BuiltinTypeDeclBuilder setupRWTextureType(CXXRecordDecl *Decl, Sema &S,
.addStaticInitializationFunctions(false);
}
+/// Set up Texture2DMS (multisampled) type: SRV texture with only operator[]
+/// and sample-indexed Load. It does not support sampling, gather, LOD, or mips.
+static BuiltinTypeDeclBuilder setupMSTextureType(CXXRecordDecl *Decl, Sema &S,
+ bool IsArray,
+ ResourceDimension Dim) {
+ return BuiltinTypeDeclBuilder(S, Decl)
+ .addTextureHandle(ResourceClass::SRV, /*IsROV=*/false, IsArray, Dim,
+ /*IsMultiSampled=*/true)
+ .addTextureLoadMSMethods(Dim, IsArray)
+ .addArraySubscriptOperators(Dim, IsArray)
+ // TODO: Add MS-specific GetDimensions (with a NumberOfSamples output);
+ // the generic addGetDimensionsMethods is mip-based and unsuitable here.
+ .addDefaultHandleConstructor()
+ .addCopyConstructor()
+ .addCopyAssignmentOperator()
+ .addStaticInitializationFunctions(false);
+}
+
// Add a partial specialization for a template. The `TextureTemplate` is
// `Texture<element_type>`, and it will be specialized for vectors:
// `Texture<vector<element_type, element_count>>`.
@@ -765,6 +783,19 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
ResourceDimension::Dim2D)
.completeDefinition();
});
+
+ // Texture2DMS — like Texture2D but multisampled, and the element type has no
+ // default argument.
+ Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "Texture2DMS")
+ .addMSTextureTemplateParams("element_type", "sample_count",
+ TypedBufferConcept)
+ .finalizeForwardDeclaration();
+
+ onCompletion(Decl, [this](CXXRecordDecl *Decl) {
+ setupMSTextureType(Decl, *SemaPtr, /*IsArray=*/false,
+ ResourceDimension::Dim2D)
+ .completeDefinition();
+ });
}
// Build a single overload of an HLSL atomic intrinsic in the hlsl namespace.
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index ae5e022399e49..30635ac0dd000 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2096,7 +2096,8 @@ void SemaHLSL::handleShaderAttr(Decl *D, const ParsedAttr &AL) {
bool clang::CreateHLSLAttributedResourceType(
Sema &S, QualType Wrapped, ArrayRef<const Attr *> AttrList,
- QualType &ResType, HLSLAttributedResourceLocInfo *LocInfo) {
+ QualType &ResType, HLSLAttributedResourceLocInfo *LocInfo,
+ Expr *SampleCountExpr) {
assert(AttrList.size() && "expected list of resource attributes");
QualType ContainedTy = QualType();
@@ -2167,6 +2168,7 @@ bool clang::CreateHLSLAttributedResourceType(
return false;
}
ResAttrs.IsMultiSampled = true;
+ ResAttrs.SampleCountExpr = SampleCountExpr;
break;
case attr::HLSLIsCounter:
if (ResAttrs.IsCounter) {
@@ -3950,6 +3952,49 @@ static bool CheckLoadLevelBuiltin(Sema &S, CallExpr *TheCall) {
return false;
}
+static bool CheckLoadMSBuiltin(Sema &S, CallExpr *TheCall) {
+ if (S.checkArgCountRange(TheCall, 3, 4))
+ return true;
+
+ // Check the multisampled texture handle.
+ if (CheckResourceHandle(&S, TheCall, 0,
+ [](const HLSLAttributedResourceType *ResType) {
+ return !ResType->getAttrs().IsMultiSampled;
+ }))
+ return true;
+
+ auto *ResourceTy =
+ TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
+
+ // Check the location (int2 for Texture2DMS, int3 for Texture2DMSArray).
+ // Unlike Load on regular textures, there is no mip/LOD component.
+ unsigned ResourceDim =
+ getResourceDimensions(ResourceTy->getAttrs().ResourceDimension);
+ unsigned LocationDim = ResourceDim + (ResourceTy->getAttrs().IsArray ? 1 : 0);
+ QualType LocationTy = TheCall->getArg(1)->getType();
+ if (CheckVectorElementCount(&S, LocationTy, S.Context.IntTy, LocationDim,
+ TheCall->getArg(1)->getBeginLoc()))
+ return true;
+
+ // Check the sample index operand (scalar int).
+ if (!TheCall->getArg(2)->getType()->isIntegerType()) {
+ S.Diag(TheCall->getArg(2)->getBeginLoc(), diag::err_typecheck_expect_int)
+ << TheCall->getArg(2)->getType();
+ return true;
+ }
+
+ // Check the offset operand (int2 for 2D textures; no array slice).
+ if (TheCall->getNumArgs() > 3) {
+ if (CheckVectorElementCount(&S, TheCall->getArg(3)->getType(),
+ S.Context.IntTy, ResourceDim,
+ TheCall->getArg(3)->getBeginLoc()))
+ return true;
+ }
+
+ TheCall->setType(ResourceTy->getContainedType());
+ return false;
+}
+
static bool CheckSamplingBuiltin(Sema &S, CallExpr *TheCall, SampleKind Kind) {
unsigned MinArgs, MaxArgs;
if (Kind == SampleKind::Sample) {
@@ -4173,6 +4218,8 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
}
case Builtin::BI__builtin_hlsl_resource_load_level:
return CheckLoadLevelBuiltin(SemaRef, TheCall);
+ case Builtin::BI__builtin_hlsl_resource_load_ms:
+ return CheckLoadMSBuiltin(SemaRef, TheCall);
case Builtin::BI__builtin_hlsl_resource_sample:
return CheckSamplingBuiltin(SemaRef, TheCall, SampleKind::Sample);
case Builtin::BI__builtin_hlsl_resource_sample_bias:
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 8ed1dba3bf101..b5f4f1247025e 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -7824,11 +7824,21 @@ QualType TreeTransform<Derived>::TransformHLSLAttributedResourceType(
ContainedTy = ContainedTSI->getType();
}
+ HLSLAttributedResourceType::Attributes Attrs = oldType->getAttrs();
+ if (Attrs.SampleCountExpr) {
+ ExprResult SampleCountResult =
+ getDerived().TransformExpr(Attrs.SampleCountExpr);
+ if (SampleCountResult.isInvalid())
+ return QualType();
+ Attrs.SampleCountExpr = SampleCountResult.get();
+ }
+
QualType Result = TL.getType();
if (getDerived().AlwaysRebuild() || WrappedTy != oldType->getWrappedType() ||
- ContainedTy != oldType->getContainedType()) {
- Result = SemaRef.Context.getHLSLAttributedResourceType(
- WrappedTy, ContainedTy, oldType->getAttrs());
+ ContainedTy != oldType->getContainedType() ||
+ Attrs.SampleCountExpr != oldType->getSampleCountExpr()) {
+ Result = SemaRef.Context.getHLSLAttributedResourceType(WrappedTy,
+ ContainedTy, Attrs);
}
HLSLAttributedResourceTypeLoc NewTL =
diff --git a/clang/test/AST/HLSL/MultiSampledTextures-AST.hlsl b/clang/test/AST/HLSL/MultiSampledTextures-AST.hlsl
new file mode 100644
index 0000000000000..bd2a3b17c61c7
--- /dev/null
+++ b/clang/test/AST/HLSL/MultiSampledTextures-AST.hlsl
@@ -0,0 +1,97 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -ast-dump -disable-llvm-passes -finclude-default-header -DTEXTURE=Texture2DMS -o - %s | FileCheck %s -DTEXTURE=Texture2DMS -DINDEX_SIZE=2
+
+// The class template: an `element_type` type parameter followed by an `int`
+// `sample_count` non-type parameter that defaults to 0.
+// CHECK: ClassTemplateDecl {{.*}} [[TEXTURE]]
+// CHECK: TemplateTypeParmDecl {{.*}} element_type
+// CHECK: NonTypeTemplateParmDecl {{.*}} 'int' depth 0 index 1 sample_count
+// CHECK: TemplateArgument expr '0'
+
+// The resource handle: an SRV whose handle additionally carries [[hlsl::is_ms]].
+// CHECK: CXXRecordDecl {{.*}} [[TEXTURE]] definition
+// CHECK: FinalAttr {{.*}} Implicit final
+// CHECK-NEXT: FieldDecl {{.*}} implicit __handle '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(SRV)]]
+// CHECK-SAME{LITERAL}: [[hlsl::is_ms]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME{LITERAL}: [[hlsl::resource_dimension(2D)]]
+
+// Load(location, sampleIndex): the sample index is a separate scalar (there is
+// no packed mip level), and the read lowers to __builtin_hlsl_resource_load_ms.
+// CHECK: CXXMethodDecl {{.*}} Load 'element_type (vector<int, [[INDEX_SIZE]]>, int)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} Location 'vector<int, [[INDEX_SIZE]]>'
+// CHECK-NEXT: ParmVarDecl {{.*}} SampleIndex 'int'
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: ReturnStmt
+// CHECK-NEXT: CStyleCastExpr {{.*}} 'element_type' <Dependent>
+// CHECK-NEXT: CallExpr {{.*}} '<dependent type>'
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_load_ms' 'void (...) noexcept'
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(SRV)]]
+// CHECK-SAME{LITERAL}: [[hlsl::is_ms]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME{LITERAL}: [[hlsl::resource_dimension(2D)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[TEXTURE]]<element_type, sample_count>' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'vector<int, [[INDEX_SIZE]]>' lvalue ParmVar {{.*}} 'Location' 'vector<int, [[INDEX_SIZE]]>'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'SampleIndex' 'int'
+// CHECK-NEXT: AlwaysInlineAttr
+
+// Load(location, sampleIndex, offset): identical, with a trailing 2D offset.
+// CHECK: CXXMethodDecl {{.*}} Load 'element_type (vector<int, [[INDEX_SIZE]]>, int, vector<int, 2>)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} Location 'vector<int, [[INDEX_SIZE]]>'
+// CHECK-NEXT: ParmVarDecl {{.*}} SampleIndex 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} Offset 'vector<int, 2>'
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: ReturnStmt
+// CHECK-NEXT: CStyleCastExpr {{.*}} 'element_type' <Dependent>
+// CHECK-NEXT: CallExpr {{.*}} '<dependent type>'
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_load_ms' 'void (...) noexcept'
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[TEXTURE]]<element_type, sample_count>' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'vector<int, [[INDEX_SIZE]]>' lvalue ParmVar {{.*}} 'Location' 'vector<int, [[INDEX_SIZE]]>'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'SampleIndex' 'int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'vector<int, 2>' lvalue ParmVar {{.*}} 'Offset' 'vector<int, 2>'
+// CHECK-NEXT: AlwaysInlineAttr
+
+// operator[] returns a const reference to sample 0 (same shape as the non-MS
+// SRV subscript in Textures-AST.hlsl), via __builtin_hlsl_resource_getpointer.
+// CHECK: CXXMethodDecl {{.*}} operator[] 'const hlsl_device element_type &(vector<unsigned int, [[INDEX_SIZE]]>) const' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} Index 'vector<unsigned int, [[INDEX_SIZE]]>'
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: ReturnStmt
+// CHECK-NEXT: UnaryOperator {{.*}} 'hlsl_device element_type' lvalue prefix '*' cannot overflow
+// CHECK-NEXT: CStyleCastExpr {{.*}} 'hlsl_device element_type *' <Dependent>
+// CHECK-NEXT: CallExpr {{.*}} '<dependent type>'
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_getpointer' 'void (...) noexcept'
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'const hlsl::[[TEXTURE]]<element_type, sample_count>' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'vector<unsigned int, [[INDEX_SIZE]]>' lvalue ParmVar {{.*}} 'Index' 'vector<unsigned int, [[INDEX_SIZE]]>'
+// CHECK-NEXT: AlwaysInlineAttr
+
+// TODO(MS GetDimensions): when the multisampled GetDimensions overload is
+// implemented, dump its CHECK block here (out width/height[/elements]/
+// NumberOfSamples, on a samples-based getdimensions builtin), paralleling the
+// GetDimensions blocks in Textures-AST.hlsl.
+
+TEXTURE<float> t;
+
+// An explicit, non-default sample count binds the non-type template parameter,
+// producing a distinct specialization.
+// CHECK: ClassTemplateSpecializationDecl {{.*}} class [[TEXTURE]] definition
+// CHECK: TemplateArgument type 'vector<float, 4>'
+// CHECK: TemplateArgument integral '4'
+TEXTURE<float4, 4> tMS4;
+
+[numthreads(1, 1, 1)]
+void main() {
+ uint2 i = uint2(0, 0);
+ float x = t[i];
+ (void)x;
+ // TODO: enable once multisampled GetDimensions is implemented, paralleling
+ // the t.GetDimensions(w, h) call in Textures-AST.hlsl:
+ // uint w, h, n;
+ // t.GetDimensions(w, h, n);
+}
diff --git a/clang/test/CodeGenHLSL/resources/MultiSampledTextures-Load.hlsl b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-Load.hlsl
new file mode 100644
index 0000000000000..f8cc6e2b72b62
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-Load.hlsl
@@ -0,0 +1,64 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -o - %s | FileCheck %s --check-prefix=DXIL -DCOORD_DIM=2 -DKIND=3
+// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -o - %s | FileCheck %s --check-prefix=SPIRV -DCOORD_DIM=2 -DARRAYED=0
+
+// Load on a multisampled texture takes a separate sample index (rather than a
+// packed mip level) and lowers to the MS-specific resource.load.ms intrinsic on
+// a dx.MSTexture (DXIL) / multisampled spirv.Image (SPIR-V) handle. The correct
+// per-element-type intrinsic overload is selected, and signed integer element
+// types map to a SignedImage / IsSigned=1 handle. The resource type layout
+// itself is covered by MultiSampledTextures-default.hlsl.
+
+TEXTURE<float4> T;
+TEXTURE<float4, 4> TMS4;
+TEXTURE<float> Tf;
+TEXTURE<int> Ti;
+TEXTURE<int4> Ti4;
+
+// CHECK-LABEL: define {{.*}} <4 x float> @{{.*}}test_load
+// DXIL: call {{.*}} <4 x float> @llvm.dx.resource.load.ms.v4f32.{{.*}}(target("dx.MSTexture", <4 x float>, 0, 0, 0, [[KIND]]) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+// SPIRV: call {{.*}} <4 x float> @llvm.spv.resource.load.ms.v4f32.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], 1, 1, 0) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+float4 test_load(LOCATION_TYPE loc, int sampleIndex) {
+ return T.Load(loc, sampleIndex);
+}
+
+// The offset overload threads the constant offset through as the final operand.
+// CHECK-LABEL: define {{.*}} <4 x float> @{{.*}}test_load_offset
+// DXIL: call {{.*}} <4 x float> @llvm.dx.resource.load.ms.v4f32.{{.*}}(target("dx.MSTexture", <4 x float>, 0, 0, 0, [[KIND]]) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> %{{.*}})
+// SPIRV: call {{.*}} <4 x float> @llvm.spv.resource.load.ms.v4f32.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], 1, 1, 0) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> %{{.*}})
+float4 test_load_offset(LOCATION_TYPE loc, int sampleIndex) {
+ return T.Load(loc, sampleIndex, int2(1, 1));
+}
+
+// An explicit compile-time sample count changes only the resource handle type
+// (the dx.MSTexture sample-count operand becomes 4); the resource.load.ms call
+// is otherwise identical to the default.
+// CHECK-LABEL: define {{.*}} <4 x float> @{{.*}}test_explicit_count
+// DXIL: call {{.*}} <4 x float> @llvm.dx.resource.load.ms.v4f32.{{.*}}(target("dx.MSTexture", <4 x float>, 0, 4, 0, [[KIND]]) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+// SPIRV: call {{.*}} <4 x float> @llvm.spv.resource.load.ms.v4f32.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], 1, 1, 0) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+float4 test_explicit_count(LOCATION_TYPE loc, int sampleIndex) {
+ return TMS4.Load(loc, sampleIndex);
+}
+
+// A scalar float element selects the f32 overload.
+// CHECK-LABEL: define {{.*}} float @{{.*}}test_load_float
+// DXIL: call {{.*}} float @llvm.dx.resource.load.ms.f32.{{.*}}(target("dx.MSTexture", float, 0, 0, 0, [[KIND]]) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+// SPIRV: call {{.*}} float @llvm.spv.resource.load.ms.f32.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], 1, 1, 0) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+float test_load_float(LOCATION_TYPE loc, int sampleIndex) {
+ return Tf.Load(loc, sampleIndex);
+}
+
+// A signed integer element maps to IsSigned=1 (DXIL) / spirv.SignedImage.
+// CHECK-LABEL: define {{.*}} i32 @{{.*}}test_load_int
+// DXIL: call i32 @llvm.dx.resource.load.ms.i32.{{.*}}(target("dx.MSTexture", i32, 0, 0, 1, [[KIND]]) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+// SPIRV: call i32 @llvm.spv.resource.load.ms.i32.{{.*}}(target("spirv.SignedImage", i32, 1, 2, [[ARRAYED]], 1, 1, 0) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+int test_load_int(LOCATION_TYPE loc, int sampleIndex) {
+ return Ti.Load(loc, sampleIndex);
+}
+
+// A signed integer vector element: v4i32 overload on a SignedImage handle.
+// CHECK-LABEL: define {{.*}} <4 x i32> @{{.*}}test_load_int4
+// DXIL: call <4 x i32> @llvm.dx.resource.load.ms.v4i32.{{.*}}(target("dx.MSTexture", <4 x i32>, 0, 0, 1, [[KIND]]) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+// SPIRV: call <4 x i32> @llvm.spv.resource.load.ms.v4i32.{{.*}}(target("spirv.SignedImage", i32, 1, 2, [[ARRAYED]], 1, 1, 0) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+int4 test_load_int4(LOCATION_TYPE loc, int sampleIndex) {
+ return Ti4.Load(loc, sampleIndex);
+}
diff --git a/clang/test/CodeGenHLSL/resources/MultiSampledTextures-default-explicit-binding.hlsl b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-default-explicit-binding.hlsl
new file mode 100644
index 0000000000000..045174314a383
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-default-explicit-binding.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -o - %s | llvm-cxxfilt | FileCheck %s --check-prefixes=CHECK,DXIL -DTEXTURE=Texture2DMS -DCOORD_DIM=2 -DKIND=3
+// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -o - %s | llvm-cxxfilt | FileCheck %s --check-prefixes=CHECK,SPIRV -DTEXTURE=Texture2DMS -DCOORD_DIM=2 -DARRAYED=0
+
+// A multisampled texture with an explicit register/space binding is initialized
+// from that binding through __createFromBinding, identically to a non-MS
+// texture; only the resource handle type differs (dx.MSTexture / a multisampled
+// spirv.Image). Unlike non-multisampled textures, the element type must be
+// stated explicitly (Texture2DMS and Texture2DMS<> are both errors), so the
+// default-template / shorthand forms are not exercised here.
+
+TEXTURE<float4> explicit_binding : register(t1, space2);
+TEXTURE<float4> implicit_template : register(t0, space1);
+
+// DXIL: %"class.hlsl::[[TEXTURE]]" = type { target("dx.MSTexture", <4 x float>, 0, 0, 0, [[KIND]]) }
+// SPIRV: %"class.hlsl::[[TEXTURE]]" = type { target("spirv.Image", float, 1, 2, [[ARRAYED]], 1, 1, 0) }
+
+// CHECK: @{{.*}}explicit_binding = internal global %"class.hlsl::[[TEXTURE]]" poison, align {{[0-9]+}}
+// CHECK: @{{.*}}implicit_template = internal global %"class.hlsl::[[TEXTURE]]" poison, align {{[0-9]+}}
+
+// Each texture is initialized from its explicit register/space binding:
+// explicit_binding -> register(t1, space2) => registerNo 1, spaceNo 2
+// implicit_template -> register(t0, space1) => registerNo 0, spaceNo 1
+// CHECK: call {{.*}} @hlsl::[[TEXTURE]]<float vector[4]{{(, [0-9]+)?}}>::__createFromBinding{{.*}}(ptr {{.*}}@{{.*}}explicit_binding, i32 noundef 1, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @{{.*}})
+// CHECK: call {{.*}} @hlsl::[[TEXTURE]]<float vector[4]{{(, [0-9]+)?}}>::__createFromBinding{{.*}}(ptr {{.*}}@{{.*}}implicit_template, i32 noundef 0, i32 noundef 1, i32 noundef 1, i32 noundef 0, ptr noundef @{{.*}})
+
+float4 main(LOCATION_TYPE loc : LOC, int sampleIndex : SI) : SV_Target {
+ // A multisampled texture is read through a sample-indexed Load, not Sample.
+ // DXIL: call {{.*}} <4 x float> @llvm.dx.resource.load.ms.v4f32.{{.*}}(target("dx.MSTexture", <4 x float>, 0, 0, 0, [[KIND]]) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+ // SPIRV: call {{.*}} <4 x float> @llvm.spv.resource.load.ms.v4f32.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], 1, 1, 0) %{{.*}}, <[[COORD_DIM]] x i32> %{{.*}}, i32 %{{.*}}, <2 x i32> zeroinitializer)
+ return implicit_template.Load(loc, sampleIndex);
+}
diff --git a/clang/test/CodeGenHLSL/resources/MultiSampledTextures-default.hlsl b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-default.hlsl
new file mode 100644
index 0000000000000..e4980f3d2fb83
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-default.hlsl
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -DTEXTURE=Texture2DMS -o - %s | FileCheck %s --check-prefix=DXIL -DTEXTURE=Texture2DMS -DDXIL_ARGS="<4 x float>, 0, 0, 0, 3" -DDXIL_ARGS4="<4 x float>, 0, 4, 0, 3"
+// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -DTEXTURE=Texture2DMS -o - %s | FileCheck %s --check-prefix=SPIRV -DTEXTURE=Texture2DMS -DSPIRV_ARGS="float, 1, 2, 0, 1, 1, 0"
+
+// A multisampled texture lowers to a multisampled resource target type. For
+// DXIL this is the dedicated `dx.MSTexture` type carrying the multisampled
+// resource kind; for SPIR-V it is a `spirv.Image` with the Multisampled (MS)
+// operand set to 1.
+//
+// The Texture2DMS<T, N> sample count N is the second integer operand of
+// dx.MSTexture: it is 0 for the default (T, runtime-determined) and 4 for the
+// explicit-count global (TMS4), producing two distinct resource types. SPIR-V
+// does not encode the count, so both map to the same spirv.Image.
+
+// DXIL: %"class.hlsl::[[TEXTURE]]" = type { target("dx.MSTexture", [[DXIL_ARGS]]) }
+// DXIL: %"class.hlsl::[[TEXTURE]].0" = type { target("dx.MSTexture", [[DXIL_ARGS4]]) }
+// SPIRV: %"class.hlsl::[[TEXTURE]]" = type { target("spirv.Image", [[SPIRV_ARGS]]) }
+// SPIRV: %"class.hlsl::[[TEXTURE]].0" = type { target("spirv.Image", [[SPIRV_ARGS]]) }
+
+// DXIL: @{{.*}}T = internal global %"class.hlsl::[[TEXTURE]]" poison
+// SPIRV: @{{.*}}T = internal global %"class.hlsl::[[TEXTURE]]" poison
+TEXTURE<float4> T;
+
+// DXIL: @{{.*}}TMS4 = internal global %"class.hlsl::[[TEXTURE]].0" poison
+// SPIRV: @{{.*}}TMS4 = internal global %"class.hlsl::[[TEXTURE]].0" poison
+TEXTURE<float4, 4> TMS4;
+
+void main() {}
diff --git a/clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl
new file mode 100644
index 0000000000000..7e3b5b981648c
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -emit-pch -o %t %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -include-pch %t -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -DTEXTURE=Texture2DMS -DKIND=3
+
+// The Texture2DMS<T, N> sample count is part of the resource handle type, so it
+// has to survive serialization: a deserialized handle still lowers to a
+// dx.MSTexture with sample count 4.
+
+#ifndef HEADER
+#define HEADER
+
+TEXTURE<float4, 4> TMS4;
+
+#else
+
+// CHECK: %"class.hlsl::[[TEXTURE]]" = type { target("dx.MSTexture", <4 x float>, 0, 4, 0, [[KIND]]) }
+
+[numthreads(1, 1, 1)]
+void main() {
+ float4 V = TMS4.Load((LOCATION_TYPE)0, 0);
+ (void)V;
+}
+
+#endif
diff --git a/clang/test/CodeGenHLSL/resources/Textures-Subscript.hlsl b/clang/test/CodeGenHLSL/resources/Textures-Subscript.hlsl
index 1d765ee9cd0a1..686df4195f68e 100644
--- a/clang/test/CodeGenHLSL/resources/Textures-Subscript.hlsl
+++ b/clang/test/CodeGenHLSL/resources/Textures-Subscript.hlsl
@@ -1,7 +1,18 @@
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2D -DCOORD_TYPE=uint2 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2D -DCOORD_DIM=2 --check-prefixes=CHECK,DXIL -DDXIL_TY=2 -DRW=0
-// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2D -DCOORD_TYPE=uint2 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2D -DCOORD_DIM=2 --check-prefixes=CHECK,SPIRV -DARRAYED=0 -DSAMPLED=1 -DIMG_FMT=0
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2DArray -DCOORD_TYPE=uint3 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2DArray -DCOORD_DIM=3 --check-prefixes=CHECK,DXIL -DDXIL_TY=7 -DRW=0
-// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2DArray -DCOORD_TYPE=uint3 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2DArray -DCOORD_DIM=3 --check-prefixes=CHECK,SPIRV -DARRAYED=1 -DSAMPLED=1 -DIMG_FMT=0
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2D -DCOORD_TYPE=uint2 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2D -DCOORD_DIM=2 --check-prefixes=CHECK,DXIL -DROV_OR_COUNT=0 -DDXIL_HANDLE=dx.Texture -DDXIL_TY=2 -DRW=0
+// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2D -DCOORD_TYPE=uint2 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2D -DCOORD_DIM=2 --check-prefixes=CHECK,SPIRV -DARRAYED=0 -DMS=0 -DSAMPLED=1 -DIMG_FMT=0
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2DArray -DCOORD_TYPE=uint3 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2DArray -DCOORD_DIM=3 --check-prefixes=CHECK,DXIL -DROV_OR_COUNT=0 -DDXIL_HANDLE=dx.Texture -DDXIL_TY=7 -DRW=0
+// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2DArray -DCOORD_TYPE=uint3 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2DArray -DCOORD_DIM=3 --check-prefixes=CHECK,SPIRV -DARRAYED=1 -DMS=0 -DSAMPLED=1 -DIMG_FMT=0
+
+// Texture2DMS reuses the same operator[] codegen; only the resource handle type
+// differs (dx.MSTexture / a multisampled spirv.Image). It reads sample 0.
+//
+// ROV_OR_COUNT is the overloaded second int operand of the DXIL handle type:
+// IsROV for dx.Texture, the sample count for dx.MSTexture. It is 0 for every
+// texture below (none are ROVs, and Texture2DMS<T> defaults to a runtime
+// sample count), but a ROV or an explicit Texture2DMS<T, N> would need its own
+// value here.
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2DMS -DCOORD_TYPE=uint2 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2DMS -DCOORD_DIM=2 --check-prefixes=CHECK,DXIL -DROV_OR_COUNT=0 -DDXIL_HANDLE=dx.MSTexture -DDXIL_TY=3 -DRW=0
+// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -emit-llvm -disable-llvm-passes -finclude-default-header -Wno-sign-conversion -DTEXTURE=Texture2DMS -DCOORD_TYPE=uint2 -o - %s | llvm-cxxfilt | FileCheck %s -DTEXTURE=Texture2DMS -DCOORD_DIM=2 --check-prefixes=CHECK,SPIRV -DARRAYED=0 -DMS=1 -DSAMPLED=1 -DIMG_FMT=0
TEXTURE<float4> Tex : register(t0);
TEXTURE<float> Tex2 : register(t1);
@@ -21,56 +32,56 @@ void main(COORD_TYPE DTid : SV_DispatchThreadID) {
// CHECK: %[[VAL3:.*]] = alloca <3 x i32>
// CHECK: store <[[COORD_DIM]] x i32> %[[DTID]], ptr %[[DTID_ADDR]]
// CHECK: %[[DTID_VAL:.*]] = load <[[COORD_DIM]] x i32>, ptr %[[DTID_ADDR]]
-// CHECK: %[[CALL1:.*]] = call noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float vector[4]>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @Tex, <[[COORD_DIM]] x i32> noundef %[[DTID_VAL]])
+// CHECK: %[[CALL1:.*]] = call noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float vector[4]{{(, [0-9]+)?}}>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @Tex, <[[COORD_DIM]] x i32> noundef %[[DTID_VAL]])
// CHECK: %[[LOAD_VAL:.*]] = load <4 x float>, ptr{{.*}} %[[CALL1]]
// CHECK: store <4 x float> %[[LOAD_VAL]], ptr %[[VAL]]
// CHECK: %[[DTID_VAL2:.*]] = load <[[COORD_DIM]] x i32>, ptr %[[DTID_ADDR]]
-// CHECK: %[[CALL2:.*]] = call noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @Tex2, <[[COORD_DIM]] x i32> noundef %[[DTID_VAL2]])
+// CHECK: %[[CALL2:.*]] = call noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float{{(, [0-9]+)?}}>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @Tex2, <[[COORD_DIM]] x i32> noundef %[[DTID_VAL2]])
// CHECK: %[[LOAD_VAL2:.*]] = load float, ptr{{.*}} %[[CALL2]]
// CHECK: store float %[[LOAD_VAL2]], ptr %[[VAL2]]
// CHECK: %[[DTID_VAL3:.*]] = load <[[COORD_DIM]] x i32>, ptr %[[DTID_ADDR]]
-// CHECK: %[[CALL3:.*]] = call noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<int vector[3]>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @Tex3, <[[COORD_DIM]] x i32> noundef %[[DTID_VAL3]])
+// CHECK: %[[CALL3:.*]] = call noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<int vector[3]{{(, [0-9]+)?}}>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @Tex3, <[[COORD_DIM]] x i32> noundef %[[DTID_VAL3]])
// CHECK: %[[LOAD_VAL3:.*]] = load <3 x i32>, ptr{{.*}} %[[CALL3]]
// CHECK: store <3 x i32> %[[LOAD_VAL3]], ptr %[[VAL3]]
-// CHECK: define linkonce_odr hidden noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float vector[4]>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %[[THIS:.*]], <[[COORD_DIM]] x i32> noundef %[[INDEX:.*]])
+// CHECK: define linkonce_odr hidden noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float vector[4]{{(, [0-9]+)?}}>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %[[THIS:.*]], <[[COORD_DIM]] x i32> noundef %[[INDEX:.*]])
// CHECK: %[[THIS_ADDR:.*]] = alloca ptr
// CHECK: %[[INDEX_ADDR:.*]] = alloca <[[COORD_DIM]] x i32>
// CHECK: store ptr %[[THIS]], ptr %[[THIS_ADDR]]
// CHECK: store <[[COORD_DIM]] x i32> %[[INDEX]], ptr %[[INDEX_ADDR]]
// CHECK: %[[THIS1:.*]] = load ptr, ptr %[[THIS_ADDR]]
// CHECK: %[[HANDLE_PTR:.*]] = getelementptr {{.*}} %"class.hlsl::[[TEXTURE]]", ptr %[[THIS1]], i32 0, i32 0
-// DXIL: %[[HANDLE:.*]] = load target("dx.Texture", <4 x float>, [[RW]], 0, 0, [[DXIL_TY]]), ptr %[[HANDLE_PTR]]
-// SPIRV: %[[HANDLE:.*]] = load target("spirv.Image", float, 1, 2, [[ARRAYED]], 0, [[SAMPLED]], [[IMG_FMT]]), ptr %[[HANDLE_PTR]]
+// DXIL: %[[HANDLE:.*]] = load target("[[DXIL_HANDLE]]", <4 x float>, [[RW]], [[ROV_OR_COUNT]], 0, [[DXIL_TY]]), ptr %[[HANDLE_PTR]]
+// SPIRV: %[[HANDLE:.*]] = load target("spirv.Image", float, 1, 2, [[ARRAYED]], [[MS]], [[SAMPLED]], [[IMG_FMT]]), ptr %[[HANDLE_PTR]]
// CHECK: %[[INDEX_VAL:.*]] = load <[[COORD_DIM]] x i32>, ptr %[[INDEX_ADDR]]
-// DXIL: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.{{.*}}(target("dx.Texture", <4 x float>, [[RW]], 0, 0, [[DXIL_TY]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
-// SPIRV: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], 0, [[SAMPLED]], [[IMG_FMT]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
+// DXIL: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.{{.*}}(target("[[DXIL_HANDLE]]", <4 x float>, [[RW]], [[ROV_OR_COUNT]], 0, [[DXIL_TY]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
+// SPIRV: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], [[MS]], [[SAMPLED]], [[IMG_FMT]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
// CHECK: ret ptr {{.*}}%[[PTR]]
-// CHECK: define linkonce_odr hidden noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %[[THIS:.*]], <[[COORD_DIM]] x i32> noundef %[[INDEX:.*]])
+// CHECK: define linkonce_odr hidden noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<float{{(, [0-9]+)?}}>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %[[THIS:.*]], <[[COORD_DIM]] x i32> noundef %[[INDEX:.*]])
// CHECK: %[[THIS_ADDR:.*]] = alloca ptr
// CHECK: %[[INDEX_ADDR:.*]] = alloca <[[COORD_DIM]] x i32>
// CHECK: store ptr %[[THIS]], ptr %[[THIS_ADDR]]
// CHECK: store <[[COORD_DIM]] x i32> %[[INDEX]], ptr %[[INDEX_ADDR]]
// CHECK: %[[THIS1:.*]] = load ptr, ptr %[[THIS_ADDR]]
// CHECK: %[[HANDLE_PTR:.*]] = getelementptr {{.*}} %"class.hlsl::[[TEXTURE]].0", ptr %[[THIS1]], i32 0, i32 0
-// DXIL: %[[HANDLE:.*]] = load target("dx.Texture", float, [[RW]], 0, 0, [[DXIL_TY]]), ptr %[[HANDLE_PTR]]
-// SPIRV: %[[HANDLE:.*]] = load target("spirv.Image", float, 1, 2, [[ARRAYED]], 0, [[SAMPLED]], [[IMG_FMT]]), ptr %[[HANDLE_PTR]]
+// DXIL: %[[HANDLE:.*]] = load target("[[DXIL_HANDLE]]", float, [[RW]], [[ROV_OR_COUNT]], 0, [[DXIL_TY]]), ptr %[[HANDLE_PTR]]
+// SPIRV: %[[HANDLE:.*]] = load target("spirv.Image", float, 1, 2, [[ARRAYED]], [[MS]], [[SAMPLED]], [[IMG_FMT]]), ptr %[[HANDLE_PTR]]
// CHECK: %[[INDEX_VAL:.*]] = load <[[COORD_DIM]] x i32>, ptr %[[INDEX_ADDR]]
-// DXIL: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.{{.*}}(target("dx.Texture", float, [[RW]], 0, 0, [[DXIL_TY]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
-// SPIRV: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], 0, [[SAMPLED]], [[IMG_FMT]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
+// DXIL: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.{{.*}}(target("[[DXIL_HANDLE]]", float, [[RW]], [[ROV_OR_COUNT]], 0, [[DXIL_TY]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
+// SPIRV: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.{{.*}}(target("spirv.Image", float, 1, 2, [[ARRAYED]], [[MS]], [[SAMPLED]], [[IMG_FMT]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
// CHECK: ret ptr {{.*}}%[[PTR]]
-// CHECK: define linkonce_odr hidden noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<int vector[3]>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %[[THIS:.*]], <[[COORD_DIM]] x i32> noundef %[[INDEX:.*]])
+// CHECK: define linkonce_odr hidden noundef {{.*}}ptr{{.*}} @hlsl::[[TEXTURE]]<int vector[3]{{(, [0-9]+)?}}>::operator[](unsigned int vector[[[COORD_DIM]]]) const(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %[[THIS:.*]], <[[COORD_DIM]] x i32> noundef %[[INDEX:.*]])
// CHECK: %[[THIS_ADDR:.*]] = alloca ptr
// CHECK: %[[INDEX_ADDR:.*]] = alloca <[[COORD_DIM]] x i32>
// CHECK: store ptr %[[THIS]], ptr %[[THIS_ADDR]]
// CHECK: store <[[COORD_DIM]] x i32> %[[INDEX]], ptr %[[INDEX_ADDR]]
// CHECK: %[[THIS1:.*]] = load ptr, ptr %[[THIS_ADDR]]
// CHECK: %[[HANDLE_PTR:.*]] = getelementptr {{.*}} %"class.hlsl::[[TEXTURE]].1", ptr %[[THIS1]], i32 0, i32 0
-// DXIL: %[[HANDLE:.*]] = load target("dx.Texture", <3 x i32>, [[RW]], 0, 1, [[DXIL_TY]]), ptr %[[HANDLE_PTR]]
-// SPIRV: %[[HANDLE:.*]] = load target("spirv.SignedImage", i32, 1, 2, [[ARRAYED]], 0, [[SAMPLED]], [[IMG_FMT]]), ptr %[[HANDLE_PTR]]
+// DXIL: %[[HANDLE:.*]] = load target("[[DXIL_HANDLE]]", <3 x i32>, [[RW]], [[ROV_OR_COUNT]], 1, [[DXIL_TY]]), ptr %[[HANDLE_PTR]]
+// SPIRV: %[[HANDLE:.*]] = load target("spirv.SignedImage", i32, 1, 2, [[ARRAYED]], [[MS]], [[SAMPLED]], [[IMG_FMT]]), ptr %[[HANDLE_PTR]]
// CHECK: %[[INDEX_VAL:.*]] = load <[[COORD_DIM]] x i32>, ptr %[[INDEX_ADDR]]
-// DXIL: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.{{.*}}(target("dx.Texture", <3 x i32>, [[RW]], 0, 1, [[DXIL_TY]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
-// SPIRV: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.{{.*}}(target("spirv.SignedImage", i32, 1, 2, [[ARRAYED]], 0, [[SAMPLED]], [[IMG_FMT]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
+// DXIL: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.{{.*}}(target("[[DXIL_HANDLE]]", <3 x i32>, [[RW]], [[ROV_OR_COUNT]], 1, [[DXIL_TY]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
+// SPIRV: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.{{.*}}(target("spirv.SignedImage", i32, 1, 2, [[ARRAYED]], [[MS]], [[SAMPLED]], [[IMG_FMT]]) %[[HANDLE]], <[[COORD_DIM]] x i32> %[[INDEX_VAL]])
// CHECK: ret ptr {{.*}}%[[PTR]]
diff --git a/clang/test/SemaHLSL/Resources/MultiSampledTextures-Sema.hlsl b/clang/test/SemaHLSL/Resources/MultiSampledTextures-Sema.hlsl
new file mode 100644
index 0000000000000..40236507d85e2
--- /dev/null
+++ b/clang/test/SemaHLSL/Resources/MultiSampledTextures-Sema.hlsl
@@ -0,0 +1,55 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -fsyntax-only -verify -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -DINDEX_TYPE=uint2 %s
+
+// A multisampled texture supports the sample-indexed Load and operator[], but
+// does not support Sample/SampleBias/SampleGrad/SampleLevel/SampleCmp, Gather,
+// CalculateLevelOfDetail, or mips.
+
+TEXTURE<float4> T;
+SamplerState S;
+
+// The sample_count template parameter is a non-type 'int' parameter, so it must
+// be an integer constant expression. There is currently no HLSL-specific
+// validation of the value itself (0, negative, non-power-of-2, and out-of-range
+// counts are all accepted); the value is forwarded to the generated resource
+// type as the multisampled sample count.
+TEXTURE<float4, 4> TExplicit; // explicit valid count
+static const int SC = 8;
+TEXTURE<float4, SC> TConst; // constant-expression count
+
+int NonConst;
+TEXTURE<float4, NonConst> TBad; // expected-error {{non-type template argument is not a constant expression}}
+// expected-note at -1 {{read of non-const variable 'NonConst' is not allowed in a constant expression}}
+// expected-note at -3 {{declared here}}
+TEXTURE<float4, 1.5> TFloat; // expected-error {{conversion from 'float' to 'int' is not allowed in a converted constant expression}}
+
+// The element type has no default argument: a multisampled texture must be
+// declared with an explicit element type, matching DXC. Both the bare
+// template-name (shorthand) form and an empty template argument list are
+// errors. (Non-multisampled textures such as Texture2D do allow these forms.)
+TEXTURE TBare; // expected-error-re {{use of class template 'Texture2DMS{{(Array)?}}' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source}}
+TEXTURE<> TEmpty; // expected-error-re {{too few template arguments for class template 'Texture2DMS{{(Array)?}}'}}
+// expected-note@*:* {{template declaration from hidden source}}
+
+void valid() {
+ // Sample-indexed Load and its offset overload.
+ float4 a = T.Load((LOCATION_TYPE)0, 0);
+ float4 b = T.Load((LOCATION_TYPE)0, 0, int2(1, 1));
+ // operator[] reads sample 0.
+ float4 c = T[(INDEX_TYPE)0];
+}
+
+void unsupported() {
+ T.Sample(S, float2(0, 0)); // expected-error-re {{no member named 'Sample' in 'hlsl::Texture2DMS{{(Array)?}}<vector<float, 4>>'}}
+ T.SampleLevel(S, float2(0, 0), 0); // expected-error-re {{no member named 'SampleLevel' in 'hlsl::Texture2DMS{{(Array)?}}<vector<float, 4>>'}}
+ T.Gather(S, float2(0, 0)); // expected-error-re {{no member named 'Gather' in 'hlsl::Texture2DMS{{(Array)?}}<vector<float, 4>>'}}
+ T.CalculateLevelOfDetail(S, float2(0, 0)); // expected-error-re {{no member named 'CalculateLevelOfDetail' in 'hlsl::Texture2DMS{{(Array)?}}<vector<float, 4>>'}}
+ T.mips[0][(LOCATION_TYPE)0]; // expected-error-re {{no member named 'mips' in 'hlsl::Texture2DMS{{(Array)?}}<vector<float, 4>>'}}
+}
+
+void bad_load() {
+ // Load on a multisampled texture requires a sample index.
+ T.Load((LOCATION_TYPE)0); // expected-error {{no matching member function for call to 'Load'}}
+ // expected-note@*:* {{candidate function not viable: requires 2 arguments, but 1 was provided}}
+ // expected-note@*:* {{candidate function not viable: requires 3 arguments, but 1 was provided}}
+}
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index 4dd86270f0d01..81f390cc4fd8e 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -134,6 +134,12 @@ def int_dx_resource_load_level
llvm_any_ty],
[IntrReadMem]>;
+def int_dx_resource_load_ms
+ : DefaultAttrsIntrinsic<[llvm_any_ty],
+ [llvm_any_ty, llvm_any_ty, llvm_i32_ty,
+ llvm_any_ty],
+ [IntrReadMem]>;
+
def int_dx_resource_calculate_lod
: DefaultAttrsIntrinsic<[llvm_float_ty],
[llvm_any_ty, llvm_any_ty, llvm_any_ty],
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index d948ef78b9584..448f9664fc545 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -290,6 +290,12 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
llvm_any_ty],
[IntrReadMem]>;
+ def int_spv_resource_load_ms
+ : DefaultAttrsIntrinsic<[llvm_any_ty],
+ [llvm_any_ty, llvm_any_ty, llvm_i32_ty,
+ llvm_any_ty],
+ [IntrReadMem]>;
+
def int_spv_resource_samplecmp
: DefaultAttrsIntrinsic<[llvm_any_ty],
[llvm_any_ty, llvm_any_ty, llvm_any_ty,
>From d8f6aeb2f5955cacf075a6bf10a41144a9fa26f8 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Tue, 28 Jul 2026 14:56:25 -0700
Subject: [PATCH 2/6] Remove unecessary -pch test
---
.../resources/MultiSampledTextures-pch.hlsl | 23 -------------------
1 file changed, 23 deletions(-)
delete mode 100644 clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl
diff --git a/clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl b/clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl
deleted file mode 100644
index 7e3b5b981648c..0000000000000
--- a/clang/test/CodeGenHLSL/resources/MultiSampledTextures-pch.hlsl
+++ /dev/null
@@ -1,23 +0,0 @@
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -emit-pch -o %t %s
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -DTEXTURE=Texture2DMS -DLOCATION_TYPE=int2 -include-pch %t -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -DTEXTURE=Texture2DMS -DKIND=3
-
-// The Texture2DMS<T, N> sample count is part of the resource handle type, so it
-// has to survive serialization: a deserialized handle still lowers to a
-// dx.MSTexture with sample count 4.
-
-#ifndef HEADER
-#define HEADER
-
-TEXTURE<float4, 4> TMS4;
-
-#else
-
-// CHECK: %"class.hlsl::[[TEXTURE]]" = type { target("dx.MSTexture", <4 x float>, 0, 4, 0, [[KIND]]) }
-
-[numthreads(1, 1, 1)]
-void main() {
- float4 V = TMS4.Load((LOCATION_TYPE)0, 0);
- (void)V;
-}
-
-#endif
>From 6fc08d07032b46ff69c23db81235aa1d2dbf9240 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Tue, 28 Jul 2026 14:57:44 -0700
Subject: [PATCH 3/6] Add sema test for the sample count template parameter
---
.../SampleCount-template-arg-errors.hlsl | 74 +++++++++++++++++++
1 file changed, 74 insertions(+)
create mode 100644 clang/test/SemaHLSL/Resources/SampleCount-template-arg-errors.hlsl
diff --git a/clang/test/SemaHLSL/Resources/SampleCount-template-arg-errors.hlsl b/clang/test/SemaHLSL/Resources/SampleCount-template-arg-errors.hlsl
new file mode 100644
index 0000000000000..96c5f1ba45529
--- /dev/null
+++ b/clang/test/SemaHLSL/Resources/SampleCount-template-arg-errors.hlsl
@@ -0,0 +1,74 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -fsyntax-only -verify %s
+
+// The `sample_count` non-type template parameter only exists on multisampled
+// texture types. Every other resource type takes just an `element_type`, so
+// providing a second template argument must be diagnosed as too many template
+// arguments.
+
+struct S {
+ float4 F;
+};
+
+// Multisampled textures accept the sample count.
+Texture2DMS<float4> MSDefaultCount;
+Texture2DMS<float4, 4> MSExplicitCount;
+
+// ...but not more than that.
+Texture2DMS<float4, 4, 8> MSTooMany;
+// expected-error at -1 {{too many template arguments for class template 'Texture2DMS'}}
+// expected-note@*:* {{template declaration from hidden source}}
+
+// Typed buffers.
+Buffer<float4, 4> B;
+// expected-error at -1 {{too many template arguments for class template 'Buffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+RWBuffer<float4, 4> RWB;
+// expected-error at -1 {{too many template arguments for class template 'RWBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+RasterizerOrderedBuffer<float4, 4> ROB;
+// expected-error at -1 {{too many template arguments for class template 'RasterizerOrderedBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+
+// Structured buffers.
+StructuredBuffer<S, 4> SB;
+// expected-error at -1 {{too many template arguments for class template 'StructuredBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+RWStructuredBuffer<S, 4> RWSB;
+// expected-error at -1 {{too many template arguments for class template 'RWStructuredBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+AppendStructuredBuffer<S, 4> ASB;
+// expected-error at -1 {{too many template arguments for class template 'AppendStructuredBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+ConsumeStructuredBuffer<S, 4> CSB;
+// expected-error at -1 {{too many template arguments for class template 'ConsumeStructuredBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+RasterizerOrderedStructuredBuffer<S, 4> ROSB;
+// expected-error at -1 {{too many template arguments for class template 'RasterizerOrderedStructuredBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+
+// Constant buffers.
+ConstantBuffer<S, 4> CB;
+// expected-error at -1 {{too many template arguments for class template 'ConstantBuffer'}}
+// expected-note@*:* {{template declaration from hidden source}}
+
+// Non-multisampled textures.
+Texture2D<float4, 4> T2D;
+// expected-error at -1 {{too many template arguments for class template 'Texture2D'}}
+// expected-note@*:* {{template declaration from hidden source}}
+RWTexture2D<float4, 4> RWT2D;
+// expected-error at -1 {{too many template arguments for class template 'RWTexture2D'}}
+// expected-note@*:* {{template declaration from hidden source}}
+Texture2DArray<float4, 4> T2DA;
+// expected-error at -1 {{too many template arguments for class template 'Texture2DArray'}}
+// expected-note@*:* {{template declaration from hidden source}}
+RWTexture2DArray<float4, 4> RWT2DA;
+// expected-error at -1 {{too many template arguments for class template 'RWTexture2DArray'}}
+// expected-note@*:* {{template declaration from hidden source}}
+
+// Resource types that are not templates at all cannot take a sample count
+// either.
+ByteAddressBuffer<4> BAB; // expected-error {{expected unqualified-id}}
+RWByteAddressBuffer<4> RWBAB; // expected-error {{expected unqualified-id}}
+RasterizerOrderedByteAddressBuffer<4> ROBAB; // expected-error {{expected unqualified-id}}
+SamplerState<4> Samp; // expected-error {{expected unqualified-id}}
+SamplerComparisonState<4> SampCmp; // expected-error {{expected unqualified-id}}
>From 4703ca6a4b8a652f9310e74b310d431ef0c8b6d2 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Tue, 18 Aug 2026 11:47:42 -0700
Subject: [PATCH 4/6] Remove IsMultiSampled field and fix
ASTStructuralEquivalence logic with SampleCountExpr
---
clang/include/clang/AST/TypeBase.h | 23 ++++++-------
clang/include/clang/AST/TypeProperties.td | 5 +--
clang/lib/AST/ASTStructuralEquivalence.cpp | 15 +++++++--
clang/lib/AST/ItaniumMangle.cpp | 2 +-
clang/lib/AST/Type.cpp | 1 -
clang/lib/AST/TypePrinter.cpp | 2 +-
clang/lib/CodeGen/Targets/DirectX.cpp | 4 +--
clang/lib/CodeGen/Targets/SPIR.cpp | 2 +-
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 33 +++++++------------
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 5 ++-
clang/lib/Sema/HLSLExternalSemaSource.cpp | 11 ++++++-
clang/lib/Sema/SemaHLSL.cpp | 13 +++++---
12 files changed, 63 insertions(+), 53 deletions(-)
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 791b014779a05..aae5e9f82635d 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -6882,37 +6882,37 @@ class HLSLAttributedResourceType : public Type, public llvm::FoldingSetNode {
LLVM_PREFERRED_TYPE(bool)
uint8_t IsArray : 1;
- LLVM_PREFERRED_TYPE(bool)
- uint8_t IsMultiSampled : 1;
-
- /// The N in Texture2DMS<T, N>; null for every other resource.
+ /// The N in Texture2DMS<T, N>; null for every resource that is not
+ /// multisampled. A multisampled resource always carries a sample count,
+ /// defaulting to 0, which means the count comes from the bound resource
+ /// at runtime rather than denoting zero samples.
Expr *SampleCountExpr;
Attributes(llvm::dxil::ResourceClass ResourceClass,
llvm::dxil::ResourceDimension ResourceDimension,
bool IsROV = false, bool RawBuffer = false,
bool IsCounter = false, bool IsArray = false,
- bool IsMultiSampled = false, Expr *SampleCountExpr = nullptr)
+ Expr *SampleCountExpr = nullptr)
: ResourceClass(ResourceClass), ResourceDimension(ResourceDimension),
IsROV(IsROV), RawBuffer(RawBuffer), IsCounter(IsCounter),
- IsArray(IsArray), IsMultiSampled(IsMultiSampled),
- SampleCountExpr(SampleCountExpr) {}
+ IsArray(IsArray), SampleCountExpr(SampleCountExpr) {}
Attributes(llvm::dxil::ResourceClass ResourceClass)
: Attributes(ResourceClass, llvm::dxil::ResourceDimension::Unknown) {}
Attributes()
: Attributes(llvm::dxil::ResourceClass::UAV,
- llvm::dxil::ResourceDimension::Unknown, false, false,
- false, false, false) {}
+ llvm::dxil::ResourceDimension::Unknown) {}
+
+ bool isMultiSampled() const { return SampleCountExpr != nullptr; }
friend bool operator==(const Attributes &LHS, const Attributes &RHS) {
return std::tie(LHS.ResourceClass, LHS.ResourceDimension, LHS.IsROV,
LHS.RawBuffer, LHS.IsCounter, LHS.IsArray,
- LHS.IsMultiSampled, LHS.SampleCountExpr) ==
+ LHS.SampleCountExpr) ==
std::tie(RHS.ResourceClass, RHS.ResourceDimension, RHS.IsROV,
RHS.RawBuffer, RHS.IsCounter, RHS.IsArray,
- RHS.IsMultiSampled, RHS.SampleCountExpr);
+ RHS.SampleCountExpr);
}
friend bool operator!=(const Attributes &LHS, const Attributes &RHS) {
return !(LHS == RHS);
@@ -6938,6 +6938,7 @@ class HLSLAttributedResourceType : public Type, public llvm::FoldingSetNode {
QualType getContainedType() const { return ContainedType; }
bool hasContainedType() const { return !ContainedType.isNull(); }
Expr *getSampleCountExpr() const { return Attrs.SampleCountExpr; }
+ bool isMultiSampled() const { return Attrs.isMultiSampled(); }
const Attributes &getAttrs() const { return Attrs; }
bool isRaw() const { return Attrs.RawBuffer; }
bool isStructured() const { return !ContainedType->isChar8Type(); }
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 55fafa3acfa4a..054e7a7357970 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -693,9 +693,6 @@ let Class = HLSLAttributedResourceType in {
def : Property<"isArray", Bool> {
let Read = [{ node->getAttrs().IsArray }];
}
- def : Property<"isMultiSampled", Bool> {
- let Read = [{ node->getAttrs().IsMultiSampled }];
- }
def : Property<"sampleCountExpr", Optional<ExprRef>> {
let Read = [{ makeOptionalFromPointer(node->getSampleCountExpr()) }];
}
@@ -703,7 +700,7 @@ let Class = HLSLAttributedResourceType in {
HLSLAttributedResourceType::Attributes attrs(
static_cast<llvm::dxil::ResourceClass>(resClass),
static_cast<llvm::dxil::ResourceDimension>(resDimension), isROV,
- rawBuffer, isCounter, isArray, isMultiSampled,
+ rawBuffer, isCounter, isArray,
makePointerFromOptional(sampleCountExpr));
return ctx.getHLSLAttributedResourceType(wrappedTy, containedTy, attrs);
}]>;
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index d8bbfbe5dac72..0ebb3c0d55974 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1238,9 +1238,18 @@ bool ASTStructuralEquivalence::isEquivalent(
Context, cast<HLSLAttributedResourceType>(T1)->getContainedType(),
cast<HLSLAttributedResourceType>(T2)->getContainedType()))
return false;
- if (cast<HLSLAttributedResourceType>(T1)->getAttrs() !=
- cast<HLSLAttributedResourceType>(T2)->getAttrs())
- return false;
+ {
+ const auto *Res1 = cast<HLSLAttributedResourceType>(T1);
+ const auto *Res2 = cast<HLSLAttributedResourceType>(T2);
+ if (!IsStructurallyEquivalent(Context, Res1->getSampleCountExpr(),
+ Res2->getSampleCountExpr()))
+ return false;
+ HLSLAttributedResourceType::Attributes Attrs1 = Res1->getAttrs();
+ HLSLAttributedResourceType::Attributes Attrs2 = Res2->getAttrs();
+ Attrs1.SampleCountExpr = Attrs2.SampleCountExpr = nullptr;
+ if (Attrs1 != Attrs2)
+ return false;
+ }
break;
case Type::HLSLInlineSpirv:
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index f8e6b898be250..40fff45cbe781 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -4691,7 +4691,7 @@ void CXXNameMangler::mangleType(const HLSLAttributedResourceType *T) {
Str += "_Counter";
if (Attrs.IsArray)
Str += "_Array";
- if (Attrs.IsMultiSampled)
+ if (Attrs.isMultiSampled())
Str += "_MS";
if (T->hasContainedType())
Str += "_CT";
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index db3c94e13b7bc..6c33e25f5d513 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5982,7 +5982,6 @@ void HLSLAttributedResourceType::Profile(llvm::FoldingSetNodeID &ID,
ID.AddBoolean(Attrs.RawBuffer);
ID.AddBoolean(Attrs.IsCounter);
ID.AddBoolean(Attrs.IsArray);
- ID.AddBoolean(Attrs.IsMultiSampled);
ID.AddBoolean(Attrs.SampleCountExpr != nullptr);
if (Attrs.SampleCountExpr)
Attrs.SampleCountExpr->Profile(ID, Ctx, /*Canonical=*/true);
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 60964d2859790..a7ceec29f7158 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -2213,7 +2213,7 @@ void TypePrinter::printHLSLAttributedResourceAfter(
OS << " [[hlsl::is_counter]]";
if (Attrs.IsArray)
OS << " [[hlsl::is_array]]";
- if (Attrs.IsMultiSampled)
+ if (Attrs.isMultiSampled())
OS << " [[hlsl::is_ms]]";
QualType ContainedTy = T->getContainedType();
diff --git a/clang/lib/CodeGen/Targets/DirectX.cpp b/clang/lib/CodeGen/Targets/DirectX.cpp
index 29998f6dfb16f..6240a64b6a539 100644
--- a/clang/lib/CodeGen/Targets/DirectX.cpp
+++ b/clang/lib/CodeGen/Targets/DirectX.cpp
@@ -71,7 +71,7 @@ llvm::Type *DirectXTargetCodeGenInfo::getHLSLType(
ResAttrs.ResourceDimension != llvm::dxil::ResourceDimension::Unknown;
assert((!IsRawBuffer || !IsTexture) && "A resource cannot be both a raw "
"buffer and a texture.");
- bool IsMultiSampledTexture = IsTexture && ResAttrs.IsMultiSampled;
+ bool IsMultiSampledTexture = IsTexture && ResAttrs.isMultiSampled();
llvm::StringRef TypeName = "dx.TypedBuffer";
if (IsRawBuffer)
TypeName = "dx.RawBuffer";
@@ -112,7 +112,7 @@ llvm::Type *DirectXTargetCodeGenInfo::getHLSLType(
RK = llvm::dxil::ResourceKind::Texture1D;
break;
case llvm::dxil::ResourceDimension::Dim2D:
- if (ResAttrs.IsMultiSampled)
+ if (ResAttrs.isMultiSampled())
RK = ResAttrs.IsArray ? llvm::dxil::ResourceKind::Texture2DMSArray
: llvm::dxil::ResourceKind::Texture2DMS;
else
diff --git a/clang/lib/CodeGen/Targets/SPIR.cpp b/clang/lib/CodeGen/Targets/SPIR.cpp
index dba7d4fa73923..152d6214526df 100644
--- a/clang/lib/CodeGen/Targets/SPIR.cpp
+++ b/clang/lib/CodeGen/Targets/SPIR.cpp
@@ -936,7 +936,7 @@ llvm::Type *CommonSPIRTargetCodeGenInfo::getSPIRVImageTypeFromHLSLResource(
IntParams[2] = static_cast<unsigned>(attributes.IsArray);
// MS
- IntParams[3] = static_cast<unsigned>(attributes.IsMultiSampled);
+ IntParams[3] = static_cast<unsigned>(attributes.isMultiSampled());
// Sampled
IntParams[4] =
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 7b778bd362d58..70ece482ae2e7 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -1065,8 +1065,7 @@ BuiltinTypeDeclBuilder::addBufferHandles(ResourceClass RC, bool IsROV,
AccessSpecifier Access) {
QualType ElementTy = getHandleElementType();
addHandleMember(RC, ResourceDimension::Unknown, IsROV, RawBuffer,
- /*IsArray=*/false, ElementTy, /*IsMultiSampled=*/false,
- Access);
+ /*IsArray=*/false, ElementTy, Access);
if (HasCounter)
addCounterHandleMember(RC, IsROV, RawBuffer, ElementTy, Access);
return *this;
@@ -1074,9 +1073,10 @@ BuiltinTypeDeclBuilder::addBufferHandles(ResourceClass RC, bool IsROV,
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addTextureHandle(
ResourceClass RC, bool IsROV, bool IsArray, ResourceDimension RD,
- bool IsMultiSampled, AccessSpecifier Access) {
- addHandleMember(RC, RD, IsROV, /*RawBuffer=*/false, IsArray,
- getHandleElementType(), IsMultiSampled, Access);
+ Expr *SampleCountExpr, AccessSpecifier Access) {
+ addResourceMember("__handle", RC, RD, IsROV, /*RawBuffer=*/false,
+ /*IsCounter=*/false, IsArray, getHandleElementType(),
+ SampleCountExpr, Access);
return *this;
}
@@ -1138,11 +1138,10 @@ CXXRecordDecl *BuiltinTypeDeclBuilder::addPrivateNestedRecord(StringRef Name) {
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addHandleMember(
ResourceClass RC, ResourceDimension RD, bool IsROV, bool RawBuffer,
- bool IsArray, QualType ElementTy, bool IsMultiSampled,
- AccessSpecifier Access) {
+ bool IsArray, QualType ElementTy, AccessSpecifier Access) {
return addResourceMember("__handle", RC, RD, IsROV, RawBuffer,
/*IsCounter=*/false, IsArray, ElementTy,
- IsMultiSampled, Access);
+ /*SampleCountExpr=*/nullptr, Access);
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addCounterHandleMember(
@@ -1151,13 +1150,13 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addCounterHandleMember(
return addResourceMember("__counter_handle", RC, ResourceDimension::Unknown,
IsROV, RawBuffer, /*IsCounter=*/true,
/*IsArray=*/false, ElementTy,
- /*IsMultiSampled=*/false, Access);
+ /*SampleCountExpr=*/nullptr, Access);
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addResourceMember(
StringRef MemberName, ResourceClass RC, ResourceDimension RD, bool IsROV,
bool RawBuffer, bool IsCounter, bool IsArray, QualType ElementTy,
- bool IsMultiSampled, AccessSpecifier Access) {
+ Expr *SampleCountExpr, AccessSpecifier Access) {
assert(!Record->isCompleteDefinition() && "record is already complete");
ASTContext &Ctx = SemaRef.getASTContext();
@@ -1183,16 +1182,8 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addResourceMember(
Attrs.push_back(HLSLIsCounterAttr::CreateImplicit(Ctx));
if (IsArray)
Attrs.push_back(HLSLIsArrayAttr::CreateImplicit(Ctx));
- Expr *SampleCountExpr = nullptr;
- if (IsMultiSampled) {
+ if (SampleCountExpr)
Attrs.push_back(HLSLIsMultiSampledAttr::CreateImplicit(Ctx));
- ClassTemplateDecl *CTD = Record->getDescribedClassTemplate();
- assert(CTD && "multisampled texture must be a class template");
- auto *NTTP = cast<NonTypeTemplateParmDecl>(
- CTD->getTemplateParameters()->getParam(1));
- SampleCountExpr = SemaRef.BuildDeclRefExpr(NTTP, NTTP->getType(),
- VK_PRValue, SourceLocation());
- }
if (CreateHLSLAttributedResourceType(SemaRef, Ctx.HLSLResourceTy, Attrs,
AttributedResTy, /*LocInfo=*/nullptr,
@@ -1506,7 +1497,7 @@ CXXRecordDecl *BuiltinTypeDeclBuilder::addMipsSliceType(ResourceDimension Dim,
.addHandleMember(getResourceAttrs().ResourceClass, Dim,
getResourceAttrs().IsROV, /*RawBuffer=*/false,
getResourceAttrs().IsArray, ReturnType,
- /*IsMultiSampled=*/false, AccessSpecifier::AS_public)
+ AccessSpecifier::AS_public)
.addMemberVariable("__level", IntTy, {}, AccessSpecifier::AS_public)
.addDefaultHandleConstructor(AccessSpecifier::AS_protected)
.addCopyConstructor(AccessSpecifier::AS_protected)
@@ -1550,7 +1541,7 @@ CXXRecordDecl *BuiltinTypeDeclBuilder::addMipsType(ResourceDimension Dim,
.addHandleMember(getResourceAttrs().ResourceClass, Dim,
getResourceAttrs().IsROV, /*RawBuffer=*/false,
getResourceAttrs().IsArray, ReturnType,
- /*IsMultiSampled=*/false, AccessSpecifier::AS_public)
+ AccessSpecifier::AS_public)
.addDefaultHandleConstructor(AccessSpecifier::AS_protected)
.addCopyConstructor(AccessSpecifier::AS_protected)
.addCopyAssignmentOperator(AccessSpecifier::AS_protected);
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index 2b527765ce527..a7521cd8480fd 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -87,7 +87,7 @@ class BuiltinTypeDeclBuilder {
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &
addTextureHandle(ResourceClass RC, bool IsROV, bool IsArray,
- ResourceDimension RD, bool IsMultiSampled = false,
+ ResourceDimension RD, Expr *SampleCountExpr = nullptr,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &addSamplerHandle();
BuiltinTypeDeclBuilder &addConstantBufferConversionToType();
@@ -162,7 +162,7 @@ class BuiltinTypeDeclBuilder {
addResourceMember(StringRef MemberName, ResourceClass RC,
ResourceDimension RD, bool IsROV, bool RawBuffer,
bool IsCounter, bool IsArray, QualType ElementTy,
- bool IsMultiSampled = false,
+ Expr *SampleCountExpr = nullptr,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &addFriend(CXXRecordDecl *Friend);
CXXRecordDecl *addPrivateNestedRecord(StringRef Name);
@@ -171,7 +171,6 @@ class BuiltinTypeDeclBuilder {
BuiltinTypeDeclBuilder &
addHandleMember(ResourceClass RC, ResourceDimension RD, bool IsROV,
bool RawBuffer, bool IsArray, QualType ElementTy,
- bool IsMultiSampled = false,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &
addCounterHandleMember(ResourceClass RC, bool IsROV, bool RawBuffer,
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index c666134b83304..18822ce7f92b1 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -302,9 +302,18 @@ static BuiltinTypeDeclBuilder setupRWTextureType(CXXRecordDecl *Decl, Sema &S,
static BuiltinTypeDeclBuilder setupMSTextureType(CXXRecordDecl *Decl, Sema &S,
bool IsArray,
ResourceDimension Dim) {
+ ClassTemplateDecl *CTD = Decl->getDescribedClassTemplate();
+ assert(CTD && "multisampled texture must be a class template");
+
+ // Parameter 1 is the N in Texture2DMS<T, N>.
+ auto *NTTP =
+ cast<NonTypeTemplateParmDecl>(CTD->getTemplateParameters()->getParam(1));
+ Expr *SampleCountExpr =
+ S.BuildDeclRefExpr(NTTP, NTTP->getType(), VK_PRValue, SourceLocation());
+
return BuiltinTypeDeclBuilder(S, Decl)
.addTextureHandle(ResourceClass::SRV, /*IsROV=*/false, IsArray, Dim,
- /*IsMultiSampled=*/true)
+ SampleCountExpr)
.addTextureLoadMSMethods(Dim, IsArray)
.addArraySubscriptOperators(Dim, IsArray)
// TODO: Add MS-specific GetDimensions (with a NumberOfSamples output);
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index b3126324fae6c..1615ca3129e51 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2163,12 +2163,17 @@ bool clang::CreateHLSLAttributedResourceType(
ResAttrs.IsArray = true;
break;
case attr::HLSLIsMultiSampled:
- if (ResAttrs.IsMultiSampled) {
+ if (ResAttrs.SampleCountExpr) {
S.Diag(A->getLocation(), diag::warn_duplicate_attribute_exact) << A;
return false;
}
- ResAttrs.IsMultiSampled = true;
- ResAttrs.SampleCountExpr = SampleCountExpr;
+ // A bare [[hlsl::is_ms]] carries no count, so default it to 0, the same
+ // value Texture2DMS<T> gets from its template parameter.
+ ResAttrs.SampleCountExpr =
+ SampleCountExpr
+ ? SampleCountExpr
+ : IntegerLiteral::Create(S.Context, llvm::APInt(32, 0),
+ S.Context.IntTy, A->getLocation());
break;
case attr::HLSLIsCounter:
if (ResAttrs.IsCounter) {
@@ -3959,7 +3964,7 @@ static bool CheckLoadMSBuiltin(Sema &S, CallExpr *TheCall) {
// Check the multisampled texture handle.
if (CheckResourceHandle(&S, TheCall, 0,
[](const HLSLAttributedResourceType *ResType) {
- return !ResType->getAttrs().IsMultiSampled;
+ return !ResType->isMultiSampled();
}))
return true;
>From 789aa94228c8593474a2c1028a9d24a3baa62838 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Tue, 18 Aug 2026 18:20:15 -0700
Subject: [PATCH 5/6] Make texture load methods return *this like the buffer
loads
---
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 70ece482ae2e7..4a9b7b1b56a64 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -1612,12 +1612,14 @@ BuiltinTypeDeclBuilder::addTextureLoadMethods(ResourceDimension Dim,
.finalize();
// T Load(int3 location, int2 offset)
- return BuiltinTypeMethodBuilder(*this, "Load", ReturnType)
+ BuiltinTypeMethodBuilder(*this, "Load", ReturnType)
.addParam("Location", LocationTy)
.addParam("Offset", OffsetTy)
.callBuiltin("__builtin_hlsl_resource_load_level", ReturnType, PH::Handle,
PH::_0, PH::_1)
.finalize();
+
+ return *this;
}
BuiltinTypeDeclBuilder &
@@ -1644,13 +1646,15 @@ BuiltinTypeDeclBuilder::addTextureLoadMSMethods(ResourceDimension Dim,
.finalize();
// T Load(int2 location, int sampleIndex, int2 offset)
- return BuiltinTypeMethodBuilder(*this, "Load", ReturnType)
+ BuiltinTypeMethodBuilder(*this, "Load", ReturnType)
.addParam("Location", LocationTy)
.addParam("SampleIndex", IntTy)
.addParam("Offset", OffsetTy)
.callBuiltin("__builtin_hlsl_resource_load_ms", ReturnType, PH::Handle,
PH::_0, PH::_1, PH::_2)
.finalize();
+
+ return *this;
}
BuiltinTypeDeclBuilder &
>From b15300b8fbb9a9dcc4f8e196941e2366f9f5d276 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Tue, 18 Aug 2026 18:29:18 -0700
Subject: [PATCH 6/6] Link appropriate issue for TODO on MS-specific
GetDimensions
---
clang/lib/Sema/HLSLExternalSemaSource.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index 18822ce7f92b1..d8a697041ae7d 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -318,6 +318,7 @@ static BuiltinTypeDeclBuilder setupMSTextureType(CXXRecordDecl *Decl, Sema &S,
.addArraySubscriptOperators(Dim, IsArray)
// TODO: Add MS-specific GetDimensions (with a NumberOfSamples output);
// the generic addGetDimensionsMethods is mip-based and unsuitable here.
+ // https://github.com/llvm/wg-hlsl/issues/347
.addDefaultHandleConstructor()
.addCopyConstructor()
.addCopyAssignmentOperator()
More information about the llvm-commits
mailing list