D3D12: Decouple descriptor heap allocations.

Allows bindgroups to be populated by heap type. Previously, failing to
populate one type of GPU descriptor heap required both GPU heaps
to be switched out. This resulted in extra copies and heap allocations
should only one heap type overflow.

This change also simplifies GPU descriptor heap management:
- Allocator no longer needs to operate on both heaps.
- Sub-allocation tracking can be moved into handles.

A follow-up change will remove duplicated sampler heap allocations.

BUG=dawn:155

Change-Id: I1960cf3a8bc3d86d3e8b2775da3d0c92125bcf82
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/19887
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index 4251196..3852fcf 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -299,11 +299,11 @@
       "d3d12/D3D12Error.h",
       "d3d12/D3D12Info.cpp",
       "d3d12/D3D12Info.h",
-      "d3d12/DescriptorHeapAllocationD3D12.cpp",
-      "d3d12/DescriptorHeapAllocationD3D12.h",
       "d3d12/DeviceD3D12.cpp",
       "d3d12/DeviceD3D12.h",
       "d3d12/Forward.h",
+      "d3d12/GPUDescriptorHeapAllocationD3D12.cpp",
+      "d3d12/GPUDescriptorHeapAllocationD3D12.h",
       "d3d12/HeapAllocatorD3D12.cpp",
       "d3d12/HeapAllocatorD3D12.h",
       "d3d12/HeapD3D12.cpp",
diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt
index 9d78325..1465ad0 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -180,11 +180,11 @@
         "d3d12/D3D12Error.h"
         "d3d12/D3D12Info.cpp"
         "d3d12/D3D12Info.h"
-        "d3d12/DescriptorHeapAllocationD3D12.cpp"
-        "d3d12/DescriptorHeapAllocationD3D12.h"
         "d3d12/DeviceD3D12.cpp"
         "d3d12/DeviceD3D12.h"
         "d3d12/Forward.h"
+        "d3d12/GPUDescriptorHeapAllocationD3D12.cpp"
+        "d3d12/GPUDescriptorHeapAllocationD3D12.h"
         "d3d12/HeapAllocatorD3D12.cpp"
         "d3d12/HeapAllocatorD3D12.h"
         "d3d12/HeapD3D12.cpp"
diff --git a/src/dawn_native/d3d12/BindGroupD3D12.cpp b/src/dawn_native/d3d12/BindGroupD3D12.cpp
index 4d27b52..4e32874 100644
--- a/src/dawn_native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupD3D12.cpp
@@ -151,71 +151,53 @@
         ASSERT(!mCPUSamplerAllocation.IsValid());
     }
 
-    ResultOrError<bool> BindGroup::Populate(ShaderVisibleDescriptorAllocator* allocator) {
-        Device* device = ToBackend(GetDevice());
+    bool BindGroup::PopulateViews(ShaderVisibleDescriptorAllocator* viewAllocator) {
+        const BindGroupLayout* bgl = ToBackend(GetLayout());
+        return Populate(viewAllocator, bgl->GetCbvUavSrvDescriptorCount(),
+                        D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, mCPUViewAllocation,
+                        &mGPUViewAllocation);
+    }
 
-        if (allocator->IsAllocationStillValid(mLastUsageSerial, mHeapSerial)) {
+    bool BindGroup::PopulateSamplers(ShaderVisibleDescriptorAllocator* samplerAllocator) {
+        const BindGroupLayout* bgl = ToBackend(GetLayout());
+        return Populate(samplerAllocator, bgl->GetSamplerDescriptorCount(),
+                        D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, mCPUSamplerAllocation,
+                        &mGPUSamplerAllocation);
+    }
+
+    bool BindGroup::Populate(ShaderVisibleDescriptorAllocator* allocator,
+                             uint32_t descriptorCount,
+                             D3D12_DESCRIPTOR_HEAP_TYPE heapType,
+                             const CPUDescriptorHeapAllocation& stagingAllocation,
+                             GPUDescriptorHeapAllocation* allocation) {
+        if (descriptorCount == 0 || allocator->IsAllocationStillValid(*allocation)) {
             return true;
         }
 
         // Attempt to allocate descriptors for the currently bound shader-visible heaps.
         // If either failed, return early to re-allocate and switch the heaps.
-        const BindGroupLayout* bgl = ToBackend(GetLayout());
-        const Serial pendingSerial = device->GetPendingCommandSerial();
+        Device* device = ToBackend(GetDevice());
 
-        ID3D12Device* d3d12Device = device->GetD3D12Device();
+        D3D12_CPU_DESCRIPTOR_HANDLE baseCPUDescriptor;
+        if (!allocator->AllocateGPUDescriptors(descriptorCount, device->GetPendingCommandSerial(),
+                                               &baseCPUDescriptor, allocation)) {
+            return false;
+        }
 
         // CPU bindgroups are sparsely allocated across CPU heaps. Instead of doing
         // simple copies per bindgroup, a single non-simple copy could be issued.
         // TODO(dawn:155): Consider doing this optimization.
-        const uint32_t viewDescriptorCount = bgl->GetCbvUavSrvDescriptorCount();
-        if (viewDescriptorCount > 0) {
-            DescriptorHeapAllocation viewDescriptorHeapAllocation;
-            DAWN_TRY_ASSIGN(
-                viewDescriptorHeapAllocation,
-                allocator->AllocateGPUDescriptors(viewDescriptorCount, pendingSerial,
-                                                  D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
-            if (viewDescriptorHeapAllocation.IsInvalid()) {
-                return false;
-            }
-
-            d3d12Device->CopyDescriptorsSimple(
-                viewDescriptorCount, viewDescriptorHeapAllocation.GetBaseCPUDescriptor(),
-                mCPUViewAllocation.GetBaseDescriptor(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
-
-            mBaseViewDescriptor = viewDescriptorHeapAllocation.GetBaseGPUDescriptor();
-        }
-
-        const uint32_t samplerDescriptorCount = bgl->GetSamplerDescriptorCount();
-        if (samplerDescriptorCount > 0) {
-            DescriptorHeapAllocation samplerDescriptorHeapAllocation;
-            DAWN_TRY_ASSIGN(samplerDescriptorHeapAllocation,
-                            allocator->AllocateGPUDescriptors(samplerDescriptorCount, pendingSerial,
-                                                              D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER));
-            if (samplerDescriptorHeapAllocation.IsInvalid()) {
-                return false;
-            }
-
-            d3d12Device->CopyDescriptorsSimple(
-                samplerDescriptorCount, samplerDescriptorHeapAllocation.GetBaseCPUDescriptor(),
-                mCPUSamplerAllocation.GetBaseDescriptor(), D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
-
-            mBaseSamplerDescriptor = samplerDescriptorHeapAllocation.GetBaseGPUDescriptor();
-        }
-
-        // Record both the device and heap serials to determine later if the allocations are still
-        // valid.
-        mLastUsageSerial = pendingSerial;
-        mHeapSerial = allocator->GetShaderVisibleHeapsSerial();
+        device->GetD3D12Device()->CopyDescriptorsSimple(
+            descriptorCount, baseCPUDescriptor, stagingAllocation.GetBaseDescriptor(), heapType);
 
         return true;
     }
 
-    D3D12_GPU_DESCRIPTOR_HANDLE BindGroup::GetBaseCbvUavSrvDescriptor() const {
-        return mBaseViewDescriptor;
+    D3D12_GPU_DESCRIPTOR_HANDLE BindGroup::GetBaseViewDescriptor() const {
+        return mGPUViewAllocation.GetBaseDescriptor();
     }
 
     D3D12_GPU_DESCRIPTOR_HANDLE BindGroup::GetBaseSamplerDescriptor() const {
-        return mBaseSamplerDescriptor;
+        return mGPUSamplerAllocation.GetBaseDescriptor();
     }
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/BindGroupD3D12.h b/src/dawn_native/d3d12/BindGroupD3D12.h
index ac9e71d..05d67b4 100644
--- a/src/dawn_native/d3d12/BindGroupD3D12.h
+++ b/src/dawn_native/d3d12/BindGroupD3D12.h
@@ -19,6 +19,7 @@
 #include "common/Serial.h"
 #include "dawn_native/BindGroup.h"
 #include "dawn_native/d3d12/CPUDescriptorHeapAllocationD3D12.h"
+#include "dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
 
@@ -38,19 +39,23 @@
                   const CPUDescriptorHeapAllocation& samplerAllocation);
 
         // Returns true if the BindGroup was successfully populated.
-        ResultOrError<bool> Populate(ShaderVisibleDescriptorAllocator* allocator);
+        bool PopulateViews(ShaderVisibleDescriptorAllocator* viewAllocator);
+        bool PopulateSamplers(ShaderVisibleDescriptorAllocator* samplerAllocator);
 
-        D3D12_GPU_DESCRIPTOR_HANDLE GetBaseCbvUavSrvDescriptor() const;
+        D3D12_GPU_DESCRIPTOR_HANDLE GetBaseViewDescriptor() const;
         D3D12_GPU_DESCRIPTOR_HANDLE GetBaseSamplerDescriptor() const;
 
       private:
+        bool Populate(ShaderVisibleDescriptorAllocator* allocator,
+                      uint32_t descriptorCount,
+                      D3D12_DESCRIPTOR_HEAP_TYPE heapType,
+                      const CPUDescriptorHeapAllocation& stagingAllocation,
+                      GPUDescriptorHeapAllocation* allocation);
+
         ~BindGroup() override;
 
-        Serial mLastUsageSerial = 0;
-        Serial mHeapSerial = 0;
-
-        D3D12_GPU_DESCRIPTOR_HANDLE mBaseViewDescriptor = {0};
-        D3D12_GPU_DESCRIPTOR_HANDLE mBaseSamplerDescriptor = {0};
+        GPUDescriptorHeapAllocation mGPUSamplerAllocation;
+        GPUDescriptorHeapAllocation mGPUViewAllocation;
 
         CPUDescriptorHeapAllocation mCPUSamplerAllocation;
         CPUDescriptorHeapAllocation mCPUViewAllocation;
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index 9a5b663..0a45e2e 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -95,7 +95,8 @@
       public:
         BindGroupStateTracker(Device* device)
             : BindGroupAndStorageBarrierTrackerBase(),
-              mAllocator(device->GetShaderVisibleDescriptorAllocator()) {
+              mViewAllocator(device->GetViewShaderVisibleDescriptorAllocator()),
+              mSamplerAllocator(device->GetSamplerShaderVisibleDescriptorAllocator()) {
         }
 
         void SetInComputePass(bool inCompute_) {
@@ -111,21 +112,27 @@
             // Re-populating all bindgroups after the last one fails causes duplicated allocations
             // to occur on overflow.
             // TODO(bryan.bernhart@intel.com): Consider further optimization.
-            bool didCreateBindGroups = true;
+            bool didCreateBindGroupViews = true;
+            bool didCreateBindGroupSamplers = true;
             for (uint32_t index : IterateBitSet(mDirtyBindGroups)) {
-                DAWN_TRY_ASSIGN(didCreateBindGroups,
-                                ToBackend(mBindGroups[index])->Populate(mAllocator));
-                if (!didCreateBindGroups) {
+                BindGroup* group = ToBackend(mBindGroups[index]);
+                didCreateBindGroupViews = group->PopulateViews(mViewAllocator);
+                didCreateBindGroupSamplers = group->PopulateSamplers(mSamplerAllocator);
+                if (!didCreateBindGroupViews && !didCreateBindGroupSamplers) {
                     break;
                 }
             }
 
-            // This will re-create bindgroups for both heaps even if only one overflowed.
-            // TODO(bryan.bernhart@intel.com): Consider re-allocating heaps independently
-            // such that overflowing one doesn't re-allocate the another.
             ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();
-            if (!didCreateBindGroups) {
-                DAWN_TRY(mAllocator->AllocateAndSwitchShaderVisibleHeaps());
+
+            if (!didCreateBindGroupViews || !didCreateBindGroupSamplers) {
+                if (!didCreateBindGroupViews) {
+                    DAWN_TRY(mViewAllocator->AllocateAndSwitchShaderVisibleHeap());
+                }
+
+                if (!didCreateBindGroupSamplers) {
+                    DAWN_TRY(mSamplerAllocator->AllocateAndSwitchShaderVisibleHeap());
+                }
 
                 mDirtyBindGroupsObjectChangedOrIsDynamic |= mBindGroupLayoutsMask;
                 mDirtyBindGroups |= mBindGroupLayoutsMask;
@@ -134,9 +141,11 @@
                 SetID3D12DescriptorHeaps(commandList);
 
                 for (uint32_t index : IterateBitSet(mBindGroupLayoutsMask)) {
-                    DAWN_TRY_ASSIGN(didCreateBindGroups,
-                                    ToBackend(mBindGroups[index])->Populate(mAllocator));
-                    ASSERT(didCreateBindGroups);
+                    BindGroup* group = ToBackend(mBindGroups[index]);
+                    didCreateBindGroupViews = group->PopulateViews(mViewAllocator);
+                    didCreateBindGroupSamplers = group->PopulateSamplers(mSamplerAllocator);
+                    ASSERT(didCreateBindGroupViews);
+                    ASSERT(didCreateBindGroupSamplers);
                 }
             }
 
@@ -183,11 +192,11 @@
 
         void SetID3D12DescriptorHeaps(ID3D12GraphicsCommandList* commandList) {
             ASSERT(commandList != nullptr);
-            std::array<ID3D12DescriptorHeap*, 2> descriptorHeaps =
-                mAllocator->GetShaderVisibleHeaps();
+            std::array<ID3D12DescriptorHeap*, 2> descriptorHeaps = {
+                mViewAllocator->GetShaderVisibleHeap(), mSamplerAllocator->GetShaderVisibleHeap()};
             ASSERT(descriptorHeaps[0] != nullptr);
             ASSERT(descriptorHeaps[1] != nullptr);
-            commandList->SetDescriptorHeaps(2, descriptorHeaps.data());
+            commandList->SetDescriptorHeaps(descriptorHeaps.size(), descriptorHeaps.data());
         }
 
       private:
@@ -269,8 +278,7 @@
 
             if (cbvUavSrvCount > 0) {
                 uint32_t parameterIndex = pipelineLayout->GetCbvUavSrvRootParameterIndex(index);
-                const D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor =
-                    group->GetBaseCbvUavSrvDescriptor();
+                const D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor = group->GetBaseViewDescriptor();
                 if (mInCompute) {
                     commandList->SetComputeRootDescriptorTable(parameterIndex, baseDescriptor);
                 } else {
@@ -292,7 +300,8 @@
 
         bool mInCompute = false;
 
-        ShaderVisibleDescriptorAllocator* mAllocator;
+        ShaderVisibleDescriptorAllocator* mViewAllocator;
+        ShaderVisibleDescriptorAllocator* mSamplerAllocator;
     };
 
     namespace {
diff --git a/src/dawn_native/d3d12/DescriptorHeapAllocationD3D12.cpp b/src/dawn_native/d3d12/DescriptorHeapAllocationD3D12.cpp
deleted file mode 100644
index 2ca7885..0000000
--- a/src/dawn_native/d3d12/DescriptorHeapAllocationD3D12.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 The Dawn Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "dawn_native/d3d12/DescriptorHeapAllocationD3D12.h"
-#include "dawn_native/Error.h"
-
-namespace dawn_native { namespace d3d12 {
-
-    DescriptorHeapAllocation::DescriptorHeapAllocation(
-        D3D12_CPU_DESCRIPTOR_HANDLE baseCPUDescriptorHandle,
-        D3D12_GPU_DESCRIPTOR_HANDLE baseGPUDescriptorHandle)
-        : mBaseCPUDescriptorHandle(baseCPUDescriptorHandle),
-          mBaseGPUDescriptorHandle(baseGPUDescriptorHandle) {
-    }
-
-    D3D12_GPU_DESCRIPTOR_HANDLE DescriptorHeapAllocation::GetBaseGPUDescriptor() const {
-        ASSERT(!IsInvalid());
-        return mBaseGPUDescriptorHandle;
-    }
-
-    D3D12_CPU_DESCRIPTOR_HANDLE DescriptorHeapAllocation::GetBaseCPUDescriptor() const {
-        ASSERT(!IsInvalid());
-        return mBaseCPUDescriptorHandle;
-    }
-
-    bool DescriptorHeapAllocation::IsInvalid() const {
-        return mBaseCPUDescriptorHandle.ptr == 0;
-    }
-}}  // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/DescriptorHeapAllocationD3D12.h b/src/dawn_native/d3d12/DescriptorHeapAllocationD3D12.h
deleted file mode 100644
index e63d415..0000000
--- a/src/dawn_native/d3d12/DescriptorHeapAllocationD3D12.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 The Dawn Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef DAWNNATIVE_D3D12_DESCRIPTORHEAPALLOCATIOND3D12_H_
-#define DAWNNATIVE_D3D12_DESCRIPTORHEAPALLOCATIOND3D12_H_
-
-#include "dawn_native/d3d12/d3d12_platform.h"
-
-#include <cstdint>
-
-namespace dawn_native { namespace d3d12 {
-
-    // Wrapper for a handle into a GPU-only descriptor heap.
-    class DescriptorHeapAllocation {
-      public:
-        DescriptorHeapAllocation() = default;
-        DescriptorHeapAllocation(D3D12_CPU_DESCRIPTOR_HANDLE baseCPUDescriptorHandle,
-                                 D3D12_GPU_DESCRIPTOR_HANDLE baseGPUDescriptorHandle);
-        ~DescriptorHeapAllocation() = default;
-
-        D3D12_GPU_DESCRIPTOR_HANDLE GetBaseGPUDescriptor() const;
-        D3D12_CPU_DESCRIPTOR_HANDLE GetBaseCPUDescriptor() const;
-
-        bool IsInvalid() const;
-
-      private:
-        D3D12_CPU_DESCRIPTOR_HANDLE mBaseCPUDescriptorHandle = {0};
-        D3D12_GPU_DESCRIPTOR_HANDLE mBaseGPUDescriptorHandle = {0};
-    };
-}}  // namespace dawn_native::d3d12
-
-#endif  // DAWNNATIVE_D3D12_DESCRIPTORHEAPALLOCATIOND3D12_H_
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 8f09bf9..fdd620f 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -82,9 +82,13 @@
         // Initialize backend services
         mCommandAllocatorManager = std::make_unique<CommandAllocatorManager>(this);
 
-        mShaderVisibleDescriptorAllocator =
-            std::make_unique<ShaderVisibleDescriptorAllocator>(this);
-        DAWN_TRY(mShaderVisibleDescriptorAllocator->Initialize());
+        DAWN_TRY_ASSIGN(
+            mViewShaderVisibleDescriptorAllocator,
+            ShaderVisibleDescriptorAllocator::Create(this, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
+
+        DAWN_TRY_ASSIGN(
+            mSamplerShaderVisibleDescriptorAllocator,
+            ShaderVisibleDescriptorAllocator::Create(this, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER));
 
         // Zero sized allocator is never requested and does not need to exist.
         for (uint32_t countIndex = 1; countIndex < kNumOfStagingDescriptorAllocators;
@@ -212,7 +216,8 @@
 
         mResourceAllocatorManager->Tick(mCompletedSerial);
         DAWN_TRY(mCommandAllocatorManager->Tick(mCompletedSerial));
-        mShaderVisibleDescriptorAllocator->Tick(mCompletedSerial);
+        mViewShaderVisibleDescriptorAllocator->Tick(mCompletedSerial);
+        mSamplerShaderVisibleDescriptorAllocator->Tick(mCompletedSerial);
         mRenderTargetViewAllocator->Tick(mCompletedSerial);
         mDepthStencilViewAllocator->Tick(mCompletedSerial);
         mMapRequestTracker->Tick(mCompletedSerial);
@@ -471,8 +476,12 @@
         ASSERT(!mPendingCommands.IsOpen());
     }
 
-    ShaderVisibleDescriptorAllocator* Device::GetShaderVisibleDescriptorAllocator() const {
-        return mShaderVisibleDescriptorAllocator.get();
+    ShaderVisibleDescriptorAllocator* Device::GetViewShaderVisibleDescriptorAllocator() const {
+        return mViewShaderVisibleDescriptorAllocator.get();
+    }
+
+    ShaderVisibleDescriptorAllocator* Device::GetSamplerShaderVisibleDescriptorAllocator() const {
+        return mSamplerShaderVisibleDescriptorAllocator.get();
     }
 
     StagingDescriptorAllocator* Device::GetViewStagingDescriptorAllocator(
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 615f1fb..569acbf 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -100,7 +100,8 @@
 
         void DeallocateMemory(ResourceHeapAllocation& allocation);
 
-        ShaderVisibleDescriptorAllocator* GetShaderVisibleDescriptorAllocator() const;
+        ShaderVisibleDescriptorAllocator* GetViewShaderVisibleDescriptorAllocator() const;
+        ShaderVisibleDescriptorAllocator* GetSamplerShaderVisibleDescriptorAllocator() const;
 
         // Returns nullptr when descriptor count is zero.
         StagingDescriptorAllocator* GetViewStagingDescriptorAllocator(
@@ -180,7 +181,6 @@
         std::unique_ptr<MapRequestTracker> mMapRequestTracker;
         std::unique_ptr<ResourceAllocatorManager> mResourceAllocatorManager;
         std::unique_ptr<ResidencyManager> mResidencyManager;
-        std::unique_ptr<ShaderVisibleDescriptorAllocator> mShaderVisibleDescriptorAllocator;
 
         // Index corresponds to the descriptor count in the range [0, kMaxBindingsPerGroup].
         static constexpr uint32_t kNumOfStagingDescriptorAllocators = kMaxBindingsPerGroup + 1;
@@ -194,6 +194,10 @@
         std::unique_ptr<StagingDescriptorAllocator> mRenderTargetViewAllocator;
 
         std::unique_ptr<StagingDescriptorAllocator> mDepthStencilViewAllocator;
+
+        std::unique_ptr<ShaderVisibleDescriptorAllocator> mViewShaderVisibleDescriptorAllocator;
+
+        std::unique_ptr<ShaderVisibleDescriptorAllocator> mSamplerShaderVisibleDescriptorAllocator;
     };
 
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.cpp b/src/dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.cpp
new file mode 100644
index 0000000..c72605a
--- /dev/null
+++ b/src/dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.cpp
@@ -0,0 +1,39 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h"
+
+namespace dawn_native { namespace d3d12 {
+
+    GPUDescriptorHeapAllocation::GPUDescriptorHeapAllocation(
+        D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor,
+        Serial lastUsageSerial,
+        Serial heapSerial)
+        : mBaseDescriptor(baseDescriptor),
+          mLastUsageSerial(lastUsageSerial),
+          mHeapSerial(heapSerial) {
+    }
+
+    D3D12_GPU_DESCRIPTOR_HANDLE GPUDescriptorHeapAllocation::GetBaseDescriptor() const {
+        return mBaseDescriptor;
+    }
+
+    Serial GPUDescriptorHeapAllocation::GetLastUsageSerial() const {
+        return mLastUsageSerial;
+    }
+
+    Serial GPUDescriptorHeapAllocation::GetHeapSerial() const {
+        return mHeapSerial;
+    }
+}}  // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h b/src/dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h
new file mode 100644
index 0000000..c18d266
--- /dev/null
+++ b/src/dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h
@@ -0,0 +1,43 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DAWNNATIVE_D3D12_GPUDESCRIPTORHEAPALLOCATION_H_
+#define DAWNNATIVE_D3D12_GPUDESCRIPTORHEAPALLOCATION_H_
+
+#include "common/Serial.h"
+#include "dawn_native/d3d12/d3d12_platform.h"
+
+namespace dawn_native { namespace d3d12 {
+
+    // Wrapper for a handle into a GPU-only descriptor heap.
+    class GPUDescriptorHeapAllocation {
+      public:
+        GPUDescriptorHeapAllocation() = default;
+        GPUDescriptorHeapAllocation(D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor,
+                                    Serial lastUsageSerial,
+                                    Serial heapSerial);
+
+        D3D12_GPU_DESCRIPTOR_HANDLE GetBaseDescriptor() const;
+        Serial GetLastUsageSerial() const;
+        Serial GetHeapSerial() const;
+
+      private:
+        D3D12_GPU_DESCRIPTOR_HANDLE mBaseDescriptor = {0};
+        Serial mLastUsageSerial = 0;
+        Serial mHeapSerial = 0;
+    };
+
+}}  // namespace dawn_native::d3d12
+
+#endif  // DAWNNATIVE_D3D12_CPUDESCRIPTORHEAPALLOCATION_H_
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
index d881f26..9c36ac5 100644
--- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
+++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
@@ -15,13 +15,10 @@
 #include "dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h"
 #include "dawn_native/d3d12/D3D12Error.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
+#include "dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
 
-    // Check that d3d heap type enum correctly mirrors the type index used by the static arrays.
-    static_assert(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV == 0, "");
-    static_assert(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER == 1, "");
-
     // Thresholds should be adjusted (lower == faster) to avoid tests taking too long to complete.
     static constexpr const uint32_t kShaderVisibleSmallHeapSizes[] = {1024, 512};
 
@@ -50,119 +47,93 @@
         }
     }
 
-    ShaderVisibleDescriptorAllocator::ShaderVisibleDescriptorAllocator(Device* device)
-        : mDevice(device),
-          mSizeIncrements{
-              device->GetD3D12Device()->GetDescriptorHandleIncrementSize(
-                  D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV),
-              device->GetD3D12Device()->GetDescriptorHandleIncrementSize(
-                  D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER),
-          } {
+    // static
+    ResultOrError<std::unique_ptr<ShaderVisibleDescriptorAllocator>>
+    ShaderVisibleDescriptorAllocator::Create(Device* device, D3D12_DESCRIPTOR_HEAP_TYPE heapType) {
+        std::unique_ptr<ShaderVisibleDescriptorAllocator> allocator =
+            std::make_unique<ShaderVisibleDescriptorAllocator>(device, heapType);
+        DAWN_TRY(allocator->AllocateAndSwitchShaderVisibleHeap());
+        return std::move(allocator);
     }
 
-    MaybeError ShaderVisibleDescriptorAllocator::Initialize() {
-        ASSERT(mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV].heap.Get() == nullptr);
-        mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV].heapType =
-            D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
-
-        ASSERT(mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER].heap.Get() == nullptr);
-        mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER].heapType =
-            D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
-
-        DAWN_TRY(AllocateAndSwitchShaderVisibleHeaps());
-
-        return {};
-    }
-
-    MaybeError ShaderVisibleDescriptorAllocator::AllocateAndSwitchShaderVisibleHeaps() {
-        DAWN_TRY(AllocateGPUHeap(&mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV]));
-        DAWN_TRY(AllocateGPUHeap(&mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER]));
-
-        // Invalidate all bindgroup allocations on previously bound heaps by incrementing the heap
-        // serial. When a bindgroup attempts to re-populate, it will compare with its recorded
-        // heap serial.
-        mShaderVisibleHeapsSerial++;
-
-        return {};
-    }
-
-    ResultOrError<DescriptorHeapAllocation>
-    ShaderVisibleDescriptorAllocator::AllocateGPUDescriptors(uint32_t descriptorCount,
-                                                             Serial pendingSerial,
-                                                             D3D12_DESCRIPTOR_HEAP_TYPE heapType) {
+    ShaderVisibleDescriptorAllocator::ShaderVisibleDescriptorAllocator(
+        Device* device,
+        D3D12_DESCRIPTOR_HEAP_TYPE heapType)
+        : mHeapType(heapType),
+          mDevice(device),
+          mSizeIncrement(device->GetD3D12Device()->GetDescriptorHandleIncrementSize(heapType)) {
         ASSERT(heapType == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV ||
                heapType == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
-        ASSERT(mShaderVisibleBuffers[heapType].heap != nullptr);
-        const uint64_t startOffset =
-            mShaderVisibleBuffers[heapType].allocator.Allocate(descriptorCount, pendingSerial);
+    }
+
+    bool ShaderVisibleDescriptorAllocator::AllocateGPUDescriptors(
+        uint32_t descriptorCount,
+        Serial pendingSerial,
+        D3D12_CPU_DESCRIPTOR_HANDLE* baseCPUDescriptor,
+        GPUDescriptorHeapAllocation* allocation) {
+        ASSERT(mHeap != nullptr);
+        const uint64_t startOffset = mAllocator.Allocate(descriptorCount, pendingSerial);
         if (startOffset == RingBufferAllocator::kInvalidOffset) {
-            return DescriptorHeapAllocation{};  // Invalid
+            return false;
         }
 
-        ID3D12DescriptorHeap* descriptorHeap = mShaderVisibleBuffers[heapType].heap.Get();
+        ID3D12DescriptorHeap* descriptorHeap = mHeap.Get();
 
-        const uint64_t heapOffset = mSizeIncrements[heapType] * startOffset;
+        const uint64_t heapOffset = mSizeIncrement * startOffset;
 
         // Check for 32-bit overflow since CPU heap start handle uses size_t.
         const size_t cpuHeapStartPtr = descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr;
 
         ASSERT(heapOffset <= std::numeric_limits<size_t>::max() - cpuHeapStartPtr);
 
-        const D3D12_CPU_DESCRIPTOR_HANDLE baseCPUDescriptor = {cpuHeapStartPtr +
-                                                               static_cast<size_t>(heapOffset)};
+        *baseCPUDescriptor = {cpuHeapStartPtr + static_cast<size_t>(heapOffset)};
 
         const D3D12_GPU_DESCRIPTOR_HANDLE baseGPUDescriptor = {
             descriptorHeap->GetGPUDescriptorHandleForHeapStart().ptr + heapOffset};
 
-        return DescriptorHeapAllocation{baseCPUDescriptor, baseGPUDescriptor};
+        // Record both the device and heap serials to determine later if the allocations are
+        // still valid.
+        *allocation = GPUDescriptorHeapAllocation{baseGPUDescriptor, pendingSerial, mHeapSerial};
+
+        return true;
     }
 
-    std::array<ID3D12DescriptorHeap*, 2> ShaderVisibleDescriptorAllocator::GetShaderVisibleHeaps()
-        const {
-        return {mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV].heap.Get(),
-                mShaderVisibleBuffers[D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER].heap.Get()};
+    ID3D12DescriptorHeap* ShaderVisibleDescriptorAllocator::GetShaderVisibleHeap() const {
+        return mHeap.Get();
     }
 
     void ShaderVisibleDescriptorAllocator::Tick(uint64_t completedSerial) {
-        for (uint32_t i = 0; i < mShaderVisibleBuffers.size(); i++) {
-            ASSERT(mShaderVisibleBuffers[i].heap != nullptr);
-            mShaderVisibleBuffers[i].allocator.Deallocate(completedSerial);
-        }
+        mAllocator.Deallocate(completedSerial);
     }
 
     // Creates a GPU descriptor heap that manages descriptors in a FIFO queue.
-    MaybeError ShaderVisibleDescriptorAllocator::AllocateGPUHeap(
-        ShaderVisibleBuffer* shaderVisibleBuffer) {
+    MaybeError ShaderVisibleDescriptorAllocator::AllocateAndSwitchShaderVisibleHeap() {
         ComPtr<ID3D12DescriptorHeap> heap;
         // Return the switched out heap to the pool and retrieve the oldest heap that is no longer
         // used by GPU. This maintains a heap buffer to avoid frequently re-creating heaps for heavy
         // users.
         // TODO(dawn:256): Consider periodically triming to avoid OOM.
-        if (shaderVisibleBuffer->heap != nullptr) {
-            shaderVisibleBuffer->pool.push_back(
-                {mDevice->GetPendingCommandSerial(), std::move(shaderVisibleBuffer->heap)});
+        if (mHeap != nullptr) {
+            mPool.push_back({mDevice->GetPendingCommandSerial(), std::move(mHeap)});
         }
 
         // Recycle existing heap if possible.
-        if (!shaderVisibleBuffer->pool.empty() &&
-            shaderVisibleBuffer->pool.front().heapSerial <= mDevice->GetCompletedCommandSerial()) {
-            heap = std::move(shaderVisibleBuffer->pool.front().heap);
-            shaderVisibleBuffer->pool.pop_front();
+        if (!mPool.empty() && mPool.front().heapSerial <= mDevice->GetCompletedCommandSerial()) {
+            heap = std::move(mPool.front().heap);
+            mPool.pop_front();
         }
 
-        const D3D12_DESCRIPTOR_HEAP_TYPE heapType = shaderVisibleBuffer->heapType;
-
         // TODO(bryan.bernhart@intel.com): Allocating to max heap size wastes memory
         // should the developer not allocate any bindings for the heap type.
         // Consider dynamically re-sizing GPU heaps.
         const uint32_t descriptorCount = GetD3D12ShaderVisibleHeapSize(
-            heapType, mDevice->IsToggleEnabled(Toggle::UseD3D12SmallShaderVisibleHeapForTesting));
+            mHeapType, mDevice->IsToggleEnabled(Toggle::UseD3D12SmallShaderVisibleHeapForTesting));
 
         if (heap == nullptr) {
             D3D12_DESCRIPTOR_HEAP_DESC heapDescriptor;
-            heapDescriptor.Type = heapType;
+            heapDescriptor.Type = mHeapType;
             heapDescriptor.NumDescriptors = descriptorCount;
-            heapDescriptor.Flags = GetD3D12HeapFlags(heapType);
+            heapDescriptor.Flags = GetD3D12HeapFlags(mHeapType);
             heapDescriptor.NodeMask = 0;
             DAWN_TRY(CheckOutOfMemoryHRESULT(mDevice->GetD3D12Device()->CreateDescriptorHeap(
                                                  &heapDescriptor, IID_PPV_ARGS(&heap)),
@@ -170,41 +141,34 @@
         }
 
         // Create a FIFO buffer from the recently created heap.
-        shaderVisibleBuffer->heap = std::move(heap);
-        shaderVisibleBuffer->allocator = RingBufferAllocator(descriptorCount);
+        mHeap = std::move(heap);
+        mAllocator = RingBufferAllocator(descriptorCount);
+
+        // Invalidate all bindgroup allocations on previously bound heaps by incrementing the heap
+        // serial. When a bindgroup attempts to re-populate, it will compare with its recorded
+        // heap serial.
+        mHeapSerial++;
+
         return {};
     }
 
-    Serial ShaderVisibleDescriptorAllocator::GetShaderVisibleHeapsSerial() const {
-        return mShaderVisibleHeapsSerial;
+    Serial ShaderVisibleDescriptorAllocator::GetShaderVisibleHeapSerialForTesting() const {
+        return mHeapSerial;
     }
 
-    uint64_t ShaderVisibleDescriptorAllocator::GetShaderVisibleHeapSizeForTesting(
-        D3D12_DESCRIPTOR_HEAP_TYPE heapType) const {
-        ASSERT(heapType == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV ||
-               heapType == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
-        return mShaderVisibleBuffers[heapType].allocator.GetSize();
+    uint64_t ShaderVisibleDescriptorAllocator::GetShaderVisibleHeapSizeForTesting() const {
+        return mAllocator.GetSize();
     }
 
-    ComPtr<ID3D12DescriptorHeap> ShaderVisibleDescriptorAllocator::GetShaderVisibleHeapForTesting(
-        D3D12_DESCRIPTOR_HEAP_TYPE heapType) const {
-        ASSERT(heapType == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV ||
-               heapType == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
-        return mShaderVisibleBuffers[heapType].heap;
+    uint64_t ShaderVisibleDescriptorAllocator::GetShaderVisiblePoolSizeForTesting() const {
+        return mPool.size();
     }
 
-    uint64_t ShaderVisibleDescriptorAllocator::GetShaderVisiblePoolSizeForTesting(
-        D3D12_DESCRIPTOR_HEAP_TYPE heapType) const {
-        ASSERT(heapType == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV ||
-               heapType == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
-        return mShaderVisibleBuffers[heapType].pool.size();
-    }
-
-    bool ShaderVisibleDescriptorAllocator::IsAllocationStillValid(Serial lastUsageSerial,
-                                                                  Serial heapSerial) const {
+    bool ShaderVisibleDescriptorAllocator::IsAllocationStillValid(
+        const GPUDescriptorHeapAllocation& allocation) const {
         // Consider valid if allocated for the pending submit and the shader visible heaps
         // have not switched over.
-        return (lastUsageSerial > mDevice->GetCompletedCommandSerial() &&
-                heapSerial == mShaderVisibleHeapsSerial);
+        return (allocation.GetLastUsageSerial() > mDevice->GetCompletedCommandSerial() &&
+                allocation.GetHeapSerial() == mHeapSerial);
     }
 }}  // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
index 66f63f5..be4e839 100644
--- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
+++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
@@ -17,39 +17,47 @@
 
 #include "dawn_native/Error.h"
 #include "dawn_native/RingBufferAllocator.h"
-#include "dawn_native/d3d12/DescriptorHeapAllocationD3D12.h"
+#include "dawn_native/d3d12/d3d12_platform.h"
 
-#include <array>
 #include <list>
 
+// |ShaderVisibleDescriptorAllocator| allocates a variable-sized block of descriptors from a GPU
+// descriptor heap pool.
+// Internally, it manages a list of heaps using a ringbuffer block allocator. The heap is in one
+// of two states: switched in or out. Only a switched in heap can be bound to the pipeline. If
+// the heap is full, the caller must switch-in a new heap before re-allocating and the old one
+// is returned to the pool.
 namespace dawn_native { namespace d3d12 {
 
     class Device;
+    class GPUDescriptorHeapAllocation;
 
-    // Manages descriptor heap allocators used by the device to create descriptors using allocation
-    // methods based on the heap type.
     class ShaderVisibleDescriptorAllocator {
       public:
-        ShaderVisibleDescriptorAllocator(Device* device);
-        MaybeError Initialize();
-
-        ResultOrError<DescriptorHeapAllocation> AllocateGPUDescriptors(
-            uint32_t descriptorCount,
-            Serial pendingSerial,
+        static ResultOrError<std::unique_ptr<ShaderVisibleDescriptorAllocator>> Create(
+            Device* device,
             D3D12_DESCRIPTOR_HEAP_TYPE heapType);
 
+        ShaderVisibleDescriptorAllocator(Device* device, D3D12_DESCRIPTOR_HEAP_TYPE heapType);
+
+        // Returns true if the allocation was successful, when false is returned the current heap is
+        // full and AllocateAndSwitchShaderVisibleHeap() must be called.
+        bool AllocateGPUDescriptors(uint32_t descriptorCount,
+                                    Serial pendingSerial,
+                                    D3D12_CPU_DESCRIPTOR_HANDLE* baseCPUDescriptor,
+                                    GPUDescriptorHeapAllocation* allocation);
+
         void Tick(uint64_t completedSerial);
-        Serial GetShaderVisibleHeapsSerial() const;
 
-        std::array<ID3D12DescriptorHeap*, 2> GetShaderVisibleHeaps() const;
-        MaybeError AllocateAndSwitchShaderVisibleHeaps();
+        ID3D12DescriptorHeap* GetShaderVisibleHeap() const;
+        MaybeError AllocateAndSwitchShaderVisibleHeap();
 
-        uint64_t GetShaderVisibleHeapSizeForTesting(D3D12_DESCRIPTOR_HEAP_TYPE heapType) const;
-        ComPtr<ID3D12DescriptorHeap> GetShaderVisibleHeapForTesting(
-            D3D12_DESCRIPTOR_HEAP_TYPE heapType) const;
-        uint64_t GetShaderVisiblePoolSizeForTesting(D3D12_DESCRIPTOR_HEAP_TYPE heapType) const;
+        // For testing purposes only.
+        Serial GetShaderVisibleHeapSerialForTesting() const;
+        uint64_t GetShaderVisibleHeapSizeForTesting() const;
+        uint64_t GetShaderVisiblePoolSizeForTesting() const;
 
-        bool IsAllocationStillValid(Serial lastUsageSerial, Serial heapSerial) const;
+        bool IsAllocationStillValid(const GPUDescriptorHeapAllocation& allocation) const;
 
       private:
         struct SerialDescriptorHeap {
@@ -57,23 +65,19 @@
             ComPtr<ID3D12DescriptorHeap> heap;
         };
 
-        struct ShaderVisibleBuffer {
-            ComPtr<ID3D12DescriptorHeap> heap;
-            RingBufferAllocator allocator;
-            std::list<SerialDescriptorHeap> pool;
-            D3D12_DESCRIPTOR_HEAP_TYPE heapType;
-        };
-
-        MaybeError AllocateGPUHeap(ShaderVisibleBuffer* shaderVisibleBuffer);
+        ComPtr<ID3D12DescriptorHeap> mHeap;
+        RingBufferAllocator mAllocator;
+        std::list<SerialDescriptorHeap> mPool;
+        D3D12_DESCRIPTOR_HEAP_TYPE mHeapType;
 
         Device* mDevice;
 
         // The serial value of 0 means the shader-visible heaps have not been allocated.
-        // This value is never returned by GetShaderVisibleHeapsSerial() after Initialize().
-        Serial mShaderVisibleHeapsSerial = 0;
+        // This value is never returned in the GPUDescriptorHeapAllocation after
+        // AllocateGPUDescriptors() is called.
+        Serial mHeapSerial = 0;
 
-        std::array<ShaderVisibleBuffer, 2> mShaderVisibleBuffers;
-        std::array<uint32_t, 2> mSizeIncrements;
+        uint32_t mSizeIncrement;
     };
 }}  // namespace dawn_native::d3d12
 
diff --git a/src/tests/white_box/D3D12DescriptorHeapTests.cpp b/src/tests/white_box/D3D12DescriptorHeapTests.cpp
index b996d0a..bdb18c1 100644
--- a/src/tests/white_box/D3D12DescriptorHeapTests.cpp
+++ b/src/tests/white_box/D3D12DescriptorHeapTests.cpp
@@ -75,11 +75,6 @@
         return utils::BasicRenderPass(width, height, color);
     }
 
-    uint32_t GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType) const {
-        return mD3DDevice->GetShaderVisibleDescriptorAllocator()
-            ->GetShaderVisibleHeapSizeForTesting(heapType);
-    }
-
     std::array<float, 4> GetSolidColor(uint32_t n) const {
         ASSERT(n >> 24 == 0);
         float b = (n & 0xFF) / 255.0f;
@@ -119,8 +114,8 @@
     StagingDescriptorAllocator mAllocator;
 };
 
-// Verify the shader visible heaps switch over within a single submit.
-TEST_P(D3D12DescriptorHeapTests, SwitchOverHeaps) {
+// Verify the shader visible sampler heap switch within a single submit.
+TEST_P(D3D12DescriptorHeapTests, SwitchOverSamplerHeap) {
     utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
 
     // Fill in a sampler heap with "sampler only" bindgroups (1x sampler per group) by creating a
@@ -147,11 +142,11 @@
     wgpu::Sampler sampler = device.CreateSampler(&samplerDesc);
 
     Device* d3dDevice = reinterpret_cast<Device*>(device.Get());
-    ShaderVisibleDescriptorAllocator* allocator = d3dDevice->GetShaderVisibleDescriptorAllocator();
-    const uint64_t samplerHeapSize =
-        allocator->GetShaderVisibleHeapSizeForTesting(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
+    ShaderVisibleDescriptorAllocator* allocator =
+        d3dDevice->GetSamplerShaderVisibleDescriptorAllocator();
+    const uint64_t samplerHeapSize = allocator->GetShaderVisibleHeapSizeForTesting();
 
-    const Serial heapSerial = allocator->GetShaderVisibleHeapsSerial();
+    const Serial heapSerial = allocator->GetShaderVisibleHeapSerialForTesting();
 
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
     {
@@ -171,24 +166,22 @@
     wgpu::CommandBuffer commands = encoder.Finish();
     queue.Submit(1, &commands);
 
-    EXPECT_EQ(allocator->GetShaderVisibleHeapsSerial(), heapSerial + 1);
+    EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + 1);
 }
 
 // Verify shader-visible heaps can be recycled for multiple submits.
 TEST_P(D3D12DescriptorHeapTests, PoolHeapsInMultipleSubmits) {
-    constexpr D3D12_DESCRIPTOR_HEAP_TYPE heapType = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
+    ShaderVisibleDescriptorAllocator* allocator =
+        mD3DDevice->GetSamplerShaderVisibleDescriptorAllocator();
 
-    ShaderVisibleDescriptorAllocator* allocator = mD3DDevice->GetShaderVisibleDescriptorAllocator();
+    std::list<ComPtr<ID3D12DescriptorHeap>> heaps = {allocator->GetShaderVisibleHeap()};
 
-    std::list<ComPtr<ID3D12DescriptorHeap>> heaps = {
-        allocator->GetShaderVisibleHeapForTesting(heapType)};
-
-    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), 0u);
+    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), 0u);
 
     // Allocate + Tick() up to |kFrameDepth| and ensure heaps are always unique.
     for (uint32_t i = 0; i < kFrameDepth; i++) {
-        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeaps().IsSuccess());
-        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeapForTesting(heapType);
+        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeap().IsSuccess());
+        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeap();
         EXPECT_TRUE(std::find(heaps.begin(), heaps.end(), heap) == heaps.end());
         heaps.push_back(heap);
         mD3DDevice->Tick();
@@ -198,68 +191,66 @@
     // (oldest heaps are recycled first). The "+ 1" is so we also include the very first heap in the
     // check.
     for (uint32_t i = 0; i < kFrameDepth + 1; i++) {
-        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeaps().IsSuccess());
-        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeapForTesting(heapType);
+        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeap().IsSuccess());
+        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeap();
         EXPECT_TRUE(heaps.front() == heap);
         heaps.pop_front();
         mD3DDevice->Tick();
     }
 
     EXPECT_TRUE(heaps.empty());
-    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), kFrameDepth);
+    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), kFrameDepth);
 }
 
 // Verify shader-visible heaps do not recycle in a pending submit.
 TEST_P(D3D12DescriptorHeapTests, PoolHeapsInPendingSubmit) {
-    constexpr D3D12_DESCRIPTOR_HEAP_TYPE heapType = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
     constexpr uint32_t kNumOfSwitches = 5;
 
-    ShaderVisibleDescriptorAllocator* allocator = mD3DDevice->GetShaderVisibleDescriptorAllocator();
+    ShaderVisibleDescriptorAllocator* allocator =
+        mD3DDevice->GetSamplerShaderVisibleDescriptorAllocator();
 
-    const Serial heapSerial = allocator->GetShaderVisibleHeapsSerial();
+    const Serial heapSerial = allocator->GetShaderVisibleHeapSerialForTesting();
 
-    std::set<ComPtr<ID3D12DescriptorHeap>> heaps = {
-        allocator->GetShaderVisibleHeapForTesting(heapType)};
+    std::set<ComPtr<ID3D12DescriptorHeap>> heaps = {allocator->GetShaderVisibleHeap()};
 
-    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), 0u);
+    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), 0u);
 
     // Switch-over |kNumOfSwitches| and ensure heaps are always unique.
     for (uint32_t i = 0; i < kNumOfSwitches; i++) {
-        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeaps().IsSuccess());
-        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeapForTesting(heapType);
+        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeap().IsSuccess());
+        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeap();
         EXPECT_TRUE(std::find(heaps.begin(), heaps.end(), heap) == heaps.end());
         heaps.insert(heap);
     }
 
     // After |kNumOfSwitches|, no heaps are recycled.
-    EXPECT_EQ(allocator->GetShaderVisibleHeapsSerial(), heapSerial + kNumOfSwitches);
-    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), kNumOfSwitches);
+    EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + kNumOfSwitches);
+    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), kNumOfSwitches);
 }
 
 // Verify switching shader-visible heaps do not recycle in a pending submit but do so
 // once no longer pending.
 TEST_P(D3D12DescriptorHeapTests, PoolHeapsInPendingAndMultipleSubmits) {
-    constexpr D3D12_DESCRIPTOR_HEAP_TYPE heapType = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
     constexpr uint32_t kNumOfSwitches = 5;
 
-    ShaderVisibleDescriptorAllocator* allocator = mD3DDevice->GetShaderVisibleDescriptorAllocator();
-    const Serial heapSerial = allocator->GetShaderVisibleHeapsSerial();
+    ShaderVisibleDescriptorAllocator* allocator =
+        mD3DDevice->GetSamplerShaderVisibleDescriptorAllocator();
+    const Serial heapSerial = allocator->GetShaderVisibleHeapSerialForTesting();
 
-    std::set<ComPtr<ID3D12DescriptorHeap>> heaps = {
-        allocator->GetShaderVisibleHeapForTesting(heapType)};
+    std::set<ComPtr<ID3D12DescriptorHeap>> heaps = {allocator->GetShaderVisibleHeap()};
 
-    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), 0u);
+    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), 0u);
 
     // Switch-over |kNumOfSwitches| to create a pool of unique heaps.
     for (uint32_t i = 0; i < kNumOfSwitches; i++) {
-        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeaps().IsSuccess());
-        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeapForTesting(heapType);
+        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeap().IsSuccess());
+        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeap();
         EXPECT_TRUE(std::find(heaps.begin(), heaps.end(), heap) == heaps.end());
         heaps.insert(heap);
     }
 
-    EXPECT_EQ(allocator->GetShaderVisibleHeapsSerial(), heapSerial + kNumOfSwitches);
-    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), kNumOfSwitches);
+    EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + kNumOfSwitches);
+    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), kNumOfSwitches);
 
     // Ensure switched-over heaps can be recycled by advancing the GPU by at-least |kFrameDepth|.
     for (uint32_t i = 0; i < kFrameDepth; i++) {
@@ -268,15 +259,15 @@
 
     // Switch-over |kNumOfSwitches| again reusing the same heaps.
     for (uint32_t i = 0; i < kNumOfSwitches; i++) {
-        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeaps().IsSuccess());
-        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeapForTesting(heapType);
+        EXPECT_TRUE(allocator->AllocateAndSwitchShaderVisibleHeap().IsSuccess());
+        ComPtr<ID3D12DescriptorHeap> heap = allocator->GetShaderVisibleHeap();
         EXPECT_TRUE(std::find(heaps.begin(), heaps.end(), heap) != heaps.end());
         heaps.erase(heap);
     }
 
     // After switching-over |kNumOfSwitches| x 2, ensure no additional heaps exist.
-    EXPECT_EQ(allocator->GetShaderVisibleHeapsSerial(), heapSerial + kNumOfSwitches * 2);
-    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(heapType), kNumOfSwitches);
+    EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + kNumOfSwitches * 2);
+    EXPECT_EQ(allocator->GetShaderVisiblePoolSizeForTesting(), kNumOfSwitches);
 }
 
 // Verify encoding multiple heaps worth of bindgroups.
@@ -316,7 +307,8 @@
 
     wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&pipelineDescriptor);
 
-    const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+    const uint32_t heapSize =
+        mD3DDevice->GetViewShaderVisibleDescriptorAllocator()->GetShaderVisibleHeapSizeForTesting();
 
     constexpr uint32_t kNumOfHeaps = 2;
 
@@ -398,7 +390,8 @@
 
     // Encode a heap worth of descriptors.
     {
-        const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
+        const uint32_t heapSize = mD3DDevice->GetSamplerShaderVisibleDescriptorAllocator()
+                                      ->GetShaderVisibleHeapSizeForTesting();
 
         std::vector<wgpu::BindGroup> bindGroups;
         for (uint32_t i = 0; i < heapSize - 1; i++) {
@@ -462,7 +455,8 @@
     std::vector<wgpu::BindGroup> bindGroups = {utils::MakeBindGroup(
         device, pipeline.GetBindGroupLayout(0), {{0, firstUniformBuffer, 0, sizeof(redColor)}})};
 
-    const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+    const uint32_t heapSize =
+        mD3DDevice->GetViewShaderVisibleDescriptorAllocator()->GetShaderVisibleHeapSizeForTesting();
 
     for (uint32_t i = 0; i < heapSize; i++) {
         const std::array<float, 4>& fillColor = GetSolidColor(i + 1);  // Avoid black
@@ -524,7 +518,8 @@
     std::vector<wgpu::BindGroup> bindGroups = {utils::MakeBindGroup(
         device, pipeline.GetBindGroupLayout(0), {{0, firstUniformBuffer, 0, sizeof(redColor)}})};
 
-    const uint32_t heapSize = GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+    const uint32_t heapSize =
+        mD3DDevice->GetViewShaderVisibleDescriptorAllocator()->GetShaderVisibleHeapSizeForTesting();
 
     for (uint32_t i = 0; i < heapSize; i++) {
         std::array<float, 4> fillColor = GetSolidColor(i + 1);  // Avoid black
@@ -661,13 +656,16 @@
         wgpu::SamplerDescriptor samplerDescriptor;
         wgpu::Sampler sampler = device.CreateSampler(&samplerDescriptor);
 
-        ShaderVisibleDescriptorAllocator* allocator =
-            mD3DDevice->GetShaderVisibleDescriptorAllocator();
+        ShaderVisibleDescriptorAllocator* viewAllocator =
+            mD3DDevice->GetViewShaderVisibleDescriptorAllocator();
 
-        const Serial heapSerial = allocator->GetShaderVisibleHeapsSerial();
+        ShaderVisibleDescriptorAllocator* samplerAllocator =
+            mD3DDevice->GetSamplerShaderVisibleDescriptorAllocator();
 
-        const uint32_t viewHeapSize =
-            GetShaderVisibleHeapSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+        const Serial viewHeapSerial = viewAllocator->GetShaderVisibleHeapSerialForTesting();
+        const Serial samplerHeapSerial = samplerAllocator->GetShaderVisibleHeapSerialForTesting();
+
+        const uint32_t viewHeapSize = viewAllocator->GetShaderVisibleHeapSizeForTesting();
 
         // "Small" view heap is always 2 x sampler heap size and encodes 3x the descriptors per
         // group. This means the count of heaps switches is determined by the total number of views
@@ -724,7 +722,17 @@
         EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, 0, 0);
         EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, kRTSize - 1, 0);
 
-        EXPECT_EQ(allocator->GetShaderVisibleHeapsSerial(), heapSerial + kNumOfViewHeaps);
+        EXPECT_EQ(viewAllocator->GetShaderVisiblePoolSizeForTesting(), kNumOfViewHeaps);
+        EXPECT_EQ(viewAllocator->GetShaderVisibleHeapSerialForTesting(),
+                  viewHeapSerial + kNumOfViewHeaps);
+
+        const uint32_t numOfSamplerHeaps =
+            numOfEncodedBindGroups /
+            samplerAllocator->GetShaderVisibleHeapSizeForTesting();  // 1 sampler per group.
+
+        EXPECT_EQ(samplerAllocator->GetShaderVisiblePoolSizeForTesting(), numOfSamplerHeaps);
+        EXPECT_EQ(samplerAllocator->GetShaderVisibleHeapSerialForTesting(),
+                  samplerHeapSerial + numOfSamplerHeaps);
     }
 }