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
 ``[[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
 ~~~~~~~~~~~~~~
 
@@ -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
   on global variables of struct type. At most one variable can be marked as
   ``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
   Vulkan builtin variable. Allowed on function parameters, function returns,
   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,
                            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
   /// (without additional parameters).
   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,
                             bindingAttr, counterBindingAttr);
 
+  if (const auto *inputAttachment = var->getAttr<VKInputAttachmentIndexAttr>())
+    theBuilder.decorateInputAttachmentIndex(id, inputAttachment->getIndex());
+
   if (isACRWSBuffer) {
     // For {Append|Consume|RW}StructuredBuffer, we need to always create another
     // 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
         // location assignment.
         emitError("partial explicit stage %select{output|input}0 location "
-                  "assignment via [[vk::location(X)]] unsupported",
+                  "assignment via vk::location(X) unsupported",
                   {})
             << forInput;
         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);
   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) {
   const Decoration *d =
@@ -973,10 +978,10 @@ uint32_t ModuleBuilder::getImageType(uint32_t sampledType, spv::Dim dim,
     } else {
       requireCapability(spv::Capability::Sampled1D);
     }
-  }
-
-  if (dim == spv::Dim::Buffer) {
+  } else if (dim == spv::Dim::Buffer) {
     requireCapability(spv::Capability::SampledBuffer);
+  } else if (dim == spv::Dim::SubpassData) {
+    requireCapability(spv::Capability::InputAttachment);
   }
 
   if (isArray && ms) {

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

@@ -879,7 +879,59 @@ void SPIRVEmitter::doFunctionDecl(const FunctionDecl *decl) {
   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
   // * vk::push_constant applies to global variables of struct type
   // * 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);
       emitNote("push constant block previously defined here",
                seenPushConstantAt);
+      success = false;
     }
 
     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);
+      success = false;
     }
   }
+
+  return success;
 }
 
 void SPIRVEmitter::doHLSLBufferDecl(const HLSLBufferDecl *bufferDecl) {
@@ -930,7 +986,8 @@ void SPIRVEmitter::doHLSLBufferDecl(const HLSLBufferDecl *bufferDecl) {
           emitWarning("packoffset ignored since not supported", packing->Loc);
     }
   }
-  validateVKAttributes(bufferDecl);
+  if (!validateVKAttributes(bufferDecl))
+    return;
   (void)declIdMapper.createCTBuffer(bufferDecl);
 }
 
@@ -953,17 +1010,8 @@ void SPIRVEmitter::doRecordDecl(const RecordDecl *recordDecl) {
 }
 
 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>()) {
     // 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)));
 }
 
+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
 SPIRVEmitter::processBufferTextureGetDimensions(const CXXMemberCallExpr *expr) {
   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.
   const auto type = object->getType();
   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 =
       TypeTranslator::isBuffer(type) || TypeTranslator::isTexture(type);
 
@@ -2633,7 +2696,8 @@ SpirvEvalInfo SPIRVEmitter::processBufferTextureLoad(
 
   // For Texture2DMS and Texture2DMSArray, Sample must be used rather than Lod.
   uint32_t sampleNumber = 0;
-  if (TypeTranslator::isTextureMS(type)) {
+  if (TypeTranslator::isTextureMS(type) ||
+      TypeTranslator::isSubpassInputMS(type)) {
     sampleNumber = lod;
     lod = 0;
   }
@@ -3351,6 +3415,9 @@ SPIRVEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr,
   case IntrinsicOp::MOP_GetSamplePosition:
     retVal = processGetSamplePosition(expr);
     break;
+  case IntrinsicOp::MOP_SubpassLoad:
+    retVal = processSubpassLoad(expr);
+    break;
   case IntrinsicOp::MOP_GatherCmpGreen:
   case IntrinsicOp::MOP_GatherCmpBlue:
   case IntrinsicOp::MOP_GatherCmpAlpha:

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

@@ -275,8 +275,9 @@ private:
                      const llvm::SmallVector<uint32_t, 4> &indices);
 
 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:
   /// Processes the given expr, casts the result into the given bool (vector)
@@ -732,6 +733,10 @@ private:
   /// Texture2DMS(Array).
   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
   /// the given {Append|Consume}StructuredBuffer. Returns the <result-id> of
   /// 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;
 }
 
+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,
                                   uint32_t *elemCount) {
   bool isVec = false;
@@ -999,6 +1013,14 @@ uint32_t TypeTranslator::translateResourceType(QualType type, LayoutRule 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;
 }
 

+ 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.
   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
   /// scalar type. This includes normal scalar types, vectors of size 1, and
   /// 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 {
     float f;
@@ -11,4 +11,4 @@ float main() : A {
     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);
 }
 
+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
 TEST_F(FileTest, HullShaderPCFVoid) { runFileTest("hs.pcf.void.hlsl"); }
 TEST_F(FileTest, HullShaderPCFTakesInputPatch) {