D3D12: use the WGSL binding number as shader register

Currently, the bind group layout tightly packs the shader registers for
each of the sampler and non-sampler descriptors sets. This reduces the
max shader register used and helps targeting shader model 5.0, which has
a relatively low max slot count per resource. It is safe in D3D, since a
shader register collision can be valid if the descriptor types differ.

To support Mesa's SPIR-V to DXIL compiler, we need to avoid possible
collisions between resources' shader registers because it uses SPIR-V as
an intermediate representation (which does not namespace bindings by
type). This change re-works BindGroupLayoutD3D12 to not assume the
resulting shader registers will be tightly packed and group per
descriptor type.

Change-Id: I0bb51106c4683bfe02ce15ecad71716734b7a91f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/60764
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Michael Tang <tangm@microsoft.com>
diff --git a/src/dawn_native/d3d12/BindGroupD3D12.cpp b/src/dawn_native/d3d12/BindGroupD3D12.cpp
index a9f2731..4832cdd 100644
--- a/src/dawn_native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupD3D12.cpp
@@ -40,7 +40,7 @@
 
         mCPUViewAllocation = viewAllocation;
 
-        const auto& bindingOffsets = bgl->GetBindingOffsets();
+        const auto& descriptorHeapOffsets = bgl->GetDescriptorHeapOffsets();
 
         ID3D12Device* d3d12Device = device->GetD3D12Device();
 
@@ -74,8 +74,8 @@
                                 ToBackend(binding.buffer)->GetVA() + binding.offset;
 
                             d3d12Device->CreateConstantBufferView(
-                                &desc, viewAllocation.OffsetFrom(viewSizeIncrement,
-                                                                 bindingOffsets[bindingIndex]));
+                                &desc, viewAllocation.OffsetFrom(
+                                           viewSizeIncrement, descriptorHeapOffsets[bindingIndex]));
                             break;
                         }
                         case wgpu::BufferBindingType::Storage:
@@ -99,7 +99,7 @@
                             d3d12Device->CreateUnorderedAccessView(
                                 resource, nullptr, &desc,
                                 viewAllocation.OffsetFrom(viewSizeIncrement,
-                                                          bindingOffsets[bindingIndex]));
+                                                          descriptorHeapOffsets[bindingIndex]));
                             break;
                         }
                         case wgpu::BufferBindingType::ReadOnlyStorage: {
@@ -118,7 +118,7 @@
                             d3d12Device->CreateShaderResourceView(
                                 resource, &desc,
                                 viewAllocation.OffsetFrom(viewSizeIncrement,
-                                                          bindingOffsets[bindingIndex]));
+                                                          descriptorHeapOffsets[bindingIndex]));
                             break;
                         }
                         case wgpu::BufferBindingType::Undefined:
@@ -142,7 +142,8 @@
 
                     d3d12Device->CreateShaderResourceView(
                         resource, &srv,
-                        viewAllocation.OffsetFrom(viewSizeIncrement, bindingOffsets[bindingIndex]));
+                        viewAllocation.OffsetFrom(viewSizeIncrement,
+                                                  descriptorHeapOffsets[bindingIndex]));
                     break;
                 }
 
@@ -165,7 +166,7 @@
                             d3d12Device->CreateShaderResourceView(
                                 resource, &srv,
                                 viewAllocation.OffsetFrom(viewSizeIncrement,
-                                                          bindingOffsets[bindingIndex]));
+                                                          descriptorHeapOffsets[bindingIndex]));
                             break;
                         }
 
@@ -174,7 +175,7 @@
                             d3d12Device->CreateUnorderedAccessView(
                                 resource, nullptr, &uav,
                                 viewAllocation.OffsetFrom(viewSizeIncrement,
-                                                          bindingOffsets[bindingIndex]));
+                                                          descriptorHeapOffsets[bindingIndex]));
                             break;
                         }
 
@@ -201,7 +202,8 @@
 
                     d3d12Device->CreateShaderResourceView(
                         resource, &srv,
-                        viewAllocation.OffsetFrom(viewSizeIncrement, bindingOffsets[bindingIndex]));
+                        viewAllocation.OffsetFrom(viewSizeIncrement,
+                                                  descriptorHeapOffsets[bindingIndex]));
                     break;
                 }
 
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
index 28719de..30b5a54 100644
--- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
@@ -22,35 +22,35 @@
 
 namespace dawn_native { namespace d3d12 {
     namespace {
-        BindGroupLayout::DescriptorType WGPUBindingInfoToDescriptorType(
+        D3D12_DESCRIPTOR_RANGE_TYPE WGPUBindingInfoToDescriptorRangeType(
             const BindingInfo& bindingInfo) {
             switch (bindingInfo.bindingType) {
                 case BindingInfoType::Buffer:
                     switch (bindingInfo.buffer.type) {
                         case wgpu::BufferBindingType::Uniform:
-                            return BindGroupLayout::DescriptorType::CBV;
+                            return D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
                         case wgpu::BufferBindingType::Storage:
                         case kInternalStorageBufferBinding:
-                            return BindGroupLayout::DescriptorType::UAV;
+                            return D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
                         case wgpu::BufferBindingType::ReadOnlyStorage:
-                            return BindGroupLayout::DescriptorType::SRV;
+                            return D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
                         case wgpu::BufferBindingType::Undefined:
                             UNREACHABLE();
                     }
 
                 case BindingInfoType::Sampler:
-                    return BindGroupLayout::DescriptorType::Sampler;
+                    return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
 
                 case BindingInfoType::Texture:
                 case BindingInfoType::ExternalTexture:
-                    return BindGroupLayout::DescriptorType::SRV;
+                    return D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
 
                 case BindingInfoType::StorageTexture:
                     switch (bindingInfo.storageTexture.access) {
                         case wgpu::StorageTextureAccess::ReadOnly:
-                            return BindGroupLayout::DescriptorType::SRV;
+                            return D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
                         case wgpu::StorageTextureAccess::WriteOnly:
-                            return BindGroupLayout::DescriptorType::UAV;
+                            return D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
                         case wgpu::StorageTextureAccess::Undefined:
                             UNREACHABLE();
                     }
@@ -66,82 +66,62 @@
 
     BindGroupLayout::BindGroupLayout(Device* device, const BindGroupLayoutDescriptor* descriptor)
         : BindGroupLayoutBase(device, descriptor),
-          mBindingOffsets(GetBindingCount()),
-          mDescriptorCounts{},
+          mDescriptorHeapOffsets(GetBindingCount()),
+          mShaderRegisters(GetBindingCount()),
+          mCbvUavSrvDescriptorCount(0),
+          mSamplerDescriptorCount(0),
           mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {
-        for (BindingIndex bindingIndex = GetDynamicBufferCount(); bindingIndex < GetBindingCount();
-             ++bindingIndex) {
+        for (BindingIndex bindingIndex{0}; bindingIndex < GetBindingCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
 
-            // For dynamic resources, Dawn uses root descriptor in D3D12 backend.
-            // So there is no need to allocate the descriptor from descriptor heap.
-            // This loop starts after the dynamic buffer indices to skip counting
-            // dynamic resources in calculating the size of the descriptor heap.
+            D3D12_DESCRIPTOR_RANGE_TYPE descriptorRangeType =
+                WGPUBindingInfoToDescriptorRangeType(bindingInfo);
+
+            // TODO(dawn:728) In the future, special handling will be needed for external textures
+            // here because they encompass multiple views.
+            mShaderRegisters[bindingIndex] = uint32_t(bindingInfo.binding);
+
+            if (bindingIndex < GetDynamicBufferCount()) {
+                continue;
+            }
+
+            // For dynamic resources, Dawn uses root descriptor in D3D12 backend. So there is no
+            // need to allocate the descriptor from descriptor heap or create descriptor ranges.
             ASSERT(!bindingInfo.buffer.hasDynamicOffset);
 
             // TODO(dawn:728) In the future, special handling will be needed for external textures
             // here because they encompass multiple views.
-            DescriptorType descriptorType = WGPUBindingInfoToDescriptorType(bindingInfo);
-            mBindingOffsets[bindingIndex] = mDescriptorCounts[descriptorType]++;
-        }
+            mDescriptorHeapOffsets[bindingIndex] =
+                descriptorRangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER
+                    ? mSamplerDescriptorCount++
+                    : mCbvUavSrvDescriptorCount++;
 
-        auto SetDescriptorRange = [&](uint32_t index, uint32_t count, uint32_t* baseRegister,
-                                      D3D12_DESCRIPTOR_RANGE_TYPE type) -> bool {
-            if (count == 0) {
-                return false;
-            }
-
-            auto& range = mRanges[index];
-            range.RangeType = type;
-            range.NumDescriptors = count;
-            range.RegisterSpace = 0;
+            D3D12_DESCRIPTOR_RANGE range;
+            range.RangeType = descriptorRangeType;
+            range.NumDescriptors = 1;
+            range.BaseShaderRegister = GetShaderRegister(bindingIndex);
+            range.RegisterSpace = kRegisterSpacePlaceholder;
             range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
-            range.BaseShaderRegister = *baseRegister;
-            *baseRegister += count;
-            // These ranges will be copied and range.BaseShaderRegister will be set in
-            // d3d12::PipelineLayout to account for bind group register offsets
-            return true;
-        };
 
-        uint32_t rangeIndex = 0;
-        uint32_t baseRegister = 0;
+            std::vector<D3D12_DESCRIPTOR_RANGE>& descriptorRanges =
+                descriptorRangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER
+                    ? mSamplerDescriptorRanges
+                    : mCbvUavSrvDescriptorRanges;
 
-        std::array<uint32_t, DescriptorType::Count> descriptorOffsets;
-        // Ranges 0-2 contain the CBV, UAV, and SRV ranges, if they exist, tightly packed
-        // Range 3 contains the Sampler range, if there is one
-        if (SetDescriptorRange(rangeIndex, mDescriptorCounts[CBV], &baseRegister,
-                               D3D12_DESCRIPTOR_RANGE_TYPE_CBV)) {
-            descriptorOffsets[CBV] = mRanges[rangeIndex++].BaseShaderRegister;
-        }
-        if (SetDescriptorRange(rangeIndex, mDescriptorCounts[UAV], &baseRegister,
-                               D3D12_DESCRIPTOR_RANGE_TYPE_UAV)) {
-            descriptorOffsets[UAV] = mRanges[rangeIndex++].BaseShaderRegister;
-        }
-        if (SetDescriptorRange(rangeIndex, mDescriptorCounts[SRV], &baseRegister,
-                               D3D12_DESCRIPTOR_RANGE_TYPE_SRV)) {
-            descriptorOffsets[SRV] = mRanges[rangeIndex++].BaseShaderRegister;
-        }
-        uint32_t zero = 0;
-        SetDescriptorRange(Sampler, mDescriptorCounts[Sampler], &zero,
-                           D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER);
-        descriptorOffsets[Sampler] = 0;
-
-        for (BindingIndex bindingIndex{0}; bindingIndex < GetBindingCount(); ++bindingIndex) {
-            const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
-
-            if (bindingInfo.bindingType == BindingInfoType::Buffer &&
-                bindingInfo.buffer.hasDynamicOffset) {
-                // Dawn is using values in mBindingOffsets to decide register number in HLSL.
-                // Root descriptor needs to set this value to set correct register number in
-                // generated HLSL shader.
-                mBindingOffsets[bindingIndex] = baseRegister++;
-                continue;
+            // Try to join this range with the previous one, if the current range is a continuation
+            // of the previous. This is possible because the binding infos in the base type are
+            // sorted.
+            if (descriptorRanges.size() >= 2) {
+                D3D12_DESCRIPTOR_RANGE& previous = descriptorRanges.back();
+                if (previous.RangeType == range.RangeType &&
+                    previous.BaseShaderRegister + previous.NumDescriptors ==
+                        range.BaseShaderRegister) {
+                    previous.NumDescriptors += range.NumDescriptors;
+                    continue;
+                }
             }
 
-            // TODO(dawn:728) In the future, special handling will be needed here for external
-            // textures because they encompass multiple views.
-            DescriptorType descriptorType = WGPUBindingInfoToDescriptorType(bindingInfo);
-            mBindingOffsets[bindingIndex] += descriptorOffsets[descriptorType];
+            descriptorRanges.push_back(range);
         }
 
         mViewAllocator = device->GetViewStagingDescriptorAllocator(GetCbvUavSrvDescriptorCount());
@@ -181,34 +161,29 @@
         mBindGroupAllocator.Deallocate(bindGroup);
     }
 
-    ityp::span<BindingIndex, const uint32_t> BindGroupLayout::GetBindingOffsets() const {
-        return {mBindingOffsets.data(), mBindingOffsets.size()};
+    ityp::span<BindingIndex, const uint32_t> BindGroupLayout::GetDescriptorHeapOffsets() const {
+        return {mDescriptorHeapOffsets.data(), mDescriptorHeapOffsets.size()};
     }
 
-    uint32_t BindGroupLayout::GetCbvUavSrvDescriptorTableSize() const {
-        return (static_cast<uint32_t>(mDescriptorCounts[CBV] > 0) +
-                static_cast<uint32_t>(mDescriptorCounts[UAV] > 0) +
-                static_cast<uint32_t>(mDescriptorCounts[SRV] > 0));
-    }
-
-    uint32_t BindGroupLayout::GetSamplerDescriptorTableSize() const {
-        return mDescriptorCounts[Sampler] > 0;
+    uint32_t BindGroupLayout::GetShaderRegister(BindingIndex bindingIndex) const {
+        return mShaderRegisters[bindingIndex];
     }
 
     uint32_t BindGroupLayout::GetCbvUavSrvDescriptorCount() const {
-        return mDescriptorCounts[CBV] + mDescriptorCounts[UAV] + mDescriptorCounts[SRV];
+        return mCbvUavSrvDescriptorCount;
     }
 
     uint32_t BindGroupLayout::GetSamplerDescriptorCount() const {
-        return mDescriptorCounts[Sampler];
+        return mSamplerDescriptorCount;
     }
 
-    const D3D12_DESCRIPTOR_RANGE* BindGroupLayout::GetCbvUavSrvDescriptorRanges() const {
-        return mRanges;
+    const std::vector<D3D12_DESCRIPTOR_RANGE>& BindGroupLayout::GetCbvUavSrvDescriptorRanges()
+        const {
+        return mCbvUavSrvDescriptorRanges;
     }
 
-    const D3D12_DESCRIPTOR_RANGE* BindGroupLayout::GetSamplerDescriptorRanges() const {
-        return &mRanges[Sampler];
+    const std::vector<D3D12_DESCRIPTOR_RANGE>& BindGroupLayout::GetSamplerDescriptorRanges() const {
+        return mSamplerDescriptorRanges;
     }
 
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.h b/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
index 5035666..eb7b313 100644
--- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
+++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
@@ -28,6 +28,13 @@
     class Device;
     class StagingDescriptorAllocator;
 
+    // A purposefully invalid register space.
+    //
+    // We use the bind group index as the register space, but don't know the bind group index until
+    // pipeline layout creation time. This value should be replaced in PipelineLayoutD3D12.
+    static constexpr uint32_t kRegisterSpacePlaceholder =
+        D3D12_DRIVER_RESERVED_REGISTER_SPACE_VALUES_START;
+
     class BindGroupLayout final : public BindGroupLayoutBase {
       public:
         static Ref<BindGroupLayout> Create(Device* device,
@@ -37,28 +44,41 @@
                                                         const BindGroupDescriptor* descriptor);
         void DeallocateBindGroup(BindGroup* bindGroup, CPUDescriptorHeapAllocation* viewAllocation);
 
-        enum DescriptorType {
-            CBV,
-            UAV,
-            SRV,
-            Sampler,
-            Count,
-        };
+        // The offset (in descriptor count) into the corresponding descriptor heap. Not valid for
+        // dynamic binding indexes.
+        ityp::span<BindingIndex, const uint32_t> GetDescriptorHeapOffsets() const;
 
-        ityp::span<BindingIndex, const uint32_t> GetBindingOffsets() const;
-        uint32_t GetCbvUavSrvDescriptorTableSize() const;
-        uint32_t GetSamplerDescriptorTableSize() const;
+        // The D3D shader register that the Dawn binding index is mapped to by this bind group
+        // layout.
+        uint32_t GetShaderRegister(BindingIndex bindingIndex) const;
+
+        // Counts of descriptors in the descriptor tables.
         uint32_t GetCbvUavSrvDescriptorCount() const;
         uint32_t GetSamplerDescriptorCount() const;
-        const D3D12_DESCRIPTOR_RANGE* GetCbvUavSrvDescriptorRanges() const;
-        const D3D12_DESCRIPTOR_RANGE* GetSamplerDescriptorRanges() const;
+
+        const std::vector<D3D12_DESCRIPTOR_RANGE>& GetCbvUavSrvDescriptorRanges() const;
+        const std::vector<D3D12_DESCRIPTOR_RANGE>& GetSamplerDescriptorRanges() const;
 
       private:
         BindGroupLayout(Device* device, const BindGroupLayoutDescriptor* descriptor);
         ~BindGroupLayout() override = default;
-        ityp::stack_vec<BindingIndex, uint32_t, kMaxOptimalBindingsPerGroup> mBindingOffsets;
-        std::array<uint32_t, DescriptorType::Count> mDescriptorCounts;
-        D3D12_DESCRIPTOR_RANGE mRanges[DescriptorType::Count];
+
+        // Contains the offset into the descriptor heap for the given resource view. Samplers and
+        // non-samplers are stored in separate descriptor heaps, so the offsets should be unique
+        // within each group and tightly packed.
+        //
+        // Dynamic resources are not used here since their descriptors are placed directly in root
+        // parameters.
+        ityp::stack_vec<BindingIndex, uint32_t, kMaxOptimalBindingsPerGroup> mDescriptorHeapOffsets;
+
+        // Contains the shader register this binding is mapped to.
+        ityp::stack_vec<BindingIndex, uint32_t, kMaxOptimalBindingsPerGroup> mShaderRegisters;
+
+        uint32_t mCbvUavSrvDescriptorCount;
+        uint32_t mSamplerDescriptorCount;
+
+        std::vector<D3D12_DESCRIPTOR_RANGE> mCbvUavSrvDescriptorRanges;
+        std::vector<D3D12_DESCRIPTOR_RANGE> mSamplerDescriptorRanges;
 
         SlabAllocator<BindGroup> mBindGroupAllocator;
 
diff --git a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
index f239fe5..448ec22 100644
--- a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
+++ b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
@@ -70,9 +70,15 @@
         // descriptor.
         std::vector<D3D12_ROOT_PARAMETER> rootParameters;
 
-        // Ranges are D3D12_DESCRIPTOR_RANGE_TYPE_(SRV|UAV|CBV|SAMPLER)
-        // They are grouped together so each bind group has at most 4 ranges
-        D3D12_DESCRIPTOR_RANGE ranges[kMaxBindGroups * 4];
+        size_t rangesCount = 0;
+        for (BindGroupIndex group : IterateBitSet(GetBindGroupLayoutsMask())) {
+            const BindGroupLayout* bindGroupLayout = ToBackend(GetBindGroupLayout(group));
+            rangesCount += bindGroupLayout->GetCbvUavSrvDescriptorRanges().size() +
+                           bindGroupLayout->GetSamplerDescriptorRanges().size();
+        }
+
+        // We are taking pointers to `ranges`, so we cannot let it resize while we're pushing to it.
+        std::vector<D3D12_DESCRIPTOR_RANGE> ranges(rangesCount);
 
         uint32_t rangeIndex = 0;
 
@@ -83,7 +89,8 @@
             // bind group index Returns whether or not the parameter was set. A root parameter is
             // not set if the number of ranges is 0
             auto SetRootDescriptorTable =
-                [&](uint32_t rangeCount, const D3D12_DESCRIPTOR_RANGE* descriptorRanges) -> bool {
+                [&](const std::vector<D3D12_DESCRIPTOR_RANGE>& descriptorRanges) -> bool {
+                auto rangeCount = descriptorRanges.size();
                 if (rangeCount == 0) {
                     return false;
                 }
@@ -94,8 +101,9 @@
                 rootParameter.DescriptorTable.NumDescriptorRanges = rangeCount;
                 rootParameter.DescriptorTable.pDescriptorRanges = &ranges[rangeIndex];
 
-                for (uint32_t i = 0; i < rangeCount; ++i) {
-                    ranges[rangeIndex] = descriptorRanges[i];
+                for (auto& range : descriptorRanges) {
+                    ASSERT(range.RegisterSpace == kRegisterSpacePlaceholder);
+                    ranges[rangeIndex] = range;
                     ranges[rangeIndex].RegisterSpace = static_cast<uint32_t>(group);
                     rangeIndex++;
                 }
@@ -105,19 +113,13 @@
                 return true;
             };
 
-            if (SetRootDescriptorTable(bindGroupLayout->GetCbvUavSrvDescriptorTableSize(),
-                                       bindGroupLayout->GetCbvUavSrvDescriptorRanges())) {
+            if (SetRootDescriptorTable(bindGroupLayout->GetCbvUavSrvDescriptorRanges())) {
                 mCbvUavSrvRootParameterInfo[group] = rootParameters.size() - 1;
             }
-
-            if (SetRootDescriptorTable(bindGroupLayout->GetSamplerDescriptorTableSize(),
-                                       bindGroupLayout->GetSamplerDescriptorRanges())) {
+            if (SetRootDescriptorTable(bindGroupLayout->GetSamplerDescriptorRanges())) {
                 mSamplerRootParameterInfo[group] = rootParameters.size() - 1;
             }
 
-            // Get calculated shader register for root descriptors
-            const auto& shaderRegisters = bindGroupLayout->GetBindingOffsets();
-
             // Init root descriptors in root signatures for dynamic buffer bindings.
             // These are packed at the beginning of the layout binding info.
             for (BindingIndex dynamicBindingIndex{0};
@@ -136,7 +138,8 @@
 
                 // Setup root descriptor.
                 D3D12_ROOT_DESCRIPTOR rootDescriptor;
-                rootDescriptor.ShaderRegister = shaderRegisters[dynamicBindingIndex];
+                rootDescriptor.ShaderRegister =
+                    bindGroupLayout->GetShaderRegister(dynamicBindingIndex);
                 rootDescriptor.RegisterSpace = static_cast<uint32_t>(group);
 
                 // Set root descriptors in root signatures.
@@ -153,15 +156,21 @@
             }
         }
 
+        // Make sure that we added exactly the number of elements we expected. If we added more,
+        // |ranges| will have resized and the pointers in the |rootParameter|s will be invalid.
+        ASSERT(rangeIndex == rangesCount);
+
         // Since Tint's HLSL writer doesn't currently map sets to spaces, we use the default space
         // (0).
         mFirstIndexOffsetRegisterSpace = 0;
         BindGroupIndex firstOffsetGroup{mFirstIndexOffsetRegisterSpace};
         if (GetBindGroupLayoutsMask()[firstOffsetGroup]) {
             // Find the last register used on firstOffsetGroup.
+            auto bgl = ToBackend(GetBindGroupLayout(firstOffsetGroup));
             uint32_t maxRegister = 0;
-            for (uint32_t shaderRegister :
-                 ToBackend(GetBindGroupLayout(firstOffsetGroup))->GetBindingOffsets()) {
+            for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBindingCount();
+                 ++bindingIndex) {
+                uint32_t shaderRegister = bgl->GetShaderRegister(bindingIndex);
                 if (shaderRegister > maxRegister) {
                     maxRegister = shaderRegister;
                 }
diff --git a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
index eac2a4f..2ecb5fc 100644
--- a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
@@ -203,16 +203,15 @@
         // with the correct registers assigned to each interface variable.
         for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
             const BindGroupLayout* bgl = ToBackend(layout->GetBindGroupLayout(group));
-            const auto& bindingOffsets = bgl->GetBindingOffsets();
             const auto& groupBindingInfo = moduleBindingInfo[group];
             for (const auto& it : groupBindingInfo) {
                 BindingNumber binding = it.first;
                 auto const& bindingInfo = it.second;
                 BindingIndex bindingIndex = bgl->GetBindingIndex(binding);
-                uint32_t bindingOffset = bindingOffsets[bindingIndex];
                 BindingPoint srcBindingPoint{static_cast<uint32_t>(group),
                                              static_cast<uint32_t>(binding)};
-                BindingPoint dstBindingPoint{static_cast<uint32_t>(group), bindingOffset};
+                BindingPoint dstBindingPoint{static_cast<uint32_t>(group),
+                                             bgl->GetShaderRegister(bindingIndex)};
                 if (srcBindingPoint != dstBindingPoint) {
                     bindingPoints.emplace(srcBindingPoint, dstBindingPoint);
                 }
diff --git a/src/tests/end2end/BindGroupTests.cpp b/src/tests/end2end/BindGroupTests.cpp
index 90ecbed..81eeb50 100644
--- a/src/tests/end2end/BindGroupTests.cpp
+++ b/src/tests/end2end/BindGroupTests.cpp
@@ -945,6 +945,92 @@
     EXPECT_BUFFER_U32_RANGE_EQ(values.data(), outputBuffer, 0, values.size());
 }
 
+// Test that ensures that backends do not remap bindings such that dynamic and non-dynamic bindings
+// conflict. This can happen if the backend treats dynamic bindings separately from non-dynamic
+// bindings.
+TEST_P(BindGroupTests, DynamicAndNonDynamicBindingsDoNotConflictAfterRemapping) {
+    auto RunTestWith = [&](bool dynamicBufferFirst) {
+        uint32_t dynamicBufferBindingNumber = dynamicBufferFirst ? 0 : 1;
+        uint32_t bufferBindingNumber = dynamicBufferFirst ? 1 : 0;
+
+        std::array<uint32_t, 1> offsets{kMinUniformBufferOffsetAlignment};
+        std::array<uint32_t, 2> values = {21, 67};
+
+        // Create three buffers large enough to by offset by the largest offset.
+        wgpu::BufferDescriptor bufferDescriptor;
+        bufferDescriptor.size = 2 * kMinUniformBufferOffsetAlignment + sizeof(uint32_t);
+        bufferDescriptor.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
+
+        wgpu::Buffer dynamicBuffer = device.CreateBuffer(&bufferDescriptor);
+        wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
+
+        // Populate the values
+        queue.WriteBuffer(dynamicBuffer, kMinUniformBufferOffsetAlignment,
+                          &values[dynamicBufferBindingNumber], sizeof(uint32_t));
+        queue.WriteBuffer(buffer, 0, &values[bufferBindingNumber], sizeof(uint32_t));
+
+        wgpu::Buffer outputBuffer = utils::CreateBufferFromData(
+            device, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::Storage, {0, 0});
+
+        // Create a bind group layout which uses a single dynamic uniform buffer.
+        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
+            device,
+            {
+                {dynamicBufferBindingNumber, wgpu::ShaderStage::Compute,
+                 wgpu::BufferBindingType::Uniform, true},
+                {bufferBindingNumber, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform},
+                {2, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage},
+            });
+
+        wgpu::BindGroup bindGroup = utils::MakeBindGroup(
+            device, bgl,
+            {
+                {dynamicBufferBindingNumber, dynamicBuffer, 0, sizeof(uint32_t)},
+                {bufferBindingNumber, buffer, 0, sizeof(uint32_t)},
+                {2, outputBuffer, 0, 2 * sizeof(uint32_t)},
+            });
+
+        wgpu::ComputePipelineDescriptor pipelineDescriptor;
+        pipelineDescriptor.compute.module = utils::CreateShaderModule(device, R"(
+        [[block]] struct Buffer {
+            value : u32;
+        };
+
+        [[block]] struct OutputBuffer {
+            value : vec2<u32>;
+        };
+
+        [[group(0), binding(0)]] var<uniform> buffer0 : Buffer;
+        [[group(0), binding(1)]] var<uniform> buffer1 : Buffer;
+        [[group(0), binding(2)]] var<storage, read_write> outputBuffer : OutputBuffer;
+
+        [[stage(compute), workgroup_size(1)]] fn main() {
+            outputBuffer.value = vec2<u32>(buffer0.value, buffer1.value);
+        })");
+        pipelineDescriptor.compute.entryPoint = "main";
+        pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl);
+        wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDescriptor);
+
+        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
+        computePassEncoder.SetPipeline(pipeline);
+        computePassEncoder.SetBindGroup(0, bindGroup, offsets.size(), offsets.data());
+        computePassEncoder.Dispatch(1);
+        computePassEncoder.EndPass();
+
+        wgpu::CommandBuffer commands = commandEncoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER_U32_RANGE_EQ(values.data(), outputBuffer, 0, values.size());
+    };
+
+    // Run the test with the dynamic buffer in index 0 and with the non-dynamic buffer in index 1,
+    // and vice versa. This should cause a conflict at index 0, if the binding remapping is too
+    // aggressive.
+    RunTestWith(true);
+    RunTestWith(false);
+}
+
 // Test that visibility of bindings in BindGroupLayout can be none
 // This test passes by not asserting or crashing.
 TEST_P(BindGroupTests, BindGroupLayoutVisibilityCanBeNone) {