Quellcode durchsuchen

Add -fvk-auto-shift-bindings that applies -fvk-*-shift bindings to re… (#2959)

* Add -fvk-auto-shift-bindings that applies -fvk-*-shift bindings to resources that do not have explicit register assignments.  Closes #2956
* For resources that don't have an explicit register binding, first categorize their registerType using new method DeclResultIdMapper::getImplicitRegisterType.
* When finding automatic binding indices for implicit register bindings, pass an optional shift to useNextBinding.
* Change getNextBindingChunk() to handle finding the next binding range given a bindingShift

* Fix regression with binding numbers causing AppVeyor failure and also fix crash I ran into in my own testing in getImplicitRegisterType() if the getSpirvInstr() did not have an AstResultType.

* Address review comments.

* Update documentation

Co-authored-by: Ehsan Nasiri <[email protected]>
Dan Ginsburg vor 5 Jahren
Ursprung
Commit
e93ab88f7d

+ 57 - 0
docs/SPIR-V.rst

@@ -1516,6 +1516,54 @@ assigned to the next available binding number, starting from 0, in descriptor
 set #0 (If ``-auto-binding-space N`` command line option is used, then
 descriptor set #N will be used instead of descriptor set #0).
 
+If there is no register specification AND ``-fvk-auto-shift-bindings`` is specified,
+then the register type will be automatically identified based on the resource
+type (according to the following table), and the appropriate shift will
+automatically be applied according to ``-fvk-*shift N M``.
+
+.. code:: spirv
+
+  t - for shader resource views (SRV)
+      TEXTURE1D
+      TEXTURE1DARRAY
+      TEXTURE2D
+      TEXTURE2DARRAY
+      TEXTURE3D
+      TEXTURECUBE
+      TEXTURECUBEARRAY
+      TEXTURE2DMS
+      TEXTURE2DMSARRAY
+      STRUCTUREDBUFFER
+      BYTEADDRESSBUFFER
+      BUFFER
+      TBUFFER
+
+  s - for samplers
+      SAMPLER
+      SAMPLER1D
+      SAMPLER2D
+      SAMPLER3D
+      SAMPLERCUBE
+      SAMPLERSTATE
+      SAMPLERCOMPARISONSTATE
+
+  u - for unordered access views (UAV)
+      RWBYTEADDRESSBUFFER
+      RWSTRUCTUREDBUFFER
+      APPENDSTRUCTUREDBUFFER
+      CONSUMESTRUCTUREDBUFFER
+      RWBUFFER
+      RWTEXTURE1D
+      RWTEXTURE1DARRAY
+      RWTEXTURE2D
+      RWTEXTURE2DARRAY
+      RWTEXTURE3D
+
+  b - for constant buffer views (CBV)
+      CBUFFER
+      CONSTANTBUFFER
+
+
 Summary
 ~~~~~~~
 
@@ -1523,9 +1571,15 @@ In summary, the compiler essentially assigns binding numbers in three passes.
 
 - Firstly it handles all declarations with explicit ``[[vk::binding(X[, Y])]]``
   annotation.
+
 - Then the compiler processes all remaining declarations with
   ``:register(xX, spaceY)`` annotation, by applying the shift passed in using
   command-line option ``-fvk-{b|s|t|u}-shift N M``, if provided.
+
+  - If ``:register`` assignment is missing and ``-fvk-auto-shift-bindings`` is
+    specified, the register type will be automatically detected based on the
+    resource type, and the ``-fvk-{b|s|t|u}-shift N M`` will be applied.
+
 - Finally, the compiler assigns next available binding numbers to the rest in
   the declaration order.
 
@@ -3550,6 +3604,9 @@ codegen for Vulkan:
 - ``-fvk-t-shift N M``, similar to ``-fvk-b-shift``, but for t-type registers.
 - ``-fvk-s-shift N M``, similar to ``-fvk-b-shift``, but for s-type registers.
 - ``-fvk-u-shift N M``, similar to ``-fvk-b-shift``, but for u-type registers.
+- ``-fvk-auto-shift-bindings``: Automatically detects the register type for
+  resources that are missing the ``:register`` assignment, so the above shifts
+  can be applied to them if needed.
 - ``-fvk-bind-register xX Y N M`` (short alias: ``-vkbr``): Binds the resouce
   at ``register(xX, spaceY)`` to descriptor set ``M`` and binding ``N``. This
   option cannot be used together with other binding assignment options.

+ 2 - 0
include/dxc/Support/HLSLOptions.td

@@ -310,6 +310,8 @@ def fspv_target_env_EQ : Joined<["-"], "fspv-target-env=">, Group<spirv_Group>,
   HelpText<"Specify the target environment: vulkan1.0 (default) or vulkan1.1">;
 def fspv_flatten_resource_arrays: Flag<["-"], "fspv-flatten-resource-arrays">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
   HelpText<"Flatten arrays of resources so each array element takes one binding number">;
+def fvk_auto_shift_bindings: Flag<["-"], "fvk-auto-shift-bindings">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
+  HelpText<"Apply fvk-*-shift to resources without an explicit register assignment.">;
 def Wno_vk_ignored_features : Joined<["-"], "Wno-vk-ignored-features">, Group<spirv_Group>, Flags<[CoreOption, DriverOption, HelpHidden]>,
   HelpText<"Do not emit warnings for ingored features resulting from no Vulkan support">;
 def Wno_vk_emulated_features : Joined<["-"], "Wno-vk-emulated-features">, Group<spirv_Group>, Flags<[CoreOption, DriverOption, HelpHidden]>,

+ 1 - 0
include/dxc/Support/SPIRVOptions.h

@@ -54,6 +54,7 @@ struct SpirvCodeGenOptions {
   bool useGlLayout;
   bool useScalarLayout;
   bool flattenResourceArrays;
+  bool autoShiftBindings;
   SpirvLayoutRule cBufferLayoutRule;
   SpirvLayoutRule sBufferLayoutRule;
   SpirvLayoutRule tBufferLayoutRule;

+ 2 - 0
lib/DxcSupport/HLSLOptions.cpp

@@ -778,6 +778,7 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
   opts.SpirvOptions.noWarnEmulatedFeatures = Args.hasFlag(OPT_Wno_vk_emulated_features, OPT_INVALID, false);
   opts.SpirvOptions.flattenResourceArrays =
       Args.hasFlag(OPT_fspv_flatten_resource_arrays, OPT_INVALID, false);
+  opts.SpirvOptions.autoShiftBindings = Args.hasFlag(OPT_fvk_auto_shift_bindings, OPT_INVALID, false);
 
   if (!handleVkShiftArgs(Args, OPT_fvk_b_shift, "b", &opts.SpirvOptions.bShift, errors) ||
       !handleVkShiftArgs(Args, OPT_fvk_t_shift, "t", &opts.SpirvOptions.tShift, errors) ||
@@ -856,6 +857,7 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
       Args.hasFlag(OPT_fspv_reflect, OPT_INVALID, false) ||
       Args.hasFlag(OPT_Wno_vk_ignored_features, OPT_INVALID, false) ||
       Args.hasFlag(OPT_Wno_vk_emulated_features, OPT_INVALID, false) ||
+      Args.hasFlag(OPT_fvk_auto_shift_bindings, OPT_INVALID, false) ||
       !Args.getLastArgValue(OPT_fvk_stage_io_order_EQ).empty() ||
       !Args.getLastArgValue(OPT_fspv_debug_EQ).empty() ||
       !Args.getLastArgValue(OPT_fspv_extension_EQ).empty() ||

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

@@ -162,6 +162,10 @@ bool isRowMajorMatrix(const SpirvCodeGenOptions &, QualType type);
 /// \brief Returns true if the given type is a (RW)StructuredBuffer type.
 bool isStructuredBuffer(QualType type);
 
+/// \brief Returns true if the given type is a non-writable StructuredBuffer
+/// type.
+bool isNonWritableStructuredBuffer(QualType type);
+
 /// \brief Returns true if the given type is an AppendStructuredBuffer type.
 bool isAppendStructuredBuffer(QualType type);
 

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

@@ -750,6 +750,14 @@ bool isStructuredBuffer(QualType type) {
   return name == "StructuredBuffer" || name == "RWStructuredBuffer";
 }
 
+bool isNonWritableStructuredBuffer(QualType type) {
+  const auto *recordType = type->getAs<RecordType>();
+  if (!recordType)
+    return false;
+  const auto name = recordType->getDecl()->getName();
+  return name == "StructuredBuffer";
+}
+
 bool isByteAddressBuffer(QualType type) {
   if (const auto *rt = type->getAs<RecordType>()) {
     return rt->getDecl()->getName() == "ByteAddressBuffer";

+ 104 - 14
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -1345,11 +1345,13 @@ public:
     return inserted;
   }
 
-  /// Uses the next avaiable binding number in |set|. If more than one binding
+  /// Uses the next available binding number in |set|. If more than one binding
   /// number is to be occupied, it finds the next available chunk that can fit
   /// |numBindingsToUse| in the |set|.
-  uint32_t useNextBinding(uint32_t set, uint32_t numBindingsToUse = 1) {
-    uint32_t bindingNoStart = getNextBindingChunk(set, numBindingsToUse);
+  uint32_t useNextBinding(uint32_t set, uint32_t numBindingsToUse = 1,
+                          uint32_t bindingShift = 0) {
+    uint32_t bindingNoStart =
+        getNextBindingChunk(set, numBindingsToUse, bindingShift);
     auto &binding = usedBindings[set];
     for (uint32_t i = 0; i < numBindingsToUse; ++i)
       binding.insert(bindingNoStart + i);
@@ -1357,19 +1359,20 @@ public:
   }
 
   /// Returns the first available binding number in the |set| for which |n|
-  /// consecutive binding numbers are unused.
-  uint32_t getNextBindingChunk(uint32_t set, uint32_t n) {
+  /// consecutive binding numbers are unused starting at |bindingShift|.
+  uint32_t getNextBindingChunk(uint32_t set, uint32_t n,
+                               uint32_t bindingShift) {
     auto &existingBindings = usedBindings[set];
 
     // There were no bindings in this set. Can start at binding zero.
     if (existingBindings.empty())
-      return 0;
+      return bindingShift;
 
     // Check whether the chunk of |n| binding numbers can be fitted at the
     // very beginning of the list (start at binding 0 in the current set).
     uint32_t curBinding = *existingBindings.begin();
-    if (curBinding >= n)
-      return 0;
+    if (curBinding >= (n + bindingShift))
+      return bindingShift;
 
     auto iter = std::next(existingBindings.begin());
     while (iter != existingBindings.end()) {
@@ -1377,7 +1380,10 @@ public:
       // gap between current binding number and next binding number is large
       // enough to accommodate |n|.
       uint32_t nextBinding = *iter;
-      if (n <= nextBinding - curBinding - 1)
+      if ((bindingShift > 0) && (curBinding < (bindingShift - 1)))
+        curBinding = bindingShift - 1;
+
+      if (curBinding < nextBinding && n <= nextBinding - curBinding - 1)
         return curBinding + 1;
 
       curBinding = nextBinding;
@@ -1388,7 +1394,7 @@ public:
 
     // |curBinding| was the last binding that was used in this set. The next
     // chunk of |n| bindings can start at |curBinding|+1.
-    return curBinding + 1;
+    return std::max(curBinding + 1, bindingShift);
   }
 
 private:
@@ -1808,7 +1814,31 @@ bool DeclResultIdMapper::decorateResourceBindings() {
       numBindingsToUse = getNumBindingsUsedByResourceType(
           var.getSpirvInstr()->getAstResultType());
 
+    BindingShiftMapper *bindingShiftMapper = nullptr;
+    if (spirvOptions.autoShiftBindings) {
+      char registerType = '\0';
+      if (getImplicitRegisterType(var, &registerType)) {
+        switch (registerType) {
+        case 'b':
+          bindingShiftMapper = &bShiftMapper;
+          break;
+        case 't':
+          bindingShiftMapper = &tShiftMapper;
+          break;
+        case 's':
+          bindingShiftMapper = &sShiftMapper;
+          break;
+        case 'u':
+          bindingShiftMapper = &uShiftMapper;
+          break;
+        default:
+          llvm_unreachable("unknown register type found");
+        }
+      }
+    }
+
     if (var.isCounter()) {
+
       if (!var.getCounterBinding()) {
         // Process mX * c2
         uint32_t set = defaultSpace;
@@ -1817,17 +1847,23 @@ bool DeclResultIdMapper::decorateResourceBindings() {
         else if (const auto *reg = var.getRegister())
           set = reg->RegisterSpace.getValueOr(defaultSpace);
 
+        uint32_t bindingShift = 0;
+        if (bindingShiftMapper)
+          bindingShift = bindingShiftMapper->getShiftForSet(set);
         spvBuilder.decorateDSetBinding(
             var.getSpirvInstr(), set,
-            bindingSet.useNextBinding(set, numBindingsToUse));
+            bindingSet.useNextBinding(set, numBindingsToUse, bindingShift));
       }
     } else if (!var.getBinding()) {
       const auto *reg = var.getRegister();
       if (reg && reg->isSpaceOnly()) {
         const uint32_t set = reg->RegisterSpace.getValueOr(defaultSpace);
+        uint32_t bindingShift = 0;
+        if (bindingShiftMapper)
+          bindingShift = bindingShiftMapper->getShiftForSet(set);
         spvBuilder.decorateDSetBinding(
             var.getSpirvInstr(), set,
-            bindingSet.useNextBinding(set, numBindingsToUse));
+            bindingSet.useNextBinding(set, numBindingsToUse, bindingShift));
       } else if (!reg) {
         // Process m3 (no 'vk::binding' and no ':register' assignment)
 
@@ -1835,14 +1871,21 @@ bool DeclResultIdMapper::decorateResourceBindings() {
         // doesn't have either 'vk::binding' or ':register', but the user may
         // ask for a specific binding for it via command line options.
         if (bindGlobals && var.isGlobalsBuffer()) {
+          uint32_t bindingShift = 0;
+          if (bindingShiftMapper)
+            bindingShift = bindingShiftMapper->getShiftForSet(globalsSetNo);
           spvBuilder.decorateDSetBinding(var.getSpirvInstr(), globalsSetNo,
-                                         globalsBindNo);
+                                         globalsBindNo + bindingShift);
         }
         // The normal case
         else {
+          uint32_t bindingShift = 0;
+          if (bindingShiftMapper)
+            bindingShift = bindingShiftMapper->getShiftForSet(defaultSpace);
           spvBuilder.decorateDSetBinding(
               var.getSpirvInstr(), defaultSpace,
-              bindingSet.useNextBinding(defaultSpace, numBindingsToUse));
+              bindingSet.useNextBinding(defaultSpace, numBindingsToUse,
+                                        bindingShift));
         }
       }
     }
@@ -3426,6 +3469,53 @@ QualType DeclResultIdMapper::getTypeAndCreateCounterForPotentialAliasVar(
   return type;
 }
 
+bool DeclResultIdMapper::getImplicitRegisterType(const ResourceVar &var,
+                                                 char *registerTypeOut) const {
+  assert(registerTypeOut);
+
+  if (var.getSpirvInstr()) {
+    if (var.getSpirvInstr()->hasAstResultType()) {
+      QualType type = var.getSpirvInstr()->getAstResultType();
+      // Strip outer arrayness first
+      while (type->isArrayType())
+        type = type->getAsArrayTypeUnsafe()->getElementType();
+
+      // t - for shader resource views (SRV)
+      if (isTexture(type) || isNonWritableStructuredBuffer(type) ||
+          isByteAddressBuffer(type) || isBuffer(type)) {
+        *registerTypeOut = 't';
+        return true;
+      }
+      // s - for samplers
+      else if (isSampler(type)) {
+        *registerTypeOut = 's';
+        return true;
+      }
+      // u - for unordered access views (UAV)
+      else if (isRWByteAddressBuffer(type) || isRWAppendConsumeSBuffer(type) ||
+               isRWBuffer(type) || isRWTexture(type)) {
+        *registerTypeOut = 'u';
+        return true;
+      }
+    } else {
+      llvm::StringRef hlslUserType = var.getSpirvInstr()->getHlslUserType();
+      // b - for constant buffer views (CBV)
+      if (var.isGlobalsBuffer() || hlslUserType == "cbuffer" ||
+          hlslUserType == "ConstantBuffer") {
+        *registerTypeOut = 'b';
+        return true;
+      }
+      if (hlslUserType == "tbuffer") {
+        *registerTypeOut = 't';
+        return true;
+      }
+    }
+  }
+
+  *registerTypeOut = '\0';
+  return false;
+}
+
 SpirvVariable *
 DeclResultIdMapper::createRayTracingNVStageVar(spv::StorageClass sc,
                                                const VarDecl *decl) {

+ 47 - 0
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -691,6 +691,53 @@ private:
   /// Returns true if the given SPIR-V stage variable has Input storage class.
   inline bool isInputStorageClass(const StageVar &v);
 
+  /// Determines the register type for a resource that does not have an
+  /// explicit register() declaration.  Returns true if it is able to
+  /// determine the register type and will set |*registerTypeOut| to
+  /// 'u', 's', 'b', or 't'. Assumes |registerTypeOut| to be non-nullptr.
+  ///
+  /// Uses the following mapping of HLSL types to register spaces:
+  /// t - for shader resource views (SRV)
+  ///    TEXTURE1D
+  ///    TEXTURE1DARRAY
+  ///    TEXTURE2D
+  ///    TEXTURE2DARRAY
+  ///    TEXTURE3D
+  ///    TEXTURECUBE
+  ///    TEXTURECUBEARRAY
+  ///    TEXTURE2DMS
+  ///    TEXTURE2DMSARRAY
+  ///    STRUCTUREDBUFFER
+  ///    BYTEADDRESSBUFFER
+  ///    BUFFER
+  ///    TBUFFER
+  ///
+  /// s - for samplers
+  ///    SAMPLER
+  ///    SAMPLER1D
+  ///    SAMPLER2D
+  ///    SAMPLER3D
+  ///    SAMPLERCUBE
+  ///    SAMPLERSTATE
+  ///    SAMPLERCOMPARISONSTATE
+  ///
+  /// u - for unordered access views (UAV)
+  ///    RWBYTEADDRESSBUFFER
+  ///    RWSTRUCTUREDBUFFER
+  ///    APPENDSTRUCTUREDBUFFER
+  ///    CONSUMESTRUCTUREDBUFFER
+  ///    RWBUFFER
+  ///    RWTEXTURE1D
+  ///    RWTEXTURE1DARRAY
+  ///    RWTEXTURE2D
+  ///    RWTEXTURE2DARRAY
+  ///    RWTEXTURE3D
+  ///
+  /// b - for constant buffer views (CBV)
+  ///    CBUFFER
+  ///    CONSTANTBUFFER
+  bool getImplicitRegisterType(const ResourceVar &var, char *registerTypeOut) const;
+
 private:
   SpirvBuilder &spvBuilder;
   SpirvEmitter &theEmitter;

+ 132 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.cl.auto-shift-bindings.hlsl

@@ -0,0 +1,132 @@
+// Run: %dxc -T ps_6_0 -E main -fvk-b-shift 100 0 -fvk-t-shift 200 0 -fvk-t-shift 300 1 -fvk-s-shift 500 0 -fvk-u-shift 700 0 -fvk-auto-shift-bindings -fspv-flatten-resource-arrays
+
+// Tests that we can set shift for more than one sets of the same register type
+// Tests that we can override shift for the same set
+
+struct S {
+    float4 f;
+};
+
+// Explicit binding assignment is unaffected by -auto-shift-bindings
+//
+// CHECK: OpDecorate %cbuffer1 DescriptorSet 0
+// CHECK: OpDecorate %cbuffer1 Binding 42
+[[vk::binding(42)]]
+ConstantBuffer<S> cbuffer1;
+
+// "-fvk-t-shift 300 1" should shift the binding for this resource.
+//
+// CHECK: OpDecorate %texture1 DescriptorSet 1
+// CHECK: OpDecorate %texture1 Binding 301
+Texture1D<float4> texture1: register(t1, space1);
+
+// "-fvk-t-shift 200 0" should shift it by 200. So we should get binding slots 200-209.
+//
+// CHECK: OpDecorate %textureArray_1 DescriptorSet 0
+// CHECK: OpDecorate %textureArray_1 Binding 200
+Texture1DArray<float4> textureArray_1[10] : register(t0);
+
+// "-fvk-t-shift 200 0" should shift it by 200. So we should get binding slots 215-224.
+//
+// CHECK: OpDecorate %textureArray_2 DescriptorSet 0
+// CHECK: OpDecorate %textureArray_2 Binding 215
+Texture2D<float4> textureArray_2[10] : register(t15);
+
+// Missing 'register' assignment.
+// "-auto-shift-bindings" should detect that this is register of type 't'.
+// "-fvk-t-shift 200 0" should shift it by 200.
+// This is an array of 6, so it requires 6 binding slots.
+// Binding slot 200-209 is taken. There's a gap of 5 slots (210-214) inclusive
+// which is not large enough to for this resource. So, it should be placed
+// after the previous textures, starting at binding slot 225. Consumes 225-230, inclusive.
+//
+// CHECK: OpDecorate %textureArray_3 DescriptorSet 0
+// CHECK: OpDecorate %textureArray_3 Binding 225
+Texture2DArray<float4> textureArray_3[6];
+
+// The following all have the 't' register type and they take the next
+// available binding slots starting at 200.
+//
+// CHECK: OpDecorate %t3d DescriptorSet 0
+// CHECK: OpDecorate %t3d Binding 210
+Texture3D<float4> t3d;
+// CHECK: OpDecorate %tc DescriptorSet 0
+// CHECK: OpDecorate %tc Binding 211
+TextureCube<float4> tc;
+// CHECK: OpDecorate %tcarr DescriptorSet 0
+// CHECK: OpDecorate %tcarr Binding 212
+TextureCubeArray<float4> tcarr;
+// CHECK: OpDecorate %t2dms DescriptorSet 0
+// CHECK: OpDecorate %t2dms Binding 213
+Texture2DMS<float4> t2dms;
+// CHECK: OpDecorate %t2dmsarr DescriptorSet 0
+// CHECK: OpDecorate %t2dmsarr Binding 214
+Texture2DMSArray<float4> t2dmsarr;
+// CHECK: OpDecorate %sb DescriptorSet 0
+// CHECK: OpDecorate %sb Binding 231
+StructuredBuffer<float4> sb;
+// CHECK: OpDecorate %bab DescriptorSet 0
+// CHECK: OpDecorate %bab Binding 232
+ByteAddressBuffer bab;
+// CHECK: OpDecorate %buf DescriptorSet 0
+// CHECK: OpDecorate %buf Binding 233
+Buffer<float4> buf;
+// CHECK: OpDecorate %myTbuffer DescriptorSet 0
+// CHECK: OpDecorate %myTbuffer Binding 234
+tbuffer myTbuffer { float x; };
+
+// "-auto-shift-bindings" should detect that this is register of type 's'.
+// "-fvk-s-shift 500 0" should shift it by 500.
+//
+// CHECK: OpDecorate %ss DescriptorSet 0
+// CHECK: OpDecorate %ss Binding 500
+SamplerState ss;
+// CHECK: OpDecorate %scs DescriptorSet 0
+// CHECK: OpDecorate %scs Binding 501
+SamplerComparisonState scs;
+
+// "-auto-shift-bindings" should detect that this is register of type 'u'.
+// "-fvk-u-shift 700 0" should shift it by 700.
+//
+// CHECK: OpDecorate %rwbab DescriptorSet 0
+// CHECK: OpDecorate %rwbab Binding 700
+RWByteAddressBuffer rwbab;
+// CHECK: OpDecorate %rwsb DescriptorSet 0
+// CHECK: OpDecorate %rwsb Binding 701
+RWStructuredBuffer<float4> rwsb;
+// CHECK: OpDecorate %asb DescriptorSet 0
+// CHECK: OpDecorate %asb Binding 702
+AppendStructuredBuffer<float4> asb;
+// CHECK: OpDecorate %csb DescriptorSet 0
+// CHECK: OpDecorate %csb Binding 703
+ConsumeStructuredBuffer<float4> csb;
+// CHECK: OpDecorate %rwbuffer DescriptorSet 0
+// CHECK: OpDecorate %rwbuffer Binding 704
+RWBuffer<float4> rwbuffer;
+// CHECK: OpDecorate %rwtexture1d DescriptorSet 0
+// CHECK: OpDecorate %rwtexture1d Binding 705
+RWTexture1D<float4> rwtexture1d;
+// CHECK: OpDecorate %rwtexture1darray DescriptorSet 0
+// CHECK: OpDecorate %rwtexture1darray Binding 706
+RWTexture1DArray<float4> rwtexture1darray;
+// CHECK: OpDecorate %rwtexture2d DescriptorSet 0
+// CHECK: OpDecorate %rwtexture2d Binding 707
+RWTexture2D<float4> rwtexture2d;
+// CHECK: OpDecorate %rwtexture2darray DescriptorSet 0
+// CHECK: OpDecorate %rwtexture2darray Binding 708
+RWTexture2DArray<float4> rwtexture2darray;
+// CHECK: OpDecorate %rwtexture3d DescriptorSet 0
+// CHECK: OpDecorate %rwtexture3d Binding 709
+RWTexture3D<float4> rwtexture3d;
+
+// Missing 'register' assignment.
+// "-auto-shift-bindings" should detect that this is register of type 'b'.
+// "-fvk-b-shift 100 0" should shift it by 100.
+//
+// CHECK: OpDecorate %cbuffer2 DescriptorSet 0
+// CHECK: OpDecorate %cbuffer2 Binding 100
+ConstantBuffer<S> cbuffer2;
+
+float4 main() : SV_Target {
+    return 0.xxxx;
+}

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

@@ -1770,6 +1770,12 @@ TEST_F(FileTest, VulkanStructuredBufferCounter) {
   runFileTest("vk.binding.counter.hlsl");
 }
 
+TEST_F(FileTest, AutoShiftBindings) {
+  // Checks the correctness for the "-fvk-auto-shift-bindings" command line
+  // option.
+  runFileTest("vk.binding.cl.auto-shift-bindings.hlsl");
+}
+
 TEST_F(FileTest, BindingStructureOfResources1) {
   // In Vulkan, OpTypeStruct must not contain an opaque type.
   // Therefore this test fails validation before legalization is performed.