d3d11: enable DynamicBufferOffsetTests

This binds UAV slots all together at the same time.

Bug: dawn:1816
Bug: dawn:1705

Change-Id: Iea7af9cf00caf126a3964cc43bbf2162b35f80e0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/132600
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Jie A Chen <jie.a.chen@intel.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 011242e..8d306fd 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -266,6 +266,7 @@
     mState = State::Alive;
 
     DAWN_TRY_ASSIGN(mEmptyBindGroupLayout, CreateEmptyBindGroupLayout());
+    DAWN_TRY_ASSIGN(mEmptyPipelineLayout, CreateEmptyPipelineLayout());
 
     // If placeholder fragment shader module is needed, initialize it
     if (IsToggleEnabled(Toggle::UsePlaceholderFragmentInVertexOnlyPipeline)) {
@@ -481,6 +482,7 @@
     // Destroy() via APIGetQueue.
     mDynamicUploader = nullptr;
     mEmptyBindGroupLayout = nullptr;
+    mEmptyPipelineLayout = nullptr;
     mInternalPipelineStore = nullptr;
     mExternalTexturePlaceholderView = nullptr;
 
@@ -850,11 +852,24 @@
     return GetOrCreateBindGroupLayout(&desc);
 }
 
+ResultOrError<Ref<PipelineLayoutBase>> DeviceBase::CreateEmptyPipelineLayout() {
+    PipelineLayoutDescriptor desc = {};
+    desc.bindGroupLayoutCount = 0;
+    desc.bindGroupLayouts = nullptr;
+
+    return GetOrCreatePipelineLayout(&desc);
+}
+
 BindGroupLayoutBase* DeviceBase::GetEmptyBindGroupLayout() {
     ASSERT(mEmptyBindGroupLayout != nullptr);
     return mEmptyBindGroupLayout.Get();
 }
 
+PipelineLayoutBase* DeviceBase::GetEmptyPipelineLayout() {
+    ASSERT(mEmptyPipelineLayout != nullptr);
+    return mEmptyPipelineLayout.Get();
+}
+
 Ref<ComputePipelineBase> DeviceBase::GetCachedComputePipeline(
     ComputePipelineBase* uninitializedComputePipeline) {
     Ref<ComputePipelineBase> cachedPipeline;
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index e7c8bb5..3643566 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -197,6 +197,7 @@
     void UncacheBindGroupLayout(BindGroupLayoutBase* obj);
 
     BindGroupLayoutBase* GetEmptyBindGroupLayout();
+    PipelineLayoutBase* GetEmptyPipelineLayout();
 
     void UncacheComputePipeline(ComputePipelineBase* obj);
 
@@ -506,6 +507,7 @@
     void FlushCallbackTaskQueue();
 
     ResultOrError<Ref<BindGroupLayoutBase>> CreateEmptyBindGroupLayout();
+    ResultOrError<Ref<PipelineLayoutBase>> CreateEmptyPipelineLayout();
 
     Ref<ComputePipelineBase> GetCachedComputePipeline(
         ComputePipelineBase* uninitializedComputePipeline);
@@ -588,6 +590,7 @@
     std::unique_ptr<Caches> mCaches;
 
     Ref<BindGroupLayoutBase> mEmptyBindGroupLayout;
+    Ref<PipelineLayoutBase> mEmptyPipelineLayout;
 
     Ref<TextureViewBase> mExternalTexturePlaceholderView;
 
diff --git a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
index d7be5c0..879e5ae 100644
--- a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
+++ b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
@@ -14,6 +14,9 @@
 
 #include "dawn/native/d3d11/BindGroupTrackerD3D11.h"
 
+#include <utility>
+#include <vector>
+
 #include "dawn/common/Assert.h"
 #include "dawn/native/Format.h"
 #include "dawn/native/d3d/D3DError.h"
@@ -68,23 +71,64 @@
         ASSERT(sampler == nullptr);
     }
 
-    // Check UAV slots
+    // Check UAV slots for compute
     for (UINT slot = 0; slot < D3D11_1_UAV_SLOT_COUNT; ++slot) {
         ID3D11UnorderedAccessView* uav = nullptr;
         deviceContext1->CSGetUnorderedAccessViews(slot, 1, &uav);
         ASSERT(uav == nullptr);
     }
+    // Check UAV slots for render
+    for (UINT slot = 0; slot < commandContext->GetDevice()->GetUAVSlotCount(); ++slot) {
+        ID3D11UnorderedAccessView* uav = nullptr;
+        deviceContext1->OMGetRenderTargetsAndUnorderedAccessViews(0, nullptr, nullptr, slot, 1,
+                                                                  &uav);
+        ASSERT(uav == nullptr);
+    }
 
     return true;
 }
 
+void ResetAllRenderSlots(CommandRecordingContext* commandContext) {
+    ID3D11DeviceContext1* deviceContext1 = commandContext->GetD3D11DeviceContext1();
+
+    // Reserve one slot for builtin constants.
+    constexpr uint32_t kReservedCBVSlots = 1;
+
+    ID3D11Buffer* d3d11Buffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] = {};
+    uint32_t num = D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT - kReservedCBVSlots;
+    deviceContext1->VSSetConstantBuffers1(0, num, d3d11Buffers, nullptr, nullptr);
+    deviceContext1->PSSetConstantBuffers1(0, num, d3d11Buffers, nullptr, nullptr);
+
+    ID3D11ShaderResourceView* d3d11SRVs[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
+    num = D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
+    deviceContext1->VSSetShaderResources(0, num, d3d11SRVs);
+    deviceContext1->PSSetShaderResources(0, num, d3d11SRVs);
+
+    ID3D11SamplerState* d3d11Samplers[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT] = {};
+    num = D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
+    deviceContext1->VSSetSamplers(0, num, d3d11Samplers);
+    deviceContext1->PSSetSamplers(0, num, d3d11Samplers);
+
+    ID3D11UnorderedAccessView* d3d11UAVs[D3D11_1_UAV_SLOT_COUNT] = {};
+    num = commandContext->GetDevice()->GetUAVSlotCount();
+    deviceContext1->OMSetRenderTargetsAndUnorderedAccessViews(
+        D3D11_KEEP_RENDER_TARGETS_AND_DEPTH_STENCIL, nullptr, nullptr, 0, num, d3d11UAVs, nullptr);
+}
+
 }  // namespace
 
-BindGroupTracker::BindGroupTracker(CommandRecordingContext* commandContext)
-    : mCommandContext(commandContext) {}
+BindGroupTracker::BindGroupTracker(CommandRecordingContext* commandContext, bool isRenderPass)
+    : mCommandContext(commandContext),
+      mIsRenderPass(isRenderPass),
+      mVisibleStages(isRenderPass ? wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment
+                                  : wgpu::ShaderStage::Compute) {
+    mLastAppliedPipelineLayout = commandContext->GetDevice()->GetEmptyPipelineLayout();
+}
 
 BindGroupTracker::~BindGroupTracker() {
-    if (mLastAppliedPipelineLayout) {
+    if (mIsRenderPass) {
+        ResetAllRenderSlots(mCommandContext);
+    } else {
         for (BindGroupIndex index :
              IterateBitSet(mLastAppliedPipelineLayout->GetBindGroupLayoutsMask())) {
             UnApplyBindGroup(index);
@@ -97,15 +141,93 @@
 MaybeError BindGroupTracker::Apply() {
     BeforeApply();
 
-    // A resource cannot be bound as both input resource and UAV at the same time, so to avoid this
-    // conflict, we need to unbind groups which are not used by the new pipeline. and unbind groups
-    // which are not inherited by the new pipeline.
-    if (mLastAppliedPipelineLayout) {
-        BindGroupLayoutMask lastGroups = mLastAppliedPipelineLayout->GetBindGroupLayoutsMask();
-        BindGroupLayoutMask currentGroups = mPipelineLayout->GetBindGroupLayoutsMask();
-        BindGroupLayoutMask unApplyGroups = (mDirtyBindGroups | ~currentGroups) & lastGroups;
-        // Unset bind groups which are not used by the new pipeline and are not inherited.
-        for (BindGroupIndex index : IterateBitSet(unApplyGroups)) {
+    if (mIsRenderPass) {
+        // As D3d11 requires to bind all UAVs slots at the same time for pixel shaders, we record
+        // all UAV slot assignments in the bind groups, and then bind them all together.
+        const BindGroupLayoutMask uavBindGroups =
+            ToBackend(mPipelineLayout)->GetUAVBindGroupLayoutsMask();
+        std::vector<ComPtr<ID3D11UnorderedAccessView>> d3d11UAVs;
+        for (BindGroupIndex index : IterateBitSet(uavBindGroups)) {
+            BindGroupBase* group = mBindGroups[index];
+            const ityp::vector<BindingIndex, uint64_t>& dynamicOffsets = mDynamicOffsets[index];
+
+            for (BindingIndex bindingIndex{0}; bindingIndex < group->GetLayout()->GetBindingCount();
+                 ++bindingIndex) {
+                const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
+
+                switch (bindingInfo.bindingType) {
+                    case BindingInfoType::Buffer: {
+                        BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex);
+                        auto offset = binding.offset;
+                        if (bindingInfo.buffer.hasDynamicOffset) {
+                            // Dynamic buffers are packed at the front of BindingIndices.
+                            offset += dynamicOffsets[bindingIndex];
+                        }
+
+                        switch (bindingInfo.buffer.type) {
+                            case wgpu::BufferBindingType::Storage:
+                            case kInternalStorageBufferBinding: {
+                                ASSERT(IsSubset(
+                                    bindingInfo.visibility,
+                                    wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute));
+                                ComPtr<ID3D11UnorderedAccessView> d3d11UAV;
+                                DAWN_TRY_ASSIGN(d3d11UAV, ToBackend(binding.buffer)
+                                                              ->CreateD3D11UnorderedAccessView1(
+                                                                  offset, binding.size));
+                                ToBackend(binding.buffer)->MarkMutated();
+                                d3d11UAVs.insert(d3d11UAVs.begin(), std::move(d3d11UAV));
+                                break;
+                            }
+                            case wgpu::BufferBindingType::Uniform:
+                            case wgpu::BufferBindingType::ReadOnlyStorage:
+                            case wgpu::BufferBindingType::Undefined: {
+                                break;
+                            }
+                        }
+                        break;
+                    }
+
+                    case BindingInfoType::StorageTexture: {
+                        ASSERT(bindingInfo.storageTexture.access ==
+                               wgpu::StorageTextureAccess::WriteOnly);
+                        ComPtr<ID3D11UnorderedAccessView> d3d11UAV;
+                        TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
+                        DAWN_TRY_ASSIGN(d3d11UAV, view->CreateD3D11UnorderedAccessView());
+                        d3d11UAVs.insert(d3d11UAVs.begin(), std::move(d3d11UAV));
+                        break;
+                    }
+                    case BindingInfoType::Texture:
+                    case BindingInfoType::ExternalTexture:
+                    case BindingInfoType::Sampler: {
+                        break;
+                    }
+                }
+            }
+        }
+        uint32_t uavSlotCount = ToBackend(mPipelineLayout->GetDevice())->GetUAVSlotCount();
+        std::vector<ID3D11UnorderedAccessView*> views;
+        for (auto& uav : d3d11UAVs) {
+            views.push_back(uav.Get());
+        }
+        mCommandContext->GetD3D11DeviceContext1()->OMSetRenderTargetsAndUnorderedAccessViews(
+            D3D11_KEEP_RENDER_TARGETS_AND_DEPTH_STENCIL, nullptr, nullptr,
+            uavSlotCount - d3d11UAVs.size(), d3d11UAVs.size(), views.data(), nullptr);
+        d3d11UAVs.clear();
+    } else {
+        BindGroupLayoutMask inheritedGroups =
+            mPipelineLayout->InheritedGroupsMask(mLastAppliedPipelineLayout);
+        BindGroupLayoutMask previousGroups = mLastAppliedPipelineLayout->GetBindGroupLayoutsMask();
+
+        // To avoid UAV / SRV conflicts with bindings in previously bind groups, we unset the bind
+        // groups that aren't reused by the current pipeline.
+        // We also need to unset the inherited bind groups which are dirty as the group may have
+        // both SRV and UAV, and the same resource may change its binding from UAV to SRV next
+        // dispatch in the same group.
+        //
+        // Note: WebGPU API guarantees that resources are not used both as UAV and SRV in the same
+        // render pass. So we don't need to do this inside render passes.
+        BindGroupLayoutMask groupsToUnset = previousGroups & (~inheritedGroups | mDirtyBindGroups);
+        for (BindGroupIndex index : IterateBitSet(groupsToUnset)) {
             UnApplyBindGroup(index);
         }
     }
@@ -129,6 +251,7 @@
          ++bindingIndex) {
         const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex);
         const uint32_t bindingSlot = indices[bindingIndex];
+        const auto bindingVisibility = bindingInfo.visibility & mVisibleStages;
 
         switch (bindingInfo.bindingType) {
             case BindingInfoType::Buffer: {
@@ -156,15 +279,15 @@
                         DAWN_ASSERT(offset + numConstants * 16 <=
                                     binding.buffer->GetAllocatedSize());
 
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                        if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                             deviceContext1->VSSetConstantBuffers1(bindingSlot, 1, &d3d11Buffer,
                                                                   &firstConstant, &numConstants);
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                        if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                             deviceContext1->PSSetConstantBuffers1(bindingSlot, 1, &d3d11Buffer,
                                                                   &firstConstant, &numConstants);
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                        if (bindingVisibility & wgpu::ShaderStage::Compute) {
                             deviceContext1->CSSetConstantBuffers1(bindingSlot, 1, &d3d11Buffer,
                                                                   &firstConstant, &numConstants);
                         }
@@ -174,17 +297,12 @@
                     case kInternalStorageBufferBinding: {
                         ASSERT(IsSubset(bindingInfo.visibility,
                                         wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute));
-                        ComPtr<ID3D11UnorderedAccessView> d3d11UAV;
-                        DAWN_TRY_ASSIGN(
-                            d3d11UAV, ToBackend(binding.buffer)
-                                          ->CreateD3D11UnorderedAccessView1(offset, binding.size));
-                        ToBackend(binding.buffer)->MarkMutated();
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
-                            deviceContext1->OMSetRenderTargetsAndUnorderedAccessViews(
-                                D3D11_KEEP_RENDER_TARGETS_AND_DEPTH_STENCIL, nullptr, nullptr,
-                                bindingSlot, 1, d3d11UAV.GetAddressOf(), nullptr);
-                        }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                        if (bindingVisibility & wgpu::ShaderStage::Compute) {
+                            ComPtr<ID3D11UnorderedAccessView> d3d11UAV;
+                            DAWN_TRY_ASSIGN(d3d11UAV, ToBackend(binding.buffer)
+                                                          ->CreateD3D11UnorderedAccessView1(
+                                                              offset, binding.size));
+                            ToBackend(binding.buffer)->MarkMutated();
                             deviceContext1->CSSetUnorderedAccessViews(
                                 bindingSlot, 1, d3d11UAV.GetAddressOf(), nullptr);
                         }
@@ -195,15 +313,15 @@
                         DAWN_TRY_ASSIGN(d3d11SRV,
                                         ToBackend(binding.buffer)
                                             ->CreateD3D11ShaderResourceView(offset, binding.size));
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                        if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                             deviceContext1->VSSetShaderResources(bindingSlot, 1,
                                                                  d3d11SRV.GetAddressOf());
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                        if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                             deviceContext1->PSSetShaderResources(bindingSlot, 1,
                                                                  d3d11SRV.GetAddressOf());
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                        if (bindingVisibility & wgpu::ShaderStage::Compute) {
                             deviceContext1->CSSetShaderResources(bindingSlot, 1,
                                                                  d3d11SRV.GetAddressOf());
                         }
@@ -218,13 +336,13 @@
             case BindingInfoType::Sampler: {
                 Sampler* sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
                 ID3D11SamplerState* d3d11SamplerState = sampler->GetD3D11SamplerState();
-                if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                     deviceContext1->VSSetSamplers(bindingSlot, 1, &d3d11SamplerState);
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                     deviceContext1->PSSetSamplers(bindingSlot, 1, &d3d11SamplerState);
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext1->CSSetSamplers(bindingSlot, 1, &d3d11SamplerState);
                 }
                 break;
@@ -234,13 +352,13 @@
                 TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                 ComPtr<ID3D11ShaderResourceView> srv;
                 DAWN_TRY_ASSIGN(srv, view->CreateD3D11ShaderResourceView());
-                if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                     deviceContext1->VSSetShaderResources(bindingSlot, 1, srv.GetAddressOf());
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                     deviceContext1->PSSetShaderResources(bindingSlot, 1, srv.GetAddressOf());
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext1->CSSetShaderResources(bindingSlot, 1, srv.GetAddressOf());
                 }
                 break;
@@ -248,15 +366,10 @@
 
             case BindingInfoType::StorageTexture: {
                 ASSERT(bindingInfo.storageTexture.access == wgpu::StorageTextureAccess::WriteOnly);
-                ComPtr<ID3D11UnorderedAccessView> d3d11UAV;
-                TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
-                DAWN_TRY_ASSIGN(d3d11UAV, view->CreateD3D11UnorderedAccessView());
-                if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
-                    deviceContext1->OMSetRenderTargetsAndUnorderedAccessViews(
-                        D3D11_KEEP_RENDER_TARGETS_AND_DEPTH_STENCIL, nullptr, nullptr, bindingSlot,
-                        1, d3d11UAV.GetAddressOf(), nullptr);
-                }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                if (bindingVisibility & wgpu::ShaderStage::Compute) {
+                    ComPtr<ID3D11UnorderedAccessView> d3d11UAV;
+                    TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
+                    DAWN_TRY_ASSIGN(d3d11UAV, view->CreateD3D11UnorderedAccessView());
                     deviceContext1->CSSetUnorderedAccessViews(bindingSlot, 1,
                                                               d3d11UAV.GetAddressOf(), nullptr);
                 }
@@ -280,21 +393,22 @@
          ++bindingIndex) {
         const BindingInfo& bindingInfo = groupLayout->GetBindingInfo(bindingIndex);
         const uint32_t bindingSlot = indices[bindingIndex];
+        const auto bindingVisibility = bindingInfo.visibility & mVisibleStages;
 
         switch (bindingInfo.bindingType) {
             case BindingInfoType::Buffer: {
                 switch (bindingInfo.buffer.type) {
                     case wgpu::BufferBindingType::Uniform: {
                         ID3D11Buffer* nullBuffer = nullptr;
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                        if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                             deviceContext1->VSSetConstantBuffers1(bindingSlot, 1, &nullBuffer,
                                                                   nullptr, nullptr);
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                        if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                             deviceContext1->PSSetConstantBuffers1(bindingSlot, 1, &nullBuffer,
                                                                   nullptr, nullptr);
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                        if (bindingVisibility & wgpu::ShaderStage::Compute) {
                             deviceContext1->CSSetConstantBuffers1(bindingSlot, 1, &nullBuffer,
                                                                   nullptr, nullptr);
                         }
@@ -305,12 +419,12 @@
                         ASSERT(IsSubset(bindingInfo.visibility,
                                         wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute));
                         ID3D11UnorderedAccessView* nullUAV = nullptr;
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                        if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                             deviceContext1->OMSetRenderTargetsAndUnorderedAccessViews(
                                 D3D11_KEEP_RENDER_TARGETS_AND_DEPTH_STENCIL, nullptr, nullptr,
                                 bindingSlot, 1, &nullUAV, nullptr);
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                        if (bindingVisibility & wgpu::ShaderStage::Compute) {
                             deviceContext1->CSSetUnorderedAccessViews(bindingSlot, 1, &nullUAV,
                                                                       nullptr);
                         }
@@ -318,13 +432,13 @@
                     }
                     case wgpu::BufferBindingType::ReadOnlyStorage: {
                         ID3D11ShaderResourceView* nullSRV = nullptr;
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                        if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                             deviceContext1->VSSetShaderResources(bindingSlot, 1, &nullSRV);
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                        if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                             deviceContext1->PSSetShaderResources(bindingSlot, 1, &nullSRV);
                         }
-                        if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                        if (bindingVisibility & wgpu::ShaderStage::Compute) {
                             deviceContext1->CSSetShaderResources(bindingSlot, 1, &nullSRV);
                         }
                         break;
@@ -337,13 +451,13 @@
 
             case BindingInfoType::Sampler: {
                 ID3D11SamplerState* nullSampler = nullptr;
-                if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                     deviceContext1->VSSetSamplers(bindingSlot, 1, &nullSampler);
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                     deviceContext1->PSSetSamplers(bindingSlot, 1, &nullSampler);
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext1->CSSetSamplers(bindingSlot, 1, &nullSampler);
                 }
                 break;
@@ -351,13 +465,13 @@
 
             case BindingInfoType::Texture: {
                 ID3D11ShaderResourceView* nullSRV = nullptr;
-                if (bindingInfo.visibility & wgpu::ShaderStage::Vertex) {
+                if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                     deviceContext1->VSSetShaderResources(bindingSlot, 1, &nullSRV);
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                     deviceContext1->PSSetShaderResources(bindingSlot, 1, &nullSRV);
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext1->CSSetShaderResources(bindingSlot, 1, &nullSRV);
                 }
                 break;
@@ -366,12 +480,12 @@
             case BindingInfoType::StorageTexture: {
                 ASSERT(bindingInfo.storageTexture.access == wgpu::StorageTextureAccess::WriteOnly);
                 ID3D11UnorderedAccessView* nullUAV = nullptr;
-                if (bindingInfo.visibility & wgpu::ShaderStage::Fragment) {
+                if (bindingVisibility & wgpu::ShaderStage::Fragment) {
                     deviceContext1->OMSetRenderTargetsAndUnorderedAccessViews(
                         D3D11_KEEP_RENDER_TARGETS_AND_DEPTH_STENCIL, nullptr, nullptr, bindingSlot,
                         1, &nullUAV, nullptr);
                 }
-                if (bindingInfo.visibility & wgpu::ShaderStage::Compute) {
+                if (bindingVisibility & wgpu::ShaderStage::Compute) {
                     deviceContext1->CSSetUnorderedAccessViews(bindingSlot, 1, &nullUAV, nullptr);
                 }
                 break;
diff --git a/src/dawn/native/d3d11/BindGroupTrackerD3D11.h b/src/dawn/native/d3d11/BindGroupTrackerD3D11.h
index 7e8181b..719249d 100644
--- a/src/dawn/native/d3d11/BindGroupTrackerD3D11.h
+++ b/src/dawn/native/d3d11/BindGroupTrackerD3D11.h
@@ -24,9 +24,9 @@
 
 // We need convert WebGPU bind slot to d3d11 bind slot according a map in PipelineLayout, so we
 // cannot inherit BindGroupTrackerGroups.
-class BindGroupTracker : public BindGroupTrackerBase</*CanInheritBindGroups=*/false, uint64_t> {
+class BindGroupTracker : public BindGroupTrackerBase</*CanInheritBindGroups=*/true, uint64_t> {
   public:
-    explicit BindGroupTracker(CommandRecordingContext* commandContext);
+    BindGroupTracker(CommandRecordingContext* commandContext, bool isRenderPass);
     ~BindGroupTracker();
     MaybeError Apply();
 
@@ -35,6 +35,8 @@
     void UnApplyBindGroup(BindGroupIndex index);
 
     CommandRecordingContext* const mCommandContext;
+    const bool mIsRenderPass;
+    const wgpu::ShaderStage mVisibleStages;
 };
 
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/CommandBufferD3D11.cpp b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
index 82e87cf..ad21732 100644
--- a/src/dawn/native/d3d11/CommandBufferD3D11.cpp
+++ b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
@@ -322,7 +322,7 @@
 
 MaybeError CommandBuffer::ExecuteComputePass(CommandRecordingContext* commandContext) {
     ComputePipeline* lastPipeline = nullptr;
-    BindGroupTracker bindGroupTracker(commandContext);
+    BindGroupTracker bindGroupTracker(commandContext, /*isRenderPass=*/false);
 
     Command type;
     while (mCommands.NextCommandId(&type)) {
@@ -481,7 +481,7 @@
     d3d11DeviceContext1->RSSetScissorRects(1, &scissor);
 
     RenderPipeline* lastPipeline = nullptr;
-    BindGroupTracker bindGroupTracker(commandContext);
+    BindGroupTracker bindGroupTracker(commandContext, /*isRenderPass=*/true);
     VertexBufferTracker vertexBufferTracker(commandContext);
     std::array<float, 4> blendColor = {0.0f, 0.0f, 0.0f, 0.0f};
     uint32_t stencilReference = 0;
diff --git a/src/dawn/native/d3d11/CommandRecordingContextD3D11.cpp b/src/dawn/native/d3d11/CommandRecordingContextD3D11.cpp
index a7a3c76..055b2ba 100644
--- a/src/dawn/native/d3d11/CommandRecordingContextD3D11.cpp
+++ b/src/dawn/native/d3d11/CommandRecordingContextD3D11.cpp
@@ -30,6 +30,7 @@
 MaybeError CommandRecordingContext::Open(Device* device) {
     ASSERT(!IsOpen());
     ASSERT(device);
+    mDevice = device;
 
     if (!mD3D11DeviceContext4) {
         ID3D11Device* d3d11Device = device->GetD3D11Device();
@@ -106,11 +107,17 @@
     return mUniformBuffer.Get();
 }
 
+Device* CommandRecordingContext::GetDevice() const {
+    ASSERT(mDevice.Get());
+    return mDevice.Get();
+}
+
 void CommandRecordingContext::Release() {
     if (mIsOpen) {
         mIsOpen = false;
         mNeedsSubmit = false;
         mUniformBuffer = nullptr;
+        mDevice = nullptr;
         ID3D11Buffer* nullBuffer = nullptr;
         mD3D11DeviceContext4->VSSetConstantBuffers(PipelineLayout::kReservedConstantBufferSlot, 1,
                                                    &nullBuffer);
diff --git a/src/dawn/native/d3d11/CommandRecordingContextD3D11.h b/src/dawn/native/d3d11/CommandRecordingContextD3D11.h
index 4cdc58b..c3d2623 100644
--- a/src/dawn/native/d3d11/CommandRecordingContextD3D11.h
+++ b/src/dawn/native/d3d11/CommandRecordingContextD3D11.h
@@ -41,6 +41,7 @@
     ID3D11DeviceContext4* GetD3D11DeviceContext4() const;
     ID3DUserDefinedAnnotation* GetD3DUserDefinedAnnotation() const;
     Buffer* GetUniformBuffer() const;
+    Device* GetDevice() const;
 
   private:
     bool mIsOpen = false;
@@ -50,6 +51,7 @@
     ComPtr<ID3DUserDefinedAnnotation> mD3D11UserDefinedAnnotation;
     // The uniform buffer for built in variables.
     Ref<Buffer> mUniformBuffer;
+    Ref<Device> mDevice;
 };
 
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp
index 941002e..d724920 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -468,4 +468,8 @@
     return {dawnTexture};
 }
 
+uint32_t Device::GetUAVSlotCount() const {
+    return ToBackend(GetPhysicalDevice())->GetUAVSlotCount();
+}
+
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/DeviceD3D11.h b/src/dawn/native/d3d11/DeviceD3D11.h
index 0ab13a7..4753d56 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.h
+++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -83,6 +83,8 @@
     ResultOrError<std::unique_ptr<d3d::ExternalImageDXGIImpl>> CreateExternalImageDXGIImplImpl(
         const d3d::ExternalImageDescriptorDXGISharedHandle* descriptor) override;
 
+    uint32_t GetUAVSlotCount() const;
+
   private:
     using Base = d3d::Device;
     using Base::Base;
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index 8903ad8..95e6e4f 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -147,6 +147,7 @@
     uint32_t maxUAVsAllStages = mFeatureLevel == D3D_FEATURE_LEVEL_11_1
                                     ? D3D11_1_UAV_SLOT_COUNT
                                     : D3D11_PS_CS_UAV_REGISTER_COUNT;
+    mUAVSlotCount = maxUAVsAllStages;
     ASSERT(maxUAVsAllStages / 4 > limits->v1.maxStorageTexturesPerShaderStage);
     ASSERT(maxUAVsAllStages / 4 > limits->v1.maxStorageBuffersPerShaderStage);
     uint32_t maxUAVsPerStage = maxUAVsAllStages / 2;
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.h b/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
index 5cadee9..be10f59 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
@@ -37,6 +37,8 @@
     const DeviceInfo& GetDeviceInfo() const;
     ResultOrError<ComPtr<ID3D11Device>> CreateD3D11Device();
 
+    uint32_t GetUAVSlotCount() const { return mUAVSlotCount; }
+
   private:
     using Base = d3d::PhysicalDevice;
 
@@ -57,6 +59,7 @@
     ComPtr<ID3D11Device> mD3d11Device;
     D3D_FEATURE_LEVEL mFeatureLevel;
     DeviceInfo mDeviceInfo = {};
+    uint32_t mUAVSlotCount;
 };
 
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
index a8e18c6..f9e9462 100644
--- a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
+++ b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
@@ -37,10 +37,7 @@
     // resource slots when being written out. So we assign UAV binding index decreasingly here.
     // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargetsandunorderedaccessviews
     // TODO(dawn:1818): Support testing on both FL11_0 and FL11_1.
-    uint32_t unorderedAccessViewIndex =
-        device->GetD3D11Device()->GetFeatureLevel() == D3D_FEATURE_LEVEL_11_1
-            ? D3D11_1_UAV_SLOT_COUNT
-            : D3D11_PS_CS_UAV_REGISTER_COUNT;
+    uint32_t unorderedAccessViewIndex = device->GetUAVSlotCount();
     mTotalUAVBindingCount = unorderedAccessViewIndex;
 
     for (BindGroupIndex group : IterateBitSet(GetBindGroupLayoutsMask())) {
@@ -58,6 +55,7 @@
                         case wgpu::BufferBindingType::Storage:
                         case kInternalStorageBufferBinding:
                             mIndexInfo[group][bindingIndex] = --unorderedAccessViewIndex;
+                            mUAVBindGroups.set(group);
                             break;
                         case wgpu::BufferBindingType::ReadOnlyStorage:
                             mIndexInfo[group][bindingIndex] = shaderResourceViewIndex++;
@@ -78,6 +76,7 @@
 
                 case BindingInfoType::StorageTexture:
                     mIndexInfo[group][bindingIndex] = --unorderedAccessViewIndex;
+                    mUAVBindGroups.set(group);
                     break;
             }
         }
@@ -91,4 +90,8 @@
     return mIndexInfo;
 }
 
+const BindGroupLayoutMask& PipelineLayout::GetUAVBindGroupLayoutsMask() const {
+    return mUAVBindGroups;
+}
+
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/PipelineLayoutD3D11.h b/src/dawn/native/d3d11/PipelineLayoutD3D11.h
index d25f7fa..6f6349c 100644
--- a/src/dawn/native/d3d11/PipelineLayoutD3D11.h
+++ b/src/dawn/native/d3d11/PipelineLayoutD3D11.h
@@ -51,6 +51,9 @@
     uint32_t GetUnusedUAVBindingCount() const { return mUnusedUAVBindingCount; }
     uint32_t GetTotalUAVBindingCount() const { return mTotalUAVBindingCount; }
 
+    // Get the bind groups that use one or more UAV slots.
+    const BindGroupLayoutMask& GetUAVBindGroupLayoutsMask() const;
+
   private:
     using PipelineLayoutBase::PipelineLayoutBase;
 
@@ -62,6 +65,7 @@
 
     uint32_t mUnusedUAVBindingCount = 0u;
     uint32_t mTotalUAVBindingCount = 0u;
+    BindGroupLayoutMask mUAVBindGroups = 0;
 };
 
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/tests/end2end/BindGroupTests.cpp b/src/dawn/tests/end2end/BindGroupTests.cpp
index d01089a..e46da6a 100644
--- a/src/dawn/tests/end2end/BindGroupTests.cpp
+++ b/src/dawn/tests/end2end/BindGroupTests.cpp
@@ -705,6 +705,91 @@
     EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
 }
 
+// Test that the same renderpass can use 3 more pipelines
+TEST_P(BindGroupTests, ThreePipelinesInSameRenderpass) {
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
+
+    // Create a bind group layout which uses a single dynamic uniform buffer.
+    wgpu::BindGroupLayout uniformLayout = utils::MakeBindGroupLayout(
+        device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform, true}});
+
+    // Pipeline0 uses 2 bind groups, 'bindGroup00' and 'bindGroup01', respectively accommodated with
+    // 'color00' and 'color01'.
+    wgpu::RenderPipeline pipeline0 = MakeTestPipeline(
+        renderPass, {wgpu::BufferBindingType::Uniform, wgpu::BufferBindingType::Uniform},
+        {uniformLayout, uniformLayout});
+
+    // Pipeline1 uses 'bindGroup1', accommodated with 'color1'.
+    wgpu::RenderPipeline pipeline1 =
+        MakeTestPipeline(renderPass, {wgpu::BufferBindingType::Uniform}, {uniformLayout});
+
+    // Pipeline2 uses 'bindGroup2', accommodated with 'color2'.
+    wgpu::RenderPipeline pipeline2 =
+        MakeTestPipeline(renderPass, {wgpu::BufferBindingType::Uniform}, {uniformLayout});
+
+    // Sum up to (1, 1, 1, 1)
+    std::array<float, 4> color00 = {1, 0, 0, 0.5};
+    std::array<float, 4> color01 = {0, 1, 0, 0.5};
+    std::array<float, 4> color1 = {0, 0, 0.2, 0};
+    std::array<float, 4> color2 = {0, 0, 0.8, 0};
+
+    std::vector<uint8_t> data(sizeof(color00));
+    memcpy(data.data(), color00.data(), sizeof(color00));
+    wgpu::Buffer uniformBuffer00 =
+        utils::CreateBufferFromData(device, data.data(), data.size(), wgpu::BufferUsage::Uniform);
+    wgpu::BindGroup bindGroup00 =
+        utils::MakeBindGroup(device, uniformLayout, {{0, uniformBuffer00, 0, 4 * sizeof(float)}});
+
+    memcpy(data.data(), color01.data(), sizeof(color01));
+    wgpu::Buffer uniformBuffer01 =
+        utils::CreateBufferFromData(device, data.data(), data.size(), wgpu::BufferUsage::Uniform);
+    wgpu::BindGroup bindGroup01 =
+        utils::MakeBindGroup(device, uniformLayout, {{0, uniformBuffer01, 0, 4 * sizeof(float)}});
+
+    memcpy(data.data(), color1.data(), sizeof(color1));
+    wgpu::Buffer uniformBuffer1 =
+        utils::CreateBufferFromData(device, data.data(), data.size(), wgpu::BufferUsage::Uniform);
+    wgpu::BindGroup bindGroup1 =
+        utils::MakeBindGroup(device, uniformLayout, {{0, uniformBuffer1, 0, 4 * sizeof(float)}});
+
+    memcpy(data.data(), color2.data(), sizeof(color2));
+    wgpu::Buffer uniformBuffer2 =
+        utils::CreateBufferFromData(device, data.data(), data.size(), wgpu::BufferUsage::Uniform);
+    wgpu::BindGroup bindGroup2 =
+        utils::MakeBindGroup(device, uniformLayout, {{0, uniformBuffer2, 0, 4 * sizeof(float)}});
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+
+    uint32_t dynamicOffset = 0;
+
+    pass.SetPipeline(pipeline0);
+    pass.SetBindGroup(0, bindGroup00, 1, &dynamicOffset);
+    pass.SetBindGroup(1, bindGroup01, 1, &dynamicOffset);
+    pass.Draw(3);
+
+    pass.SetBindGroup(0, bindGroup1, 1, &dynamicOffset);
+    pass.SetPipeline(pipeline1);
+    pass.Draw(3);
+
+    pass.SetBindGroup(0, bindGroup2, 1, &dynamicOffset);
+    pass.SetPipeline(pipeline2);
+    pass.Draw(3);
+
+    pass.End();
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    utils::RGBA8 filled(255, 255, 255, 255);
+    utils::RGBA8 notFilled(0, 0, 0, 0);
+    uint32_t min = 1, max = kRTSize - 3;
+    EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min);
+    EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min);
+    EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max);
+    EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
+}
+
 // Test that bind groups set for one pipeline are still set when the pipeline changes.
 TEST_P(BindGroupTests, BindGroupsPersistAfterPipelineChange) {
     utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
diff --git a/src/dawn/tests/end2end/DynamicBufferOffsetTests.cpp b/src/dawn/tests/end2end/DynamicBufferOffsetTests.cpp
index 53d9d9b..63abeaf 100644
--- a/src/dawn/tests/end2end/DynamicBufferOffsetTests.cpp
+++ b/src/dawn/tests/end2end/DynamicBufferOffsetTests.cpp
@@ -297,6 +297,101 @@
                                mMinUniformBufferOffsetAlignment, expectedData.size());
 }
 
+// Test basic inherit on render pipeline
+TEST_P(DynamicBufferOffsetTests, BasicInheritRenderPipeline) {
+    wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
+            @vertex
+            fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
+                var pos = array(
+                    vec2f(-1.0, 0.0),
+                    vec2f(-1.0, 1.0),
+                    vec2f( 0.0, 1.0));
+                return vec4f(pos[VertexIndex], 0.0, 1.0);
+            })");
+
+    // Construct fragment shader source
+    std::ostringstream fs;
+    fs << R"(
+            struct Buf {
+                value : vec2u
+            }
+
+            @group(0) @binding(0) var<uniform> uBufferNotDynamic : Buf;
+            @group(0) @binding(1) var<storage, read_write> sBufferNotDynamic : Buf;
+            @group(1) @binding(3) var<uniform> uBuffer : Buf;
+            @group(1) @binding(4) var<storage, read_write> sBuffer : Buf;
+
+            @fragment fn main() -> @location(0) vec4f {
+                sBufferNotDynamic.value = uBufferNotDynamic.value.xy;
+                sBuffer.value = uBuffer.value.xy + uBufferNotDynamic.value.xy;
+                return vec4f(f32(uBuffer.value.x) / 255.0, f32(uBuffer.value.y) / 255.0,
+                                      1.0, 1.0);
+            }
+        )";
+
+    wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fs.str().c_str());
+
+    utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+    pipelineDescriptor.vertex.module = vsModule;
+    pipelineDescriptor.cFragment.module = fsModule;
+    pipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
+
+    wgpu::BindGroupLayout bindGroupLayouts[2];
+    bindGroupLayouts[0] = utils::MakeBindGroupLayout(
+        device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+                  wgpu::BufferBindingType::Uniform},
+                 {1, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+                  wgpu::BufferBindingType::Storage}});
+    bindGroupLayouts[1] = utils::MakeBindGroupLayout(
+        device, {{3, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+                  wgpu::BufferBindingType::Uniform, true},
+                 {4, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+                  wgpu::BufferBindingType::Storage, true}});
+
+    wgpu::BindGroup bindGroups[2];
+    bindGroups[0] = utils::MakeBindGroup(device, bindGroupLayouts[0],
+                                         {
+                                             {0, mUniformBuffers[0], 0, kBindingSize},
+                                             {1, mStorageBuffers[0], 0, kBindingSize},
+                                         });
+    bindGroups[1] = utils::MakeBindGroup(
+        device, bindGroupLayouts[1],
+        {{3, mUniformBuffers[1], 0, kBindingSize}, {4, mStorageBuffers[1], 0, kBindingSize}});
+
+    wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor;
+    pipelineLayoutDescriptor.bindGroupLayoutCount = 2;
+    pipelineLayoutDescriptor.bindGroupLayouts = bindGroupLayouts;
+    pipelineDescriptor.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor);
+
+    wgpu::RenderPipeline pipeline0 = device.CreateRenderPipeline(&pipelineDescriptor);
+    wgpu::RenderPipeline pipeline1 = device.CreateRenderPipeline(&pipelineDescriptor);
+
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
+
+    wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+    std::array<uint32_t, 2> offsets0 = {0, 0};
+    std::array<uint32_t, 2> offsets1 = {mMinUniformBufferOffsetAlignment,
+                                        mMinUniformBufferOffsetAlignment};
+    wgpu::RenderPassEncoder renderPassEncoder =
+        commandEncoder.BeginRenderPass(&renderPass.renderPassInfo);
+    renderPassEncoder.SetPipeline(pipeline0);
+    renderPassEncoder.SetBindGroup(0, bindGroups[0]);
+    renderPassEncoder.SetBindGroup(1, bindGroups[1], offsets0.size(), offsets0.data());
+    renderPassEncoder.Draw(3);
+    renderPassEncoder.SetPipeline(pipeline1);
+    // bind group 0 should be inherited and still available.
+    renderPassEncoder.SetBindGroup(1, bindGroups[1], offsets1.size(), offsets1.data());
+    renderPassEncoder.Draw(3);
+    renderPassEncoder.End();
+    wgpu::CommandBuffer commands = commandEncoder.Finish();
+    queue.Submit(1, &commands);
+
+    std::vector<uint32_t> expectedData = {6, 8};
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(5, 6, 255, 255), renderPass.color, 0, 0);
+    EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1],
+                               mMinUniformBufferOffsetAlignment, expectedData.size());
+}
+
 // Test inherit dynamic offsets on render pipeline
 TEST_P(DynamicBufferOffsetTests, InheritDynamicOffsetsRenderPipeline) {
     // Using default pipeline and setting dynamic offsets
@@ -576,6 +671,7 @@
 }
 
 DAWN_INSTANTIATE_TEST(DynamicBufferOffsetTests,
+                      D3D11Backend(),
                       D3D12Backend(),
                       MetalBackend(),
                       OpenGLBackend(),