Browse Source

[spirv] Translate SubpassInput(MS) and their methods (#1013)

Lei Zhang 7 years ago
parent
commit
4cbada6181

+ 41 - 0
docs/SPIR-V.rst

@@ -166,6 +166,44 @@ Descriptors
 To specify which Vulkan descriptor a particular resource binds to, use the
 To specify which Vulkan descriptor a particular resource binds to, use the
 ``[[vk::binding(X[, Y])]]`` attribute.
 ``[[vk::binding(X[, Y])]]`` attribute.
 
 
+Subpass inputs
+~~~~~~~~~~~~~~
+
+Within a Vulkan `rendering pass <https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#renderpass>`_,
+a subpass can write results to an output target that can then be read by the
+next subpass as an input subpass. The "Subpass Input" feature regards the
+ability to read an output target.
+
+Subpasses are read through two new builtin resource types, available only in
+pixel shader:
+
+.. code:: hlsl
+
+  class SubpassInput<T> {
+    T SubpassLoad();
+  };
+
+  class SubpassInputMS<T> {
+    T SubpassLoad(int sampleIndex);
+  };
+
+In the above, ``T`` is a scalar or vector type. If omitted, it will defaults to
+``float4``.
+
+Subpass inputs are implicitly addressed by the pixel's (x, y, layer) coordinate.
+These objects support reading the subpass input through the methods as shown
+in the above.
+
+A subpass input is selected by using a new attribute ``vk::input_attachment_index``.
+For example:
+
+.. code:: hlsl
+
+  [[vk::input_attachment_index(i)]] SubpassInput input;
+
+An ``vk::input_attachment_index`` of ``i`` selects the ith entry in the input
+pass list. (See Vulkan API spec for more information.)
+
 Push constants
 Push constants
 ~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~
 
 
@@ -209,6 +247,9 @@ The namespace ``vk`` will be used for all Vulkan attributes:
 - ``push_constant``: For marking a variable as the push constant block. Allowed
 - ``push_constant``: For marking a variable as the push constant block. Allowed
   on global variables of struct type. At most one variable can be marked as
   on global variables of struct type. At most one variable can be marked as
   ``push_constant`` in a shader.
   ``push_constant`` in a shader.
+- ``input_attachment_index(X)``: To associate the Xth entry in the input pass
+  list to the annotated object. Only allowed on objects whose type are
+  ``SubpassInput`` or ``SubpassInputMS``.
 - ``builtin("X")``: For specifying an entity should be translated into a certain
 - ``builtin("X")``: For specifying an entity should be translated into a certain
   Vulkan builtin variable. Allowed on function parameters, function returns,
   Vulkan builtin variable. Allowed on function parameters, function returns,
   and struct fields.
   and struct fields.

+ 4 - 0
tools/clang/include/clang/SPIRV/ModuleBuilder.h

@@ -357,6 +357,10 @@ public:
   void decorateDSetBinding(uint32_t targetId, uint32_t setNumber,
   void decorateDSetBinding(uint32_t targetId, uint32_t setNumber,
                            uint32_t bindingNumber);
                            uint32_t bindingNumber);
 
 
+  /// \brief Decorates the given target <result-id> with the given input
+  /// attchment index number.
+  void decorateInputAttachmentIndex(uint32_t targetId, uint32_t indexNumber);
+
   /// \brief Decorates the given target <result-id> with the given decoration
   /// \brief Decorates the given target <result-id> with the given decoration
   /// (without additional parameters).
   /// (without additional parameters).
   void decorate(uint32_t targetId, spv::Decoration);
   void decorate(uint32_t targetId, spv::Decoration);

+ 4 - 1
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -412,6 +412,9 @@ uint32_t DeclResultIdMapper::createExternVar(const VarDecl *var) {
   resourceVars.emplace_back(id, getResourceCategory(var->getType()), regAttr,
   resourceVars.emplace_back(id, getResourceCategory(var->getType()), regAttr,
                             bindingAttr, counterBindingAttr);
                             bindingAttr, counterBindingAttr);
 
 
+  if (const auto *inputAttachment = var->getAttr<VKInputAttachmentIndexAttr>())
+    theBuilder.decorateInputAttachmentIndex(id, inputAttachment->getIndex());
+
   if (isACRWSBuffer) {
   if (isACRWSBuffer) {
     // For {Append|Consume|RW}StructuredBuffer, we need to always create another
     // For {Append|Consume|RW}StructuredBuffer, we need to always create another
     // variable for its associated counter.
     // variable for its associated counter.
@@ -874,7 +877,7 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
         // We have checked that not all of the stage variables have explicit
         // We have checked that not all of the stage variables have explicit
         // location assignment.
         // location assignment.
         emitError("partial explicit stage %select{output|input}0 location "
         emitError("partial explicit stage %select{output|input}0 location "
-                  "assignment via [[vk::location(X)]] unsupported",
+                  "assignment via vk::location(X) unsupported",
                   {})
                   {})
             << forInput;
             << forInput;
         return false;
         return false;

+ 8 - 3
tools/clang/lib/SPIRV/ModuleBuilder.cpp

@@ -761,6 +761,11 @@ void ModuleBuilder::decorateDSetBinding(uint32_t targetId, uint32_t setNumber,
   d = Decoration::getBinding(theContext, bindingNumber);
   d = Decoration::getBinding(theContext, bindingNumber);
   theModule.addDecoration(d, targetId);
   theModule.addDecoration(d, targetId);
 }
 }
+void ModuleBuilder::decorateInputAttachmentIndex(uint32_t targetId,
+                                                 uint32_t indexNumber) {
+  const auto *d = Decoration::getInputAttachmentIndex(theContext, indexNumber);
+  theModule.addDecoration(d, targetId);
+}
 
 
 void ModuleBuilder::decorateLocation(uint32_t targetId, uint32_t location) {
 void ModuleBuilder::decorateLocation(uint32_t targetId, uint32_t location) {
   const Decoration *d =
   const Decoration *d =
@@ -973,10 +978,10 @@ uint32_t ModuleBuilder::getImageType(uint32_t sampledType, spv::Dim dim,
     } else {
     } else {
       requireCapability(spv::Capability::Sampled1D);
       requireCapability(spv::Capability::Sampled1D);
     }
     }
-  }
-
-  if (dim == spv::Dim::Buffer) {
+  } else if (dim == spv::Dim::Buffer) {
     requireCapability(spv::Capability::SampledBuffer);
     requireCapability(spv::Capability::SampledBuffer);
+  } else if (dim == spv::Dim::SubpassData) {
+    requireCapability(spv::Capability::InputAttachment);
   }
   }
 
 
   if (isArray && ms) {
   if (isArray && ms) {

+ 84 - 17
tools/clang/lib/SPIRV/SPIRVEmitter.cpp

@@ -879,7 +879,59 @@ void SPIRVEmitter::doFunctionDecl(const FunctionDecl *decl) {
   theBuilder.endFunction();
   theBuilder.endFunction();
 }
 }
 
 
-void SPIRVEmitter::validateVKAttributes(const NamedDecl *decl) {
+bool SPIRVEmitter::validateVKAttributes(const NamedDecl *decl) {
+  bool success = true;
+
+  if (decl->hasAttr<HLSLRowMajorAttr>()) {
+    emitWarning("row_major attribute for stand-alone matrix is not supported",
+                decl->getAttr<HLSLRowMajorAttr>()->getLocation());
+  }
+  if (decl->hasAttr<HLSLColumnMajorAttr>()) {
+    emitWarning(
+        "column_major attribute for stand-alone matrix is not supported",
+        decl->getAttr<HLSLColumnMajorAttr>()->getLocation());
+  }
+
+  if (const auto *varDecl = dyn_cast<VarDecl>(decl)) {
+    const auto varType = varDecl->getType();
+    if ((TypeTranslator::isSubpassInput(varType) ||
+         TypeTranslator::isSubpassInputMS(varType)) &&
+        !varDecl->hasAttr<VKInputAttachmentIndexAttr>()) {
+      emitError("missing vk::input_attachment_index attribute",
+                varDecl->getLocation());
+      success = false;
+    }
+  }
+
+  if (const auto *iaiAttr = decl->getAttr<VKInputAttachmentIndexAttr>()) {
+    if (!shaderModel.IsPS()) {
+      emitError("SubpassInput(MS) only allowed in pixel shader",
+                decl->getLocation());
+      success = false;
+    }
+
+    if (!decl->isExternallyVisible()) {
+      emitError("SubpassInput(MS) must be externally visible",
+                decl->getLocation());
+      success = false;
+    }
+
+    // We only allow VKInputAttachmentIndexAttr to be attached to global
+    // variables. So it should be fine to cast here.
+    const auto elementType =
+        hlsl::GetHLSLResourceResultType(cast<VarDecl>(decl)->getType());
+
+    if (!TypeTranslator::isScalarType(elementType) &&
+        !TypeTranslator::isVectorType(elementType)) {
+      emitError(
+          "only scalar/vector types allowed as SubpassInput(MS) parameter type",
+          decl->getLocation());
+      // Return directly to avoid further type processing, which will hit
+      // asserts in TypeTranslator.
+      return false;
+    }
+  }
+
   // The frontend will make sure that
   // The frontend will make sure that
   // * vk::push_constant applies to global variables of struct type
   // * vk::push_constant applies to global variables of struct type
   // * vk::binding applies to global variables or cbuffers/tbuffers
   // * vk::binding applies to global variables or cbuffers/tbuffers
@@ -903,14 +955,18 @@ void SPIRVEmitter::validateVKAttributes(const NamedDecl *decl) {
       emitError("cannot have more than one push constant block", loc);
       emitError("cannot have more than one push constant block", loc);
       emitNote("push constant block previously defined here",
       emitNote("push constant block previously defined here",
                seenPushConstantAt);
                seenPushConstantAt);
+      success = false;
     }
     }
 
 
     if (decl->hasAttr<VKBindingAttr>()) {
     if (decl->hasAttr<VKBindingAttr>()) {
-      emitError("'push_constant' attribute cannot be used together with "
-                "'binding' attribute",
+      emitError("vk::push_constant attribute cannot be used together with "
+                "vk::binding attribute",
                 loc);
                 loc);
+      success = false;
     }
     }
   }
   }
+
+  return success;
 }
 }
 
 
 void SPIRVEmitter::doHLSLBufferDecl(const HLSLBufferDecl *bufferDecl) {
 void SPIRVEmitter::doHLSLBufferDecl(const HLSLBufferDecl *bufferDecl) {
@@ -930,7 +986,8 @@ void SPIRVEmitter::doHLSLBufferDecl(const HLSLBufferDecl *bufferDecl) {
           emitWarning("packoffset ignored since not supported", packing->Loc);
           emitWarning("packoffset ignored since not supported", packing->Loc);
     }
     }
   }
   }
-  validateVKAttributes(bufferDecl);
+  if (!validateVKAttributes(bufferDecl))
+    return;
   (void)declIdMapper.createCTBuffer(bufferDecl);
   (void)declIdMapper.createCTBuffer(bufferDecl);
 }
 }
 
 
@@ -953,17 +1010,8 @@ void SPIRVEmitter::doRecordDecl(const RecordDecl *recordDecl) {
 }
 }
 
 
 void SPIRVEmitter::doVarDecl(const VarDecl *decl) {
 void SPIRVEmitter::doVarDecl(const VarDecl *decl) {
-  validateVKAttributes(decl);
-
-  if (decl->hasAttr<HLSLRowMajorAttr>()) {
-    emitWarning("row_major attribute for stand-alone matrix is not supported",
-                decl->getAttr<HLSLRowMajorAttr>()->getLocation());
-  }
-  if (decl->hasAttr<HLSLColumnMajorAttr>()) {
-    emitWarning(
-        "column_major attribute for stand-alone matrix is not supported",
-        decl->getAttr<HLSLColumnMajorAttr>()->getLocation());
-  }
+  if (!validateVKAttributes(decl))
+    return;
 
 
   if (decl->hasAttr<VKPushConstantAttr>()) {
   if (decl->hasAttr<VKPushConstantAttr>()) {
     // This is a VarDecl for PushConstant block.
     // This is a VarDecl for PushConstant block.
@@ -2338,6 +2386,18 @@ uint32_t SPIRVEmitter::processGetSamplePosition(const CXXMemberCallExpr *expr) {
   return emitGetSamplePosition(sampleCount, doExpr(expr->getArg(0)));
   return emitGetSamplePosition(sampleCount, doExpr(expr->getArg(0)));
 }
 }
 
 
+SpirvEvalInfo SPIRVEmitter::processSubpassLoad(const CXXMemberCallExpr *expr) {
+  const auto *object = expr->getImplicitObjectArgument()->IgnoreParens();
+  const uint32_t sample = expr->getNumArgs() == 1 ? doExpr(expr->getArg(0)) : 0;
+  const uint32_t zero = theBuilder.getConstantInt32(0);
+  const uint32_t location = theBuilder.getConstantComposite(
+      theBuilder.getVecType(theBuilder.getInt32Type(), 2), {zero, zero});
+
+  return processBufferTextureLoad(object, location, /*constOffset*/ 0,
+                                  /*varOffset*/ 0, /*lod*/ sample,
+                                  /*residencyCode*/ 0);
+}
+
 uint32_t
 uint32_t
 SPIRVEmitter::processBufferTextureGetDimensions(const CXXMemberCallExpr *expr) {
 SPIRVEmitter::processBufferTextureGetDimensions(const CXXMemberCallExpr *expr) {
   const auto *object = expr->getImplicitObjectArgument();
   const auto *object = expr->getImplicitObjectArgument();
@@ -2625,7 +2685,10 @@ SpirvEvalInfo SPIRVEmitter::processBufferTextureLoad(
   // The result type of an OpImageFetch must be a vec4 of float or int.
   // The result type of an OpImageFetch must be a vec4 of float or int.
   const auto type = object->getType();
   const auto type = object->getType();
   assert(TypeTranslator::isBuffer(type) || TypeTranslator::isRWBuffer(type) ||
   assert(TypeTranslator::isBuffer(type) || TypeTranslator::isRWBuffer(type) ||
-         TypeTranslator::isTexture(type) || TypeTranslator::isRWTexture(type));
+         TypeTranslator::isTexture(type) || TypeTranslator::isRWTexture(type) ||
+         TypeTranslator::isSubpassInput(type) ||
+         TypeTranslator::isSubpassInputMS(type));
+
   const bool doFetch =
   const bool doFetch =
       TypeTranslator::isBuffer(type) || TypeTranslator::isTexture(type);
       TypeTranslator::isBuffer(type) || TypeTranslator::isTexture(type);
 
 
@@ -2633,7 +2696,8 @@ SpirvEvalInfo SPIRVEmitter::processBufferTextureLoad(
 
 
   // For Texture2DMS and Texture2DMSArray, Sample must be used rather than Lod.
   // For Texture2DMS and Texture2DMSArray, Sample must be used rather than Lod.
   uint32_t sampleNumber = 0;
   uint32_t sampleNumber = 0;
-  if (TypeTranslator::isTextureMS(type)) {
+  if (TypeTranslator::isTextureMS(type) ||
+      TypeTranslator::isSubpassInputMS(type)) {
     sampleNumber = lod;
     sampleNumber = lod;
     lod = 0;
     lod = 0;
   }
   }
@@ -3351,6 +3415,9 @@ SPIRVEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr,
   case IntrinsicOp::MOP_GetSamplePosition:
   case IntrinsicOp::MOP_GetSamplePosition:
     retVal = processGetSamplePosition(expr);
     retVal = processGetSamplePosition(expr);
     break;
     break;
+  case IntrinsicOp::MOP_SubpassLoad:
+    retVal = processSubpassLoad(expr);
+    break;
   case IntrinsicOp::MOP_GatherCmpGreen:
   case IntrinsicOp::MOP_GatherCmpGreen:
   case IntrinsicOp::MOP_GatherCmpBlue:
   case IntrinsicOp::MOP_GatherCmpBlue:
   case IntrinsicOp::MOP_GatherCmpAlpha:
   case IntrinsicOp::MOP_GatherCmpAlpha:

+ 7 - 2
tools/clang/lib/SPIRV/SPIRVEmitter.h

@@ -275,8 +275,9 @@ private:
                      const llvm::SmallVector<uint32_t, 4> &indices);
                      const llvm::SmallVector<uint32_t, 4> &indices);
 
 
 private:
 private:
-  /// Validates that vk::* attributes are used correctly.
-  void validateVKAttributes(const NamedDecl *decl);
+  /// Validates that vk::* attributes are used correctly and returns false if
+  /// errors are found.
+  bool validateVKAttributes(const NamedDecl *decl);
 
 
 private:
 private:
   /// Processes the given expr, casts the result into the given bool (vector)
   /// Processes the given expr, casts the result into the given bool (vector)
@@ -732,6 +733,10 @@ private:
   /// Texture2DMS(Array).
   /// Texture2DMS(Array).
   uint32_t processGetSamplePosition(const CXXMemberCallExpr *);
   uint32_t processGetSamplePosition(const CXXMemberCallExpr *);
 
 
+  /// \brief Processes the SubpassLoad intrinsic function call on a
+  /// SubpassInput(MS).
+  SpirvEvalInfo processSubpassLoad(const CXXMemberCallExpr *);
+
   /// \brief Generates SPIR-V instructions for the .Append()/.Consume() call on
   /// \brief Generates SPIR-V instructions for the .Append()/.Consume() call on
   /// the given {Append|Consume}StructuredBuffer. Returns the <result-id> of
   /// the given {Append|Consume}StructuredBuffer. Returns the <result-id> of
   /// the loaded value for .Consume; returns zero for .Append().
   /// the loaded value for .Consume; returns zero for .Append().

+ 22 - 0
tools/clang/lib/SPIRV/TypeTranslator.cpp

@@ -613,6 +613,20 @@ bool TypeTranslator::isSampler(QualType type) {
   return false;
   return false;
 }
 }
 
 
+bool TypeTranslator::isSubpassInput(QualType type) {
+  if (const auto *rt = type->getAs<RecordType>())
+    return rt->getDecl()->getName() == "SubpassInput";
+
+  return false;
+}
+
+bool TypeTranslator::isSubpassInputMS(QualType type) {
+  if (const auto *rt = type->getAs<RecordType>())
+    return rt->getDecl()->getName() == "SubpassInputMS";
+
+  return false;
+}
+
 bool TypeTranslator::isVectorType(QualType type, QualType *elemType,
 bool TypeTranslator::isVectorType(QualType type, QualType *elemType,
                                   uint32_t *elemCount) {
                                   uint32_t *elemCount) {
   bool isVec = false;
   bool isVec = false;
@@ -999,6 +1013,14 @@ uint32_t TypeTranslator::translateResourceType(QualType type, LayoutRule rule) {
     return translateType(hlsl::GetHLSLResourceResultType(type), rule);
     return translateType(hlsl::GetHLSLResourceResultType(type), rule);
   }
   }
 
 
+  if (name == "SubpassInput" || name == "SubpassInputMS") {
+    const auto sampledType = hlsl::GetHLSLResourceResultType(type);
+    return theBuilder.getImageType(
+        translateType(getElementType(sampledType)), spv::Dim::SubpassData,
+        /*depth*/ 0, /*isArray*/ false, /*ms*/ name == "SubpassInputMS",
+        /*sampled*/ 2);
+  }
+
   return 0;
   return 0;
 }
 }
 
 

+ 6 - 0
tools/clang/lib/SPIRV/TypeTranslator.h

@@ -119,6 +119,12 @@ public:
   /// \brief Returns true if the given type is an HLSL sampler type.
   /// \brief Returns true if the given type is an HLSL sampler type.
   static bool isSampler(QualType);
   static bool isSampler(QualType);
 
 
+  /// \brief Returns true if the given type is SubpassInput.
+  static bool isSubpassInput(QualType);
+
+  /// \brief Returns true if the given type is SubpassInputMS.
+  static bool isSubpassInputMS(QualType);
+
   /// \brief Returns true if the given type will be translated into a SPIR-V
   /// \brief Returns true if the given type will be translated into a SPIR-V
   /// scalar type. This includes normal scalar types, vectors of size 1, and
   /// scalar type. This includes normal scalar types, vectors of size 1, and
   /// 1x1 matrices. If scalarType is not nullptr, writes the scalar type to
   /// 1x1 matrices. If scalarType is not nullptr, writes the scalar type to

+ 2 - 2
tools/clang/test/CodeGenSPIRV/vk.attribute.invalid.hlsl

@@ -1,4 +1,4 @@
-// Run: %dxc -T ps_6_0 -E main
+// Run: %dxc -T vs_6_0 -E main
 
 
 struct S {
 struct S {
     float f;
     float f;
@@ -11,4 +11,4 @@ float main() : A {
     return 1.0;
     return 1.0;
 }
 }
 
 
-// CHECK: :7:3: error: 'push_constant' attribute cannot be used together with 'binding' attribute
+// CHECK: :7:3: error: vk::push_constant attribute cannot be used together with vk::binding attribute

+ 27 - 0
tools/clang/test/CodeGenSPIRV/vk.subpass-input.binding.hlsl

@@ -0,0 +1,27 @@
+// Run: %dxc -T ps_6_0 -E main
+
+// CHECK: OpDecorate %SI0 InputAttachmentIndex 0
+// CHECK: OpDecorate %SI1 InputAttachmentIndex 1
+// CHECK: OpDecorate %SI2 InputAttachmentIndex 2
+
+// CHECK: OpDecorate %SI1 DescriptorSet 0
+// CHECK: OpDecorate %SI1 Binding 5
+
+// CHECK: OpDecorate %SI2 DescriptorSet 3
+// CHECK: OpDecorate %SI2 Binding 5
+
+// CHECK: OpDecorate %SI0 DescriptorSet 0
+// CHECK: OpDecorate %SI0 Binding 0
+
+[[vk::input_attachment_index(0)]]
+SubpassInput SI0;
+
+[[vk::input_attachment_index(1), vk::binding(5)]]
+SubpassInput SI1;
+
+[[vk::input_attachment_index(2), vk::binding(5, 3)]]
+SubpassInput SI2;
+
+void main() {
+
+}

+ 23 - 0
tools/clang/test/CodeGenSPIRV/vk.subpass-input.error.hlsl

@@ -0,0 +1,23 @@
+// Run: %dxc -T ps_6_0 -E main
+
+struct S {
+    float  a;
+    float2 b;
+    float  c;
+};
+
+SubpassInput SI0; // error
+
+[[vk::input_attachment_index(0)]]
+SubpassInput<S> SI1; // error
+
+[[vk::input_attachment_index(1)]]
+static SubpassInput SI2; // error
+
+void main() {
+
+}
+
+// CHECK:  :9:14: error: missing vk::input_attachment_index attribute
+// CHECK: :12:17: error: only scalar/vector types allowed as SubpassInput(MS) parameter type
+// CHECK: :15:21: error: SubpassInput(MS) must be externally visible

+ 85 - 0
tools/clang/test/CodeGenSPIRV/vk.subpass-input.hlsl

@@ -0,0 +1,85 @@
+// Run: %dxc -T ps_6_0 -E main
+
+// CHECK: OpCapability InputAttachment
+
+// CHECK: %type_subpass_image = OpTypeImage %float SubpassData 0 0 0 2 Unknown
+// CHECK: %_ptr_UniformConstant_type_subpass_image = OpTypePointer UniformConstant %type_subpass_image
+
+// CHECK: %type_subpass_image_0 = OpTypeImage %int SubpassData 0 0 0 2 Unknown
+// CHECK: %_ptr_UniformConstant_type_subpass_image_0 = OpTypePointer UniformConstant %type_subpass_image_0
+
+// CHECK: %type_subpass_image_1 = OpTypeImage %uint SubpassData 0 0 0 2 Unknown
+// CHECK: %_ptr_UniformConstant_type_subpass_image_1 = OpTypePointer UniformConstant %type_subpass_image_1
+
+// CHECK: %type_subpass_image_2 = OpTypeImage %uint SubpassData 0 0 1 2 Unknown
+// CHECK: %_ptr_UniformConstant_type_subpass_image_2 = OpTypePointer UniformConstant %type_subpass_image_2
+
+// CHECK: %type_subpass_image_3 = OpTypeImage %float SubpassData 0 0 1 2 Unknown
+// CHECK: %_ptr_UniformConstant_type_subpass_image_3 = OpTypePointer UniformConstant %type_subpass_image_3
+
+// CHECK: %type_subpass_image_4 = OpTypeImage %int SubpassData 0 0 1 2 Unknown
+// CHECK: %_ptr_UniformConstant_type_subpass_image_4 = OpTypePointer UniformConstant %type_subpass_image_4
+
+// CHECK:  [[v2i00:%\d+]] = OpConstantComposite %v2int %int_0 %int_0
+
+// CHCK:   %SI_f4 = OpVariable %_ptr_UniformConstant_type_subpass_image UniformConstant
+[[vk::input_attachment_index(0)]]  SubpassInput           SI_f4;
+// CHCK:   %SI_i3 = OpVariable %_ptr_UniformConstant_type_subpass_image_0 UniformConstant
+[[vk::input_attachment_index(1)]]  SubpassInput<int3>     SI_i3;
+// CHCK:   %SI_u2 = OpVariable %_ptr_UniformConstant_type_subpass_image_1 UniformConstant
+[[vk::input_attachment_index(2)]]  SubpassInput<uint2>    SI_u2;
+// CHCK:   %SI_f1 = OpVariable %_ptr_UniformConstant_type_subpass_image UniformConstant
+[[vk::input_attachment_index(3)]]  SubpassInput<float>    SI_f1;
+
+// CHCK: %SIMS_u4 = OpVariable %_ptr_UniformConstant_type_subpass_image_2 UniformConstant
+[[vk::input_attachment_index(10)]] SubpassInputMS<uint4>  SIMS_u4;
+// CHCK: %SIMS_f3 = OpVariable %_ptr_UniformConstant_type_subpass_image_3 UniformConstant
+[[vk::input_attachment_index(11)]] SubpassInputMS<float3> SIMS_f3;
+// CHCK: %SIMS_i2 = OpVariable %_ptr_UniformConstant_type_subpass_image_4 UniformConstant
+[[vk::input_attachment_index(12)]] SubpassInputMS<int2>   SIMS_i2;
+// CHCK: %SIMS_u1 = OpVariable %_ptr_UniformConstant_type_subpass_image_2 UniformConstant
+[[vk::input_attachment_index(13)]] SubpassInputMS<uint>   SIMS_u1;
+
+float4 main() : SV_Target {
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image %SI_f4
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4float [[img]] [[v2i00]] None
+// CHECK-NEXT:                  OpStore %v0 [[texel]]
+    float4 v0 = SI_f4.SubpassLoad();
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image_0 %SI_i3
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4int [[img]] [[v2i00]] None
+// CHECK-NEXT:   [[val:%\d+]] = OpVectorShuffle %v3int [[texel]] [[texel]] 0 1 2
+// CHECK-NEXT:                  OpStore %v1 [[val]]
+    int3   v1 = SI_i3.SubpassLoad();
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image_1 %SI_u2
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4uint [[img]] [[v2i00]] None
+// CHECK-NEXT:   [[val:%\d+]] = OpVectorShuffle %v2uint [[texel]] [[texel]] 0 1
+// CHECK-NEXT:                  OpStore %v2 [[val]]
+    uint2  v2 = SI_u2.SubpassLoad();
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image %SI_f1
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4float [[img]] [[v2i00]] None
+// CHECK-NEXT:   [[val:%\d+]] = OpCompositeExtract %float [[texel]] 0
+// CHECK-NEXT:                  OpStore %v3 [[val]]
+    float  v3 = SI_f1.SubpassLoad();
+
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image_2 %SIMS_u4
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4uint [[img]] [[v2i00]] Sample %int_1
+// CHECK-NEXT:                  OpStore %v10 [[texel]]
+    uint4  v10 = SIMS_u4.SubpassLoad(1);
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image_3 %SIMS_f3
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4float [[img]] [[v2i00]] Sample %int_2
+// CHECK-NEXT:   [[val:%\d+]] = OpVectorShuffle %v3float [[texel]] [[texel]] 0 1 2
+// CHECK-NEXT:                  OpStore %v11 [[val]]
+    float3 v11 = SIMS_f3.SubpassLoad(2);
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image_4 %SIMS_i2
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4int [[img]] [[v2i00]] Sample %int_3
+// CHECK-NEXT:   [[val:%\d+]] = OpVectorShuffle %v2int [[texel]] [[texel]] 0 1
+// CHECK-NEXT:                  OpStore %v12 [[val]]
+    int2   v12 = SIMS_i2.SubpassLoad(3);
+// CHECK:        [[img:%\d+]] = OpLoad %type_subpass_image_2 %SIMS_u1
+// CHECK-NEXT: [[texel:%\d+]] = OpImageRead %v4uint [[img]] [[v2i00]] Sample %int_4
+// CHECK-NEXT:   [[val:%\d+]] = OpCompositeExtract %uint [[texel]] 0
+// CHECK-NEXT:                  OpStore %v13 [[val]]
+    uint   v13 = SIMS_u1.SubpassLoad(4);
+
+    return v0.x + v1.y + v2.x + v3 + v10.x + v11.y + v12.x + v13;
+}

+ 8 - 0
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -1151,6 +1151,14 @@ TEST_F(FileTest, VulkanLayoutCBufferPackOffset) {
   runFileTest("vk.layout.cbuffer.packoffset.hlsl", Expect::Warning);
   runFileTest("vk.layout.cbuffer.packoffset.hlsl", Expect::Warning);
 }
 }
 
 
+TEST_F(FileTest, VulkanSubpassInput) { runFileTest("vk.subpass-input.hlsl"); }
+TEST_F(FileTest, VulkanSubpassInputBinding) {
+  runFileTest("vk.subpass-input.binding.hlsl");
+}
+TEST_F(FileTest, VulkanSubpassInputError) {
+  runFileTest("vk.subpass-input.error.hlsl", Expect::Failure);
+}
+
 // HS: for different Patch Constant Functions
 // HS: for different Patch Constant Functions
 TEST_F(FileTest, HullShaderPCFVoid) { runFileTest("hs.pcf.void.hlsl"); }
 TEST_F(FileTest, HullShaderPCFVoid) { runFileTest("hs.pcf.void.hlsl"); }
 TEST_F(FileTest, HullShaderPCFTakesInputPatch) {
 TEST_F(FileTest, HullShaderPCFTakesInputPatch) {