Pārlūkot izejas kodu

[spirv] Support globals structs of Textures/Samplers (#2994)

* [spirv] Cleanup DeclResultIdMapper::createExternVar.

* [spirv] Support resource-only global struct.

* [spirv] Provide an error if mix of resource and non-resource is used.

* getNumBindingsUsedByResourceType is used instead of arraySize.

* Add comments and cleanup usage of HLSLGroupSharedAttr.

* Detect when flattening pass should be run in spirv-opt.

* Address code review comments
Ehsan 5 gadi atpakaļ
vecāks
revīzija
d72c5fd876

+ 25 - 0
tools/clang/include/clang/SPIRV/AstTypeProbe.h

@@ -291,6 +291,31 @@ QualType getComponentVectorType(const ASTContext &, QualType matrixType);
 QualType getHLSLMatrixType(ASTContext &, Sema &, ClassTemplateDecl *,
                            QualType elemType, int rows, int columns);
 
+/// Returns true if the given type is a structure or array of structures for
+/// which flattening all of its members recursively results in resources ONLY.
+bool isResourceOnlyStructure(QualType type);
+
+/// Returns true if the given type is a structure or array of structures for
+/// which flattening all of its members recursively results in at least one
+/// resoure variable.
+bool isStructureContainingResources(QualType type);
+
+/// Returns true if the given type is a structure or array of structures for
+/// which flattening all of its members recursively results in at least one
+/// non-resoure variable.
+bool isStructureContainingNonResources(QualType type);
+
+/// Returns true if the given type is a structure or array of structures for
+/// which flattening all of its members recursively results in a mix of resource
+/// variables and non-resource variables.
+bool isStructureContainingMixOfResourcesAndNonResources(QualType type);
+
+/// Returns true if the given type is a structure or array of structures for
+/// which flattening all of its members recursively results in at least one kind
+/// of buffer: cbuffer, tbuffer, (RW)ByteAddressBuffer, or
+/// (RW|Append|Consume)StructuredBuffer.
+bool isStructureContainingAnyKindOfBuffer(QualType type);
+
 } // namespace spirv
 } // namespace clang
 

+ 81 - 0
tools/clang/lib/SPIRV/AstTypeProbe.cpp

@@ -1215,5 +1215,86 @@ QualType getHLSLMatrixType(ASTContext &astContext, Sema &S,
       TemplateName(templateDecl), templateArgumentList, canonType);
 }
 
+bool isResourceOnlyStructure(QualType type) {
+  // Remove arrayness if needed.
+  while (type->isArrayType())
+    type = type->getAsArrayTypeUnsafe()->getElementType();
+
+  if (const auto *structType = type->getAs<RecordType>()) {
+    for (const auto *field : structType->getDecl()->fields()) {
+      // isResourceType does remove arrayness for the field if needed.
+      if (!isResourceType(field) &&
+          !isResourceOnlyStructure(field->getType())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  return false;
+}
+
+bool isStructureContainingResources(QualType type) {
+  // Remove arrayness if needed.
+  while (type->isArrayType())
+    type = type->getAsArrayTypeUnsafe()->getElementType();
+
+  if (const auto *structType = type->getAs<RecordType>()) {
+    for (const auto *field : structType->getDecl()->fields()) {
+      // isStructureContainingResources and isResourceType functions both remove
+      // arrayness for the field if needed.
+      if (isStructureContainingResources(field->getType()) ||
+          isResourceType(field)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool isStructureContainingNonResources(QualType type) {
+  // Remove arrayness if needed.
+  while (type->isArrayType())
+    type = type->getAsArrayTypeUnsafe()->getElementType();
+
+  if (const auto *structType = type->getAs<RecordType>()) {
+    for (const auto *field : structType->getDecl()->fields()) {
+      // isStructureContainingNonResources and isResourceType functions both
+      // remove arrayness for the field if needed.
+      if (isStructureContainingNonResources(field->getType()) ||
+          !isResourceType(field)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool isStructureContainingMixOfResourcesAndNonResources(QualType type) {
+  return isStructureContainingResources(type) &&
+         isStructureContainingNonResources(type);
+}
+
+bool isStructureContainingAnyKindOfBuffer(QualType type) {
+  // Remove arrayness if needed.
+  while (type->isArrayType())
+    type = type->getAsArrayTypeUnsafe()->getElementType();
+
+  if (const auto *structType = type->getAs<RecordType>()) {
+    for (const auto *field : structType->getDecl()->fields()) {
+      auto fieldType = field->getType();
+      // Remove arrayness if needed.
+      while (fieldType->isArrayType())
+        fieldType = fieldType->getAsArrayTypeUnsafe()->getElementType();
+      if (isAKindOfStructuredOrByteBuffer(fieldType) ||
+          isConstantTextureBuffer(field) ||
+          isStructureContainingAnyKindOfBuffer(fieldType)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 } // namespace spirv
 } // namespace clang

+ 129 - 47
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -47,6 +47,52 @@ hlsl::ConstantPacking *getPackOffset(const clang::NamedDecl *decl) {
   return nullptr;
 }
 
+/// Returns the number of binding numbers that are used up by the given type.
+/// An array of size N consumes N*M binding numbers where M is the number of
+/// binding numbers used by each array element.
+/// The number of binding numbers used by a structure is the sum of binding
+/// numbers used by its members.
+uint32_t getNumBindingsUsedByResourceType(QualType type) {
+  // For custom-generated types that have SpirvType but no QualType.
+  if (type.isNull())
+    return 1;
+
+  // For every array dimension, the number of bindings needed should be
+  // multiplied by the array size. For example: an array of two Textures should
+  // use 2 binding slots.
+  uint32_t arrayFactor = 1;
+  while (auto constArrayType = dyn_cast<ConstantArrayType>(type)) {
+    arrayFactor *=
+        static_cast<uint32_t>(constArrayType->getSize().getZExtValue());
+    type = constArrayType->getElementType();
+  }
+
+  // Once we remove the arrayness, we expect the given type to be either a
+  // resource OR a structure that only contains resources.
+  assert(hlsl::IsHLSLResourceType(type) || isResourceOnlyStructure(type));
+
+  // In the case of a resource, each resource takes 1 binding slot, so in total
+  // it consumes: 1 * arrayFactor.
+  if (hlsl::IsHLSLResourceType(type))
+    return arrayFactor;
+
+  // In the case of a struct of resources, we need to sum up the number of
+  // bindings for the struct members. So in total it consumes:
+  //  sum(bindings of struct members) * arrayFactor.
+  if (isResourceOnlyStructure(type)) {
+    uint32_t sumOfMemberBindings = 0;
+    const auto *structDecl = type->getAs<RecordType>()->getDecl();
+    assert(structDecl);
+    for (const auto *field : structDecl->fields())
+      sumOfMemberBindings += getNumBindingsUsedByResourceType(field->getType());
+
+    return sumOfMemberBindings * arrayFactor;
+  }
+
+  llvm_unreachable(
+      "getNumBindingsUsedByResourceType was called with unknown resource type");
+}
+
 QualType getUintTypeWithSourceComponents(const ASTContext &astContext,
                                          QualType sourceType) {
   if (isScalarType(sourceType)) {
@@ -189,9 +235,14 @@ bool shouldSkipInStructLayout(const Decl *decl) {
     if (isa<HLSLBufferDecl>(decl))
       return true;
 
+    // 'groupshared' variables should not be placed in $Globals cbuffer.
+    if (decl->hasAttr<HLSLGroupSharedAttr>())
+      return true;
+
     // Other resource types
     if (const auto *valueDecl = dyn_cast<ValueDecl>(decl))
-      if (isResourceType(valueDecl))
+      if (isResourceType(valueDecl) ||
+          isResourceOnlyStructure((valueDecl->getType())))
         return true;
   }
 
@@ -333,6 +384,30 @@ inline uint32_t getNumBaseClasses(QualType type) {
     return cxxDecl->getNumBases();
   return 0;
 }
+
+/// Returns the appropriate storage class for an extern variable of the given
+/// type.
+spv::StorageClass getStorageClassForExternVar(QualType type,
+                                              bool hasGroupsharedAttr) {
+  // For CS groupshared variables
+  if (hasGroupsharedAttr)
+    return spv::StorageClass::Workgroup;
+
+  if (isAKindOfStructuredOrByteBuffer(type))
+    return spv::StorageClass::Uniform;
+
+  return spv::StorageClass::UniformConstant;
+}
+
+/// Returns the appropriate layout rule for an extern variable of the given
+/// type.
+SpirvLayoutRule getLayoutRuleForExternVar(QualType type,
+                                          const SpirvCodeGenOptions &opts) {
+  if (isAKindOfStructuredOrByteBuffer(type))
+    return opts.sBufferLayoutRule;
+  return SpirvLayoutRule::Void;
+}
+
 } // anonymous namespace
 
 std::string StageVar::getSemanticStr() const {
@@ -669,38 +744,28 @@ DeclResultIdMapper::createFileVar(const VarDecl *var,
 }
 
 SpirvVariable *DeclResultIdMapper::createExternVar(const VarDecl *var) {
-  auto storageClass = spv::StorageClass::UniformConstant;
-  auto rule = SpirvLayoutRule::Void;
-  bool isACRWSBuffer = false; // Whether is {Append|Consume|RW}StructuredBuffer
-
-  if (var->getAttr<HLSLGroupSharedAttr>()) {
-    // For CS groupshared variables
-    storageClass = spv::StorageClass::Workgroup;
-  } else if (isResourceType(var)) {
-    // See through the possible outer arrays
-    QualType resourceType = var->getType();
-    while (resourceType->isArrayType()) {
-      resourceType = resourceType->getAsArrayTypeUnsafe()->getElementType();
-    }
-
-    const llvm::StringRef typeName =
-        resourceType->getAs<RecordType>()->getDecl()->getName();
-
-    // These types are all translated into OpTypeStruct with BufferBlock
-    // decoration. They should follow standard storage buffer layout,
-    // which GLSL std430 rules statisfies.
-    if (typeName == "StructuredBuffer" || typeName == "ByteAddressBuffer" ||
-        typeName == "RWByteAddressBuffer") {
-      storageClass = spv::StorageClass::Uniform;
-      rule = spirvOptions.sBufferLayoutRule;
-    } else if (typeName == "RWStructuredBuffer" ||
-               typeName == "AppendStructuredBuffer" ||
-               typeName == "ConsumeStructuredBuffer") {
-      storageClass = spv::StorageClass::Uniform;
-      rule = spirvOptions.sBufferLayoutRule;
-      isACRWSBuffer = true;
+  const auto type = var->getType();
+  const bool isGroupShared = var->hasAttr<HLSLGroupSharedAttr>();
+  const bool isACRWSBuffer = isRWAppendConsumeSBuffer(type);
+  const auto storageClass = getStorageClassForExternVar(type, isGroupShared);
+  const auto rule = getLayoutRuleForExternVar(type, spirvOptions);
+  const auto loc = var->getLocation();
+
+  if (!isGroupShared && !isResourceType(var) &&
+      !isResourceOnlyStructure(type)) {
+
+    // We currently cannot support global structures that contain both resources
+    // and non-resources. That would require significant work in manipulating
+    // structure field decls, manipulating QualTypes, as well as inserting
+    // non-resources into the Globals cbuffer which changes offset decorations
+    // for it.
+    if (isStructureContainingMixOfResourcesAndNonResources(type)) {
+      emitError("global structures containing both resources and non-resources "
+                "are not supported",
+                loc);
+      return nullptr;
     }
-  } else {
+
     // This is a stand-alone externally-visiable non-resource-type variable.
     // They should be grouped into the $Globals cbuffer. We create that cbuffer
     // and record all variables inside it upon seeing the first such variable.
@@ -711,8 +776,31 @@ SpirvVariable *DeclResultIdMapper::createExternVar(const VarDecl *var) {
     return varInstr ? cast<SpirvVariable>(varInstr) : nullptr;
   }
 
-  const auto type = var->getType();
-  const auto loc = var->getLocation();
+  if (isResourceOnlyStructure(type)) {
+    // We currently do not support global structures that contain buffers.
+    // Supporting global structures that contain buffers has two complications:
+    //
+    // 1- Buffers have the Uniform storage class, whereas Textures/Samplers have
+    // UniformConstant storage class. As a result, if a struct contains both
+    // textures and buffers, it is not clear what storage class should be used
+    // for the struct. Also legalization cannot deduce the proper storage class
+    // for struct members based on the structure's storage class.
+    //
+    // 2- Any kind of structured buffer has associated counters. The current DXC
+    // code is not written in a way to place associated counters inside a
+    // structure. Changing this behavior is non-trivial. There's also
+    // significant work to be done both in DXC (to properly generate binding
+    // numbers for the resource and its associated counters at correct offsets)
+    // and in spirv-opt (to flatten such strcutures and modify the binding
+    // numbers accordingly).
+    if (isStructureContainingAnyKindOfBuffer(type)) {
+      emitError("global structures containing buffers are not supported", loc);
+      return nullptr;
+    }
+
+    needsFlatteningCompositeResources = true;
+  }
+
   SpirvVariable *varInstr = spvBuilder.addModuleVar(
       type, storageClass, var->hasAttr<HLSLPreciseAttr>(), var->getName(),
       llvm::None, loc);
@@ -781,16 +869,11 @@ SpirvVariable *DeclResultIdMapper::createStructOrStructArrayVarOfExplicitLayout(
   const bool forShaderRecordNV =
       usageKind == ContextUsageKind::ShaderRecordBufferNV;
 
-  const llvm::SmallVector<const Decl *, 4> &declGroup =
-      collectDeclsInDeclContext(decl);
+  const auto &declGroup = collectDeclsInDeclContext(decl);
 
   // Collect the type and name for each field
   llvm::SmallVector<HybridStructType::FieldInfo, 4> fields;
   for (const auto *subDecl : declGroup) {
-    // 'groupshared' variables should not be placed in $Globals cbuffer.
-    if (forGlobals && subDecl->hasAttr<HLSLGroupSharedAttr>())
-      continue;
-
     // The field can only be FieldDecl (for normal structs) or VarDecl (for
     // HLSLBufferDecls).
     assert(isa<VarDecl>(subDecl) || isa<FieldDecl>(subDecl));
@@ -1012,9 +1095,6 @@ void DeclResultIdMapper::createGlobalsCBuffer(const VarDecl *var) {
 
   uint32_t index = 0;
   for (const auto *decl : collectDeclsInDeclContext(context)) {
-    // 'groupshared' variables should not be placed in $Globals cbuffer.
-    if (decl->hasAttr<HLSLGroupSharedAttr>())
-      continue;
     if (const auto *varDecl = dyn_cast<VarDecl>(decl)) {
       if (!spirvOptions.noWarnIgnoredFeatures) {
         if (const auto *init = varDecl->getInit())
@@ -1642,8 +1722,9 @@ bool DeclResultIdMapper::decorateResourceBindings() {
     // resources (e.g. array of textures), DX uses one binding number per array
     // element. We can match this behavior via a command line option.
     uint32_t numBindingsToUse = 1;
-    if (spirvOptions.flattenResourceArrays)
-      numBindingsToUse = var.getArraySize();
+    if (spirvOptions.flattenResourceArrays || needsFlatteningCompositeResources)
+      numBindingsToUse = getNumBindingsUsedByResourceType(
+          var.getSpirvInstr()->getAstResultType());
 
     for (uint32_t i = 0; i < numBindingsToUse; ++i) {
       bool success = bindingSet.useBinding(bindingNo + i, setNo);
@@ -1723,8 +1804,9 @@ bool DeclResultIdMapper::decorateResourceBindings() {
     // resources (e.g. array of textures), DX uses one binding number per array
     // element. We can match this behavior via a command line option.
     uint32_t numBindingsToUse = 1;
-    if (spirvOptions.flattenResourceArrays)
-      numBindingsToUse = var.getArraySize();
+    if (spirvOptions.flattenResourceArrays || needsFlatteningCompositeResources)
+      numBindingsToUse = getNumBindingsUsedByResourceType(
+          var.getSpirvInstr()->getAstResultType());
 
     if (var.isCounter()) {
       if (!var.getCounterBinding()) {

+ 18 - 17
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -116,19 +116,7 @@ public:
               const VKCounterBindingAttr *cb, bool counter = false,
               bool globalsBuffer = false)
       : variable(var), srcLoc(loc), reg(r), binding(b), counterBinding(cb),
-        isCounterVar(counter), isGlobalsCBuffer(globalsBuffer), arraySize(1) {
-    if (decl) {
-      if (const ValueDecl *valueDecl = dyn_cast<ValueDecl>(decl)) {
-        const QualType type = valueDecl->getType();
-        if (!type.isNull() && type->isConstantArrayType()) {
-          if (auto constArrayType = dyn_cast<ConstantArrayType>(type)) {
-            arraySize =
-                static_cast<uint32_t>(constArrayType->getSize().getZExtValue());
-          }
-        }
-      }
-    }
-  }
+        isCounterVar(counter), isGlobalsCBuffer(globalsBuffer) {}
 
   SpirvVariable *getSpirvInstr() const { return variable; }
   SourceLocation getSourceLocation() const { return srcLoc; }
@@ -139,7 +127,6 @@ public:
   const VKCounterBindingAttr *getCounterBinding() const {
     return counterBinding;
   }
-  uint32_t getArraySize() const { return arraySize; }
 
 private:
   SpirvVariable *variable;                    ///< The variable
@@ -149,7 +136,6 @@ private:
   const VKCounterBindingAttr *counterBinding; ///< Vulkan counter binding
   bool isCounterVar;                          ///< Couter variable or not
   bool isGlobalsCBuffer;                      ///< $Globals cbuffer or not
-  uint32_t arraySize;                         ///< Size if resource is an array
 };
 
 /// A (instruction-pointer, is-alias-or-not) pair for counter variables
@@ -522,8 +508,16 @@ public:
   /// module under construction.
   bool decorateResourceBindings();
 
+  /// \brief Returns whether the SPIR-V module requires SPIR-V legalization
+  /// passes run to make it legal.
   bool requiresLegalization() const { return needsLegalization; }
- 
+
+  /// \brief Returns whether the SPIR-V module requires an optimization pass to
+  /// flatten array/structure of resources.
+  bool requiresFlatteningCompositeResources() const {
+    return needsFlatteningCompositeResources;
+  }
+
   /// \brief Returns the given decl's HLSL semantic information.
   static SemanticInfo getStageVarSemantic(const NamedDecl *decl);
 
@@ -791,6 +785,13 @@ private:
   /// Note: legalization specific code
   bool needsLegalization;
 
+  /// Whether the translated SPIR-V binary needs flattening of composite
+  /// resources.
+  ///
+  /// If the source HLSL contains global structure of resources, we need to run
+  /// an additional SPIR-V optimization pass to flatten such structures.
+  bool needsFlatteningCompositeResources;
+
 public:
   /// The gl_PerVertex structs for both input and output
   GlPerVertex glPerVertex;
@@ -821,7 +822,7 @@ DeclResultIdMapper::DeclResultIdMapper(ASTContext &context,
     : spvBuilder(spirvBuilder), theEmitter(emitter), spirvOptions(options),
       astContext(context), spvContext(spirvContext),
       diags(context.getDiagnostics()), entryFunction(nullptr),
-      needsLegalization(false),
+      needsLegalization(false), needsFlatteningCompositeResources(false),
       glPerVertex(context, spirvContext, spirvBuilder) {}
 
 bool DeclResultIdMapper::decorateStageIOLocations() {

+ 93 - 88
tools/clang/lib/SPIRV/SpirvEmitter.cpp

@@ -143,84 +143,6 @@ bool isReferencingNonAliasStructuredOrByteBuffer(const Expr *expr) {
   return false;
 }
 
-bool spirvToolsLegalize(spv_target_env env, std::vector<uint32_t> *mod,
-                        std::string *messages) {
-  spvtools::Optimizer optimizer(env);
-
-  optimizer.SetMessageConsumer(
-      [messages](spv_message_level_t /*level*/, const char * /*source*/,
-                 const spv_position_t & /*position*/,
-                 const char *message) { *messages += message; });
-
-  spvtools::OptimizerOptions options;
-  options.set_run_validator(false);
-
-  optimizer.RegisterLegalizationPasses();
-
-  optimizer.RegisterPass(spvtools::CreateReplaceInvalidOpcodePass());
-
-  optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
-
-  return optimizer.Run(mod->data(), mod->size(), mod, options);
-}
-
-bool spirvToolsOptimize(spv_target_env env, std::vector<uint32_t> *mod,
-                        clang::spirv::SpirvCodeGenOptions &spirvOptions,
-                        std::string *messages) {
-  spvtools::Optimizer optimizer(env);
-
-  optimizer.SetMessageConsumer(
-      [messages](spv_message_level_t /*level*/, const char * /*source*/,
-                 const spv_position_t & /*position*/,
-                 const char *message) { *messages += message; });
-
-  spvtools::OptimizerOptions options;
-  options.set_run_validator(false);
-
-  if (spirvOptions.optConfig.empty()) {
-    optimizer.RegisterPerformancePasses();
-    if (spirvOptions.flattenResourceArrays)
-      optimizer.RegisterPass(spvtools::CreateDescriptorScalarReplacementPass());
-    optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
-  } else {
-    // Command line options use llvm::SmallVector and llvm::StringRef, whereas
-    // SPIR-V optimizer uses std::vector and std::string.
-    std::vector<std::string> stdFlags;
-    for (const auto &f : spirvOptions.optConfig)
-      stdFlags.push_back(f.str());
-    if (!optimizer.RegisterPassesFromFlags(stdFlags))
-      return false;
-  }
-
-  return optimizer.Run(mod->data(), mod->size(), mod, options);
-}
-
-bool spirvToolsValidate(spv_target_env env, const SpirvCodeGenOptions &opts,
-                        bool beforeHlslLegalization, std::vector<uint32_t> *mod,
-                        std::string *messages) {
-  spvtools::SpirvTools tools(env);
-
-  tools.SetMessageConsumer(
-      [messages](spv_message_level_t /*level*/, const char * /*source*/,
-                 const spv_position_t & /*position*/,
-                 const char *message) { *messages += message; });
-
-  spvtools::ValidatorOptions options;
-  options.SetBeforeHlslLegalization(beforeHlslLegalization);
-  // GL: strict block layout rules
-  // VK: relaxed block layout rules
-  // DX: Skip block layout rules
-  if (opts.useScalarLayout || opts.useDxLayout) {
-    options.SetScalarBlockLayout(true);
-  } else if (opts.useGlLayout) {
-    // spirv-val by default checks this.
-  } else {
-    options.SetRelaxBlockLayout(true);
-  }
-
-  return tools.Validate(mod->data(), mod->size(), options);
-}
-
 /// Translates atomic HLSL opcodes into the equivalent SPIR-V opcode.
 spv::Op translateAtomicHlslOpcodeToSpirvOpcode(hlsl::IntrinsicOp opcode) {
   using namespace hlsl;
@@ -653,14 +575,17 @@ void SpirvEmitter::HandleTranslationUnit(ASTContext &context) {
   std::vector<uint32_t> m = spvBuilder.takeModule();
 
   if (!spirvOptions.codeGenHighLevel) {
-    // In order to flatten resource arrays, we must also unroll loops. Therefore
-    // we should run legalization before optimization.
-    needsLegalization = needsLegalization || spirvOptions.flattenResourceArrays;
+    // In order to flatten composite resources, we must also unroll loops.
+    // Therefore we should run legalization before optimization.
+    needsLegalization = needsLegalization ||
+                        declIdMapper.requiresLegalization() ||
+                        spirvOptions.flattenResourceArrays ||
+                        declIdMapper.requiresFlatteningCompositeResources();
 
     // Run legalization passes
-    if (needsLegalization || declIdMapper.requiresLegalization()) {
+    if (needsLegalization) {
       std::string messages;
-      if (!spirvToolsLegalize(targetEnv, &m, &messages)) {
+      if (!spirvToolsLegalize(&m, &messages)) {
         emitFatalError("failed to legalize SPIR-V: %0", {}) << messages;
         emitNote("please file a bug report on "
                  "https://github.com/Microsoft/DirectXShaderCompiler/issues "
@@ -675,7 +600,7 @@ void SpirvEmitter::HandleTranslationUnit(ASTContext &context) {
     // Run optimization passes
     if (theCompilerInstance.getCodeGenOpts().OptimizationLevel > 0) {
       std::string messages;
-      if (!spirvToolsOptimize(targetEnv, &m, spirvOptions, &messages)) {
+      if (!spirvToolsOptimize(&m, &messages)) {
         emitFatalError("failed to optimize SPIR-V: %0", {}) << messages;
         emitNote("please file a bug report on "
                  "https://github.com/Microsoft/DirectXShaderCompiler/issues "
@@ -689,10 +614,7 @@ void SpirvEmitter::HandleTranslationUnit(ASTContext &context) {
   // Validate the generated SPIR-V code
   if (!spirvOptions.disableValidation) {
     std::string messages;
-    if (!spirvToolsValidate(targetEnv, spirvOptions,
-                            needsLegalization ||
-                                declIdMapper.requiresLegalization(),
-                            &m, &messages)) {
+    if (!spirvToolsValidate(&m, &messages)) {
       emitFatalError("generated SPIR-V is invalid: %0", {}) << messages;
       emitNote("please file a bug report on "
                "https://github.com/Microsoft/DirectXShaderCompiler/issues "
@@ -11677,5 +11599,88 @@ SpirvEmitter::processRayQueryIntrinsics(const CXXMemberCallExpr *expr,
   return retVal;
 }
 
+bool SpirvEmitter::spirvToolsValidate(std::vector<uint32_t> *mod,
+                                      std::string *messages) {
+  spvtools::SpirvTools tools(featureManager.getTargetEnv());
+
+  tools.SetMessageConsumer(
+      [messages](spv_message_level_t /*level*/, const char * /*source*/,
+                 const spv_position_t & /*position*/,
+                 const char *message) { *messages += message; });
+
+  spvtools::ValidatorOptions options;
+  options.SetBeforeHlslLegalization(needsLegalization ||
+                                    declIdMapper.requiresLegalization());
+  // GL: strict block layout rules
+  // VK: relaxed block layout rules
+  // DX: Skip block layout rules
+  if (spirvOptions.useScalarLayout || spirvOptions.useDxLayout) {
+    options.SetScalarBlockLayout(true);
+  } else if (spirvOptions.useGlLayout) {
+    // spirv-val by default checks this.
+  } else {
+    options.SetRelaxBlockLayout(true);
+  }
+
+  return tools.Validate(mod->data(), mod->size(), options);
+}
+
+bool SpirvEmitter::spirvToolsOptimize(std::vector<uint32_t> *mod,
+                                      std::string *messages) {
+  spvtools::Optimizer optimizer(featureManager.getTargetEnv());
+
+  optimizer.SetMessageConsumer(
+      [messages](spv_message_level_t /*level*/, const char * /*source*/,
+                 const spv_position_t & /*position*/,
+                 const char *message) { *messages += message; });
+
+  spvtools::OptimizerOptions options;
+  options.set_run_validator(false);
+
+  if (spirvOptions.optConfig.empty()) {
+    // Add performance passes.
+    optimizer.RegisterPerformancePasses();
+
+    // Add flattening of resources if needed.
+    if (spirvOptions.flattenResourceArrays ||
+        declIdMapper.requiresFlatteningCompositeResources()) {
+      optimizer.RegisterPass(spvtools::CreateDescriptorScalarReplacementPass());
+      // ADCE should be run after desc_sroa in order to remove potentially
+      // illegal types such as structures containing opaque types.
+      optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
+    }
+
+    // Add compact ID pass.
+    optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
+  } else {
+    // Command line options use llvm::SmallVector and llvm::StringRef, whereas
+    // SPIR-V optimizer uses std::vector and std::string.
+    std::vector<std::string> stdFlags;
+    for (const auto &f : spirvOptions.optConfig)
+      stdFlags.push_back(f.str());
+    if (!optimizer.RegisterPassesFromFlags(stdFlags))
+      return false;
+  }
+
+  return optimizer.Run(mod->data(), mod->size(), mod, options);
+}
+
+bool SpirvEmitter::spirvToolsLegalize(std::vector<uint32_t> *mod,
+                                      std::string *messages) {
+  spvtools::Optimizer optimizer(featureManager.getTargetEnv());
+  optimizer.SetMessageConsumer(
+      [messages](spv_message_level_t /*level*/, const char * /*source*/,
+                 const spv_position_t & /*position*/,
+                 const char *message) { *messages += message; });
+
+  spvtools::OptimizerOptions options;
+  options.set_run_validator(false);
+  optimizer.RegisterLegalizationPasses();
+  optimizer.RegisterPass(spvtools::CreateReplaceInvalidOpcodePass());
+  optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
+
+  return optimizer.Run(mod->data(), mod->size(), mod, options);
+}
+
 } // end namespace spirv
 } // end namespace clang

+ 18 - 0
tools/clang/lib/SPIRV/SpirvEmitter.h

@@ -991,6 +991,24 @@ private:
                               const clang::FunctionDecl *,
                               bool isEntryFunction);
 
+  /// \brief Helper function to run SPIRV-Tools optimizer's performance passes.
+  /// Runs the SPIRV-Tools optimizer on the given SPIR-V module |mod|, and
+  /// gets the info/warning/error messages via |messages|.
+  /// Returns true on success and false otherwise.
+  bool spirvToolsOptimize(std::vector<uint32_t> *mod, std::string *messages);
+
+  /// \brief Helper function to run SPIRV-Tools optimizer's legalization passes.
+  /// Runs the SPIRV-Tools legalization on the given SPIR-V module |mod|, and
+  /// gets the info/warning/error messages via |messages|.
+  /// Returns true on success and false otherwise.
+  bool spirvToolsLegalize(std::vector<uint32_t> *mod, std::string *messages);
+
+  /// \brief Helper function to run the SPIRV-Tools validator.
+  /// Runs the SPIRV-Tools validator on the given SPIR-V module |mod|, and
+  /// gets the info/warning/error messages via |messages|.
+  /// Returns true on success and false otherwise.
+  bool spirvToolsValidate(std::vector<uint32_t> *mod, std::string *messages);
+
 public:
   /// \brief Wrapper method to create a fatal error message and report it
   /// in the diagnostic engine associated with this consumer.

+ 17 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resource-mix.error.1.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays
+
+struct S {
+  Texture2D t[2];
+  SamplerState s[3];
+  float a;
+};
+
+float4 tex2D(S x, float2 v) { return x.t[0].Sample(x.s[0], v); }
+
+// CHECK: 12:3: error: global structures containing both resources and non-resources are not supported
+S globalS[2];
+
+float4 main() : SV_Target {
+  return tex2D(globalS[0], float2(0,0));
+}
+

+ 21 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resource-mix.error.2.hlsl

@@ -0,0 +1,21 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays
+
+struct T {
+  float2x3 a[4];
+};
+
+struct S {
+  Texture2D t[2];
+  SamplerState s[3];
+  T sub[2];
+};
+
+float4 tex2D(S x, float2 v) { return x.t[0].Sample(x.s[0], v); }
+
+// CHECK: 16:3: error: global structures containing both resources and non-resources are not supported
+S globalS[2];
+
+float4 main() : SV_Target {
+  return tex2D(globalS[0], float2(0,0));
+}
+

+ 41 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.1.hlsl

@@ -0,0 +1,41 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays
+
+// globalS.t should take binding #0.
+// globalS.s should take binding #1.
+//
+// CHECK: OpDecorate %globalS DescriptorSet 0
+// CHECK: OpDecorate %globalS Binding 0
+//
+// CHECK: OpDecorate %globalTexture DescriptorSet 0
+// CHECK: OpDecorate %globalTexture Binding 2
+//
+// CHECK: OpDecorate %globalSamplerState DescriptorSet 0
+// CHECK: OpDecorate %globalSamplerState Binding 3
+
+
+// CHECK:                          %S = OpTypeStruct %type_2d_image %type_sampler
+// CHECK:     %_ptr_UniformConstant_S = OpTypePointer UniformConstant %S
+// CHECK-NOT:          %type__Globals =
+
+struct S {
+  Texture2D t;
+  SamplerState s;
+};
+
+float4 tex2D(S x, float2 v) { return x.t.Sample(x.s, v); }
+
+// CHECK:      %globalS = OpVariable %_ptr_UniformConstant_S UniformConstant
+// CHECK-NOT: %_Globals = OpVariable
+S globalS;
+
+
+Texture2D globalTexture;
+SamplerState globalSamplerState;
+
+float4 main() : SV_Target {
+// CHECK: [[globalS:%\d+]] = OpLoad %S %globalS
+// CHECK:                    OpStore %param_var_x [[globalS]]
+// CHECK:                    OpFunctionCall %v4float %tex2D %param_var_x %param_var_v
+  return tex2D(globalS, float2(0,0));
+}
+

+ 54 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.2.hlsl

@@ -0,0 +1,54 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays
+
+// globalS[0][0].t should take binding #0.
+// globalS[0][0].s should take binding #1.
+// globalS[0][1].t should take binding #2.
+// globalS[0][1].s should take binding #3.
+// globalS[0][2].t should take binding #4.
+// globalS[0][2].s should take binding #5.
+// globalS[1][0].t should take binding #6.
+// globalS[1][0].s should take binding #7.
+// globalS[1][1].t should take binding #8.
+// globalS[1][1].s should take binding #9.
+// globalS[1][2].t should take binding #10.
+// globalS[1][2].s should take binding #11.
+//
+// CHECK: OpDecorate %globalS DescriptorSet 0
+// CHECK: OpDecorate %globalS Binding 0
+//
+// CHECK: OpDecorate %globalTexture DescriptorSet 0
+// CHECK: OpDecorate %globalTexture Binding 12
+//
+// CHECK: OpDecorate %globalSamplerState DescriptorSet 0
+// CHECK: OpDecorate %globalSamplerState Binding 13
+
+
+// CHECK:                                              %S = OpTypeStruct %type_2d_image %type_sampler
+// CHECL:                                  %_arr_S_uint_3 = OpTypeArray %S %uint_3
+// CHECL:                      %_arr__arr_S_uint_3_uint_2 = OpTypeArray %_arr_S_uint_3 %uint_2
+// CHECL: %_ptr_UniformConstant__arr__arr_S_uint_3_uint_2 = OpTypePointer UniformConstant %_arr__arr_S_uint_3_uint_2
+// CHECK-NOT:                              %type__Globals =
+
+struct S {
+  Texture2D t;
+  SamplerState s;
+};
+
+float4 tex2D(S x, float2 v) { return x.t.Sample(x.s, v); }
+
+// CHECK:      %globalS = OpVariable %_ptr_UniformConstant__arr__arr_S_uint_3_uint_2 UniformConstant
+// CHECK-NOT: %_Globals = OpVariable
+S globalS[2][3];
+
+
+Texture2D globalTexture;
+SamplerState globalSamplerState;
+
+float4 main() : SV_Target {
+// CHECK:  [[ptr:%\d+]] = OpAccessChain %_ptr_UniformConstant_S %globalS %int_0 %int_0
+// CHECK: [[elem:%\d+]] = OpLoad %S [[ptr]]
+// CHECK:                 OpStore %param_var_x [[elem]]
+// CHECK:                 OpFunctionCall %v4float %tex2D %param_var_x %param_var_v
+  return tex2D(globalS[0][0], float2(0,0));
+}
+

+ 44 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.3.hlsl

@@ -0,0 +1,44 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays
+
+// globalS.t should take binding #0.
+// globalS.s should take binding #1.
+// CHECK: OpDecorate %globalS DescriptorSet 0
+// CHECK: OpDecorate %globalS Binding 0
+//
+// gCounter (and hence $Globals cbuffer) appears after globalS, and before
+// globalTexture, therefore, it takes binding #2.
+// CHECK: OpDecorate %_Globals DescriptorSet 0
+// CHECK: OpDecorate %_Globals Binding 2
+//
+// CHECK: OpDecorate %globalTexture DescriptorSet 0
+// CHECK: OpDecorate %globalTexture Binding 3
+
+// CHECK:                          %S = OpTypeStruct %type_2d_image %type_sampler
+// CHECK:     %_ptr_UniformConstant_S = OpTypePointer UniformConstant %S
+
+// Struct S (globalS) and Texture2D (globalTexture) should NOT be placed in the $Globals cbuffer.
+// The $Globals cbuffer should only contain one float (gCounter).
+//
+// CHECK:              %type__Globals = OpTypeStruct %float
+// CHECK: %_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+
+struct S {
+  Texture2D t;
+  SamplerState s;
+};
+
+float4 tex2D(S x, float2 v) { return x.t.Sample(x.s, v); }
+
+// CHECK: %globalS = OpVariable %_ptr_UniformConstant_S UniformConstant
+S globalS;
+
+// %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+float gCounter;
+
+// %globalTexture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+Texture2D globalTexture;
+
+float4 main() : SV_Target {
+  return tex2D(globalS, float2(0,0));
+}
+

+ 49 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.4.hlsl

@@ -0,0 +1,49 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays
+
+// globalS[0].t[0] should take binding #0.
+// globalS[0].t[1] should take binding #1.
+// globalS[0].s[0] should take binding #2.
+// globalS[0].s[1] should take binding #3.
+// globalS[0].s[2] should take binding #4.
+// globalS[1].t[0] should take binding #5.
+// globalS[1].t[1] should take binding #6.
+// globalS[1].s[0] should take binding #7.
+// globalS[1].s[1] should take binding #8.
+// globalS[1].s[2] should take binding #9.
+//
+// CHECK: OpDecorate %globalS DescriptorSet 0
+// CHECK: OpDecorate %globalS Binding 0
+//
+// CHECK: OpDecorate %globalTexture DescriptorSet 0
+// CHECK: OpDecorate %globalTexture Binding 10
+//
+// CHECK: OpDecorate %globalSamplerState DescriptorSet 0
+// CHECK: OpDecorate %globalSamplerState Binding 11
+
+
+// CHECK:                                  %S = OpTypeStruct %_arr_type_2d_image_uint_2 %_arr_type_sampler_uint_3
+// CHECK:                      %_arr_S_uint_2 = OpTypeArray %S %uint_2
+// CHECK: %_ptr_UniformConstant__arr_S_uint_2 = OpTypePointer UniformConstant %_arr_S_uint_2
+// CHECK-NOT:                  %type__Globals =
+
+struct S {
+  Texture2D t[2];
+  SamplerState s[3];
+};
+
+float4 tex2D(S x, float2 v) { return x.t[0].Sample(x.s[0], v); }
+
+// CHECK:      %globalS = OpVariable %_ptr_UniformConstant__arr_S_uint_2 UniformConstant
+// CHECK-NOT: %_Globals = OpVariable
+S globalS[2];
+
+// %globalTexture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+Texture2D globalTexture;
+
+// CHECK: %globalSamplerState = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+SamplerState globalSamplerState;
+
+float4 main() : SV_Target {
+  return tex2D(globalS[0], float2(0,0));
+}
+

+ 17 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.contains-buffer-error.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays
+
+struct S {
+  Texture2D t[2];
+  SamplerState s[3];
+  RWStructuredBuffer<float> rw;
+};
+
+float4 tex2D(S x, float2 v) { return x.t[0].Sample(x.s[0], v); }
+
+// CHECK: 12:3: error: global structures containing buffers are not supported
+S globalS[2];
+
+float4 main() : SV_Target {
+  return tex2D(globalS[0], float2(0,0)) + globalS[1].rw[0];
+}
+

+ 188 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.optimized.hlsl

@@ -0,0 +1,188 @@
+// Run: %dxc -T ps_6_0 -E main -fspv-flatten-resource-arrays -O3
+
+// Check the names
+//
+// CHECK: OpName %secondGlobal_t_0_ "secondGlobal.t[0]"
+// CHECK: OpName %secondGlobal_t_1_ "secondGlobal.t[1]"
+// CHECK: OpName %firstGlobal_0__0__t_0_ "firstGlobal[0][0].t[0]"
+// CHECK: OpName %firstGlobal_0__0__t_1_ "firstGlobal[0][0].t[1]"
+// CHECK: OpName %firstGlobal_0__1__t_0_ "firstGlobal[0][1].t[0]"
+// CHECK: OpName %firstGlobal_0__1__t_1_ "firstGlobal[0][1].t[1]"
+// CHECK: OpName %firstGlobal_1__0__t_0_ "firstGlobal[1][0].t[0]"
+// CHECK: OpName %firstGlobal_1__0__t_1_ "firstGlobal[1][0].t[1]"
+// CHECK: OpName %firstGlobal_1__1__t_0_ "firstGlobal[1][1].t[0]"
+// CHECK: OpName %firstGlobal_1__1__t_1_ "firstGlobal[1][1].t[1]"
+// CHECK: OpName %secondGlobal_tt_0__s_1_ "secondGlobal.tt[0].s[1]"
+// CHECK: OpName %secondGlobal_tt_1__s_2_ "secondGlobal.tt[1].s[2]"
+// CHECK: OpName %firstGlobal_0__0__tt_0__s_1_ "firstGlobal[0][0].tt[0].s[1]"
+// CHECK: OpName %firstGlobal_0__0__tt_1__s_2_ "firstGlobal[0][0].tt[1].s[2]"
+// CHECK: OpName %firstGlobal_0__1__tt_0__s_1_ "firstGlobal[0][1].tt[0].s[1]"
+// CHECK: OpName %firstGlobal_0__1__tt_1__s_2_ "firstGlobal[0][1].tt[1].s[2]"
+// CHECK: OpName %firstGlobal_1__0__tt_0__s_1_ "firstGlobal[1][0].tt[0].s[1]"
+// CHECK: OpName %firstGlobal_1__0__tt_1__s_2_ "firstGlobal[1][0].tt[1].s[2]"
+// CHECK: OpName %firstGlobal_1__1__tt_0__s_1_ "firstGlobal[1][1].tt[0].s[1]"
+// CHECK: OpName %firstGlobal_1__1__tt_1__s_2_ "firstGlobal[1][1].tt[1].s[2]"
+
+// Check flattening of bindings
+// Explanation: Only the resources that are used will have a binding assignment
+// Unused resources will result in gaps in the bindings. This is consistent with
+// the behavior of FXC.
+//
+// In this example:
+//
+// firstGlobal[0][0].t[0]        binding: 0  (used)
+// firstGlobal[0][0].t[1]        binding: 1  (used)
+// firstGlobal[0][0].tt[0].s[0]  binding: 2
+// firstGlobal[0][0].tt[0].s[1]  binding: 3  (used)
+// firstGlobal[0][0].tt[0].s[2]  binding: 4
+// firstGlobal[0][0].tt[1].s[0]  binding: 5
+// firstGlobal[0][0].tt[1].s[1]  binding: 6
+// firstGlobal[0][0].tt[1].s[2]  binding: 7  (used)
+// firstGlobal[0][1].t[0]        binding: 8  (used)
+// firstGlobal[0][1].t[1]        binding: 9  (used)
+// firstGlobal[0][1].tt[0].s[0]  binding: 10
+// firstGlobal[0][1].tt[0].s[1]  binding: 11 (used)
+// firstGlobal[0][1].tt[0].s[2]  binding: 12
+// firstGlobal[0][1].tt[1].s[0]  binding: 13
+// firstGlobal[0][1].tt[1].s[1]  binding: 14
+// firstGlobal[0][1].tt[1].s[2]  binding: 15 (used)
+// firstGlobal[1][0].t[0]        binding: 16 (used)
+// firstGlobal[1][0].t[1]        binding: 17 (used)
+// firstGlobal[1][0].tt[0].s[0]  binding: 18
+// firstGlobal[1][0].tt[0].s[1]  binding: 19 (used)
+// firstGlobal[1][0].tt[0].s[2]  binding: 20
+// firstGlobal[1][0].tt[1].s[0]  binding: 21
+// firstGlobal[1][0].tt[1].s[1]  binding: 22
+// firstGlobal[1][0].tt[1].s[2]  binding: 23 (used)
+// firstGlobal[1][1].t[0]        binding: 24 (used)
+// firstGlobal[1][1].t[1]        binding: 25 (used)
+// firstGlobal[1][1].tt[0].s[0]  binding: 26
+// firstGlobal[1][1].tt[0].s[1]  binding: 27 (used)
+// firstGlobal[1][1].tt[0].s[2]  binding: 28
+// firstGlobal[1][1].tt[1].s[0]  binding: 29
+// firstGlobal[1][1].tt[1].s[1]  binding: 30
+// firstGlobal[1][1].tt[1].s[2]  binding: 31 (used)
+// secondGlobal.t[0]             binding: 32 (used)
+// secondGlobal.t[1]             binding: 33 (used)
+// secondGlobal.tt[0].s[0]       binding: 34
+// secondGlobal.tt[0].s[1]       binding: 35 (used)
+// secondGlobal.tt[0].s[2]       binding: 36
+// secondGlobal.tt[1].s[0]       binding: 37
+// secondGlobal.tt[1].s[1]       binding: 38
+// secondGlobal.tt[1].s[2]       binding: 39 (used)
+//
+// CHECK: OpDecorate %secondGlobal_t_0_ Binding 32
+// CHECK: OpDecorate %secondGlobal_t_1_ Binding 33
+// CHECK: OpDecorate %firstGlobal_0__0__t_0_ Binding 0
+// CHECK: OpDecorate %firstGlobal_0__0__t_1_ Binding 1
+// CHECK: OpDecorate %firstGlobal_0__1__t_0_ Binding 8
+// CHECK: OpDecorate %firstGlobal_0__1__t_1_ Binding 9
+// CHECK: OpDecorate %firstGlobal_1__0__t_0_ Binding 16
+// CHECK: OpDecorate %firstGlobal_1__0__t_1_ Binding 17
+// CHECK: OpDecorate %firstGlobal_1__1__t_0_ Binding 24
+// CHECK: OpDecorate %firstGlobal_1__1__t_1_ Binding 25
+// CHECK: OpDecorate %secondGlobal_tt_0__s_1_ Binding 35
+// CHECK: OpDecorate %secondGlobal_tt_1__s_2_ Binding 39
+// CHECK: OpDecorate %firstGlobal_0__0__tt_0__s_1_ Binding 3
+// CHECK: OpDecorate %firstGlobal_0__0__tt_1__s_2_ Binding 7
+// CHECK: OpDecorate %firstGlobal_0__1__tt_0__s_1_ Binding 11
+// CHECK: OpDecorate %firstGlobal_0__1__tt_1__s_2_ Binding 15
+// CHECK: OpDecorate %firstGlobal_1__0__tt_0__s_1_ Binding 19
+// CHECK: OpDecorate %firstGlobal_1__0__tt_1__s_2_ Binding 23
+// CHECK: OpDecorate %firstGlobal_1__1__tt_0__s_1_ Binding 27
+// CHECK: OpDecorate %firstGlobal_1__1__tt_1__s_2_ Binding 31
+
+// Check existence of replacement variables
+//
+// CHECK: %secondGlobal_t_0_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %secondGlobal_t_1_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_0__0__t_0_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_0__0__t_1_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_0__1__t_0_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_0__1__t_1_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_1__0__t_0_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_1__0__t_1_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_1__1__t_0_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %firstGlobal_1__1__t_1_ = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+// CHECK: %secondGlobal_tt_0__s_1_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %secondGlobal_tt_1__s_2_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_0__0__tt_0__s_1_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_0__0__tt_1__s_2_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_0__1__tt_0__s_1_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_0__1__tt_1__s_2_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_1__0__tt_0__s_1_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_1__0__tt_1__s_2_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_1__1__tt_0__s_1_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+// CHECK: %firstGlobal_1__1__tt_1__s_2_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+
+struct T {
+  SamplerState s[3];
+};
+
+struct S {
+  Texture2D t[2];
+  T tt[2];
+};
+
+float4 tex2D(S x, float2 v) {
+  return x.t[0].Sample(x.tt[0].s[1], v) + x.t[1].Sample(x.tt[1].s[2], v);
+}
+
+S firstGlobal[2][2];
+S secondGlobal;
+
+float4 main() : SV_Target {
+  return 
+// CHECK:      [[fg_0_0_t_0:%\d+]] = OpLoad %type_2d_image %firstGlobal_0__0__t_0_
+// CHECK:      [[fg_0_0_t_1:%\d+]] = OpLoad %type_2d_image %firstGlobal_0__0__t_1_
+// CHECK: [[fg_0_0_tt_0_s_1:%\d+]] = OpLoad %type_sampler %firstGlobal_0__0__tt_0__s_1_
+// CHECK: [[fg_0_0_tt_1_s_2:%\d+]] = OpLoad %type_sampler %firstGlobal_0__0__tt_1__s_2_
+// CHECK:   [[sampled_img_1:%\d+]] = OpSampledImage %type_sampled_image [[fg_0_0_t_0]] [[fg_0_0_tt_0_s_1]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_1]]
+// CHECK:   [[sampled_img_2:%\d+]] = OpSampledImage %type_sampled_image [[fg_0_0_t_1]] [[fg_0_0_tt_1_s_2]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_2]]
+// CHECK:                            OpFAdd
+    tex2D(firstGlobal[0][0], float2(0,0)) +
+// CHECK:      [[fg_0_1_t_0:%\d+]] = OpLoad %type_2d_image %firstGlobal_0__1__t_0_
+// CHECK:      [[fg_0_1_t_1:%\d+]] = OpLoad %type_2d_image %firstGlobal_0__1__t_1_
+// CHECK: [[fg_0_1_tt_0_s_1:%\d+]] = OpLoad %type_sampler %firstGlobal_0__1__tt_0__s_1_
+// CHECK: [[fg_0_1_tt_1_s_2:%\d+]] = OpLoad %type_sampler %firstGlobal_0__1__tt_1__s_2_
+// CHECK:   [[sampled_img_3:%\d+]] = OpSampledImage %type_sampled_image [[fg_0_1_t_0]] [[fg_0_1_tt_0_s_1]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_3]]
+// CHECK:   [[sampled_img_4:%\d+]] = OpSampledImage %type_sampled_image [[fg_0_1_t_1]] [[fg_0_1_tt_1_s_2]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_4]]
+// CHECK:                            OpFAdd
+    tex2D(firstGlobal[0][1], float2(0,0)) +
+// CHECK:      [[fg_1_0_t_0:%\d+]] = OpLoad %type_2d_image %firstGlobal_1__0__t_0_
+// CHECK:      [[fg_1_0_t_1:%\d+]] = OpLoad %type_2d_image %firstGlobal_1__0__t_1_
+// CHECK: [[fg_1_0_tt_0_s_1:%\d+]] = OpLoad %type_sampler %firstGlobal_1__0__tt_0__s_1_
+// CHECK: [[fg_1_0_tt_1_s_2:%\d+]] = OpLoad %type_sampler %firstGlobal_1__0__tt_1__s_2_
+// CHECK:   [[sampled_img_5:%\d+]] = OpSampledImage %type_sampled_image [[fg_1_0_t_0]] [[fg_1_0_tt_0_s_1]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_5]]
+// CHECK:   [[sampled_img_6:%\d+]] = OpSampledImage %type_sampled_image [[fg_1_0_t_1]] [[fg_1_0_tt_1_s_2]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_6]]
+// CHECK:                            OpFAdd
+    tex2D(firstGlobal[1][0], float2(0,0)) +
+// CHECK:      [[fg_1_1_t_0:%\d+]] = OpLoad %type_2d_image %firstGlobal_1__1__t_0_
+// CHECK:      [[fg_1_1_t_1:%\d+]] = OpLoad %type_2d_image %firstGlobal_1__1__t_1_
+// CHECK: [[fg_1_1_tt_0_s_1:%\d+]] = OpLoad %type_sampler %firstGlobal_1__1__tt_0__s_1_
+// CHECK: [[fg_1_1_tt_1_s_2:%\d+]] = OpLoad %type_sampler %firstGlobal_1__1__tt_1__s_2_
+// CHECK:   [[sampled_img_7:%\d+]] = OpSampledImage %type_sampled_image [[fg_1_1_t_0]] [[fg_1_1_tt_0_s_1]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_7]]
+// CHECK:   [[sampled_img_8:%\d+]] = OpSampledImage %type_sampled_image [[fg_1_1_t_1]] [[fg_1_1_tt_1_s_2]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_8]]
+// CHECK:                            OpFAdd
+    tex2D(firstGlobal[1][1], float2(0,0)) +
+// CHECK:          [[sg_t_0:%\d+]] = OpLoad %type_2d_image %secondGlobal_t_0_
+// CHECK:     [[sg_tt_0_s_1:%\d+]] = OpLoad %type_sampler %secondGlobal_tt_0__s_1_
+// CHECK:   [[sampled_img_9:%\d+]] = OpSampledImage %type_sampled_image [[sg_t_0]] [[sg_tt_0_s_1]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_9]]
+// CHECK:                            OpFAdd
+    secondGlobal.t[0].Sample(secondGlobal.tt[0].s[1], float2(0,0)) +
+// CHECK:          [[sg_t_1:%\d+]] = OpLoad %type_2d_image %secondGlobal_t_1_
+// CHECK:     [[sg_tt_1_s_2:%\d+]] = OpLoad %type_sampler %secondGlobal_tt_1__s_2_
+// CHECK:  [[sampled_img_10:%\d+]] = OpSampledImage %type_sampled_image [[sg_t_1]] [[sg_tt_1_s_2]]
+// CHECK:                 {{%\d+}} = OpImageSampleImplicitLod %v4float [[sampled_img_10]]
+    secondGlobal.t[1].Sample(secondGlobal.tt[1].s[2], float2(0,0));
+}
+

+ 50 - 0
tools/clang/unittests/SPIRV/CodeGenSpirvTest.cpp

@@ -1770,6 +1770,56 @@ TEST_F(FileTest, VulkanStructuredBufferCounter) {
   runFileTest("vk.binding.counter.hlsl");
 }
 
+TEST_F(FileTest, BindingStructureOfResources1) {
+  // In Vulkan, OpTypeStruct must not contain an opaque type.
+  // Therefore this test fails validation before legalization is performed.
+  runFileTest("vk.binding.global-struct-of-resources.1.hlsl", Expect::Success,
+              /*runValidation*/ false);
+}
+
+TEST_F(FileTest, BindingStructureOfResources2) {
+  // In Vulkan, OpTypeStruct must not contain an opaque type.
+  // Therefore this test fails validation before legalization is performed.
+  runFileTest("vk.binding.global-struct-of-resources.2.hlsl", Expect::Success,
+              /*runValidation*/ false);
+}
+
+TEST_F(FileTest, BindingStructureOfResources3) {
+  // In Vulkan, OpTypeStruct must not contain an opaque type.
+  // Therefore this test fails validation before legalization is performed.
+  runFileTest("vk.binding.global-struct-of-resources.3.hlsl", Expect::Success,
+              /*runValidation*/ false);
+}
+
+TEST_F(FileTest, BindingStructureOfResources4) {
+  // In Vulkan, OpTypeStruct must not contain an opaque type.
+  // Therefore this test fails validation before legalization is performed.
+  runFileTest("vk.binding.global-struct-of-resources.4.hlsl", Expect::Success,
+              /*runValidation*/ false);
+}
+
+TEST_F(FileTest, BindingStructureOfResourcesOptimized) {
+  // After optimization is performed, this binary should pass validation.
+  runFileTest("vk.binding.global-struct-of-resources.optimized.hlsl",
+              Expect::Success, /*runValidation*/ true);
+}
+
+TEST_F(FileTest, BindingStructureOfResourcesAndNonResourcesError1) {
+  runFileTest("vk.binding.global-struct-of-resource-mix.error.1.hlsl",
+              Expect::Failure, /*runValidation*/ false);
+}
+
+TEST_F(FileTest, BindingStructureOfResourcesAndNonResourcesError2) {
+  runFileTest("vk.binding.global-struct-of-resource-mix.error.2.hlsl",
+              Expect::Failure);
+}
+
+TEST_F(FileTest, BindingStructureOfResourcesContainsBufferError) {
+  runFileTest(
+      "vk.binding.global-struct-of-resources.contains-buffer-error.hlsl",
+      Expect::Failure);
+}
+
 TEST_F(FileTest, VulkanPushConstant) { runFileTest("vk.push-constant.hlsl"); }
 TEST_F(FileTest, VulkanPushConstantOffset) {
   // Checks the behavior of [[vk::offset]] with [[vk::push_constant]]