Descriptor Residency 1: Add Pageable and ShaderVisibleDescriptorHeap

Refactors ShaderVisibleDescriptorAllocator to use d3d12::Heap to
represent ID3D12DescriptorHeaps.

Bug: dawn:193
Change-Id: If0a9df0bc138c4d6f1ad110750ab1e6e8084b80f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/20960
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index e4f03b5..25f02f9 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -312,6 +312,8 @@
       "d3d12/HeapD3D12.h",
       "d3d12/NativeSwapChainImplD3D12.cpp",
       "d3d12/NativeSwapChainImplD3D12.h",
+      "d3d12/PageableD3D12.cpp",
+      "d3d12/PageableD3D12.h",
       "d3d12/PipelineLayoutD3D12.cpp",
       "d3d12/PipelineLayoutD3D12.h",
       "d3d12/PlatformFunctions.cpp",
diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt
index 6232477..c51ae82 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -193,6 +193,8 @@
         "d3d12/HeapD3D12.h"
         "d3d12/NativeSwapChainImplD3D12.cpp"
         "d3d12/NativeSwapChainImplD3D12.h"
+        "d3d12/PageableD3D12.cpp"
+        "d3d12/PageableD3D12.h"
         "d3d12/PipelineLayoutD3D12.cpp"
         "d3d12/PipelineLayoutD3D12.h"
         "d3d12/PlatformFunctions.cpp"
diff --git a/src/dawn_native/d3d12/HeapD3D12.cpp b/src/dawn_native/d3d12/HeapD3D12.cpp
index add21cf..ade5d4a 100644
--- a/src/dawn_native/d3d12/HeapD3D12.cpp
+++ b/src/dawn_native/d3d12/HeapD3D12.cpp
@@ -16,16 +16,8 @@
 
 namespace dawn_native { namespace d3d12 {
     Heap::Heap(ComPtr<ID3D12Pageable> d3d12Pageable, MemorySegment memorySegment, uint64_t size)
-        : mD3d12Pageable(std::move(d3d12Pageable)), mMemorySegment(memorySegment), mSize(size) {
-    }
-
-    Heap::~Heap() {
-        // When a heap is destroyed, it no longer resides in resident memory, so we must evict
-        // it from the LRU cache. If this heap is not manually removed from the LRU-cache, the
-        // ResidencyManager will attempt to use it after it has been deallocated.
-        if (IsInResidencyLRUCache()) {
-            RemoveFromList();
-        }
+        : Pageable(std::move(d3d12Pageable), memorySegment, size) {
+        mD3d12Pageable.As(&mD3d12Heap);
     }
 
     // This function should only be used when mD3D12Pageable was initialized from a
@@ -33,58 +25,7 @@
     // ID3D12Pageable was initially created as an ID3D12Resource (i.e. DirectAllocation), then
     // use GetD3D12Pageable().
     ID3D12Heap* Heap::GetD3D12Heap() const {
-        ComPtr<ID3D12Heap> heap;
-        HRESULT result = mD3d12Pageable.As(&heap);
-        ASSERT(SUCCEEDED(result));
-        return heap.Get();
-    }
-
-    ID3D12Pageable* Heap::GetD3D12Pageable() const {
-        return mD3d12Pageable.Get();
-    }
-
-    MemorySegment Heap::GetMemorySegment() const {
-        return mMemorySegment;
-    }
-
-    Serial Heap::GetLastUsage() const {
-        return mLastUsage;
-    }
-
-    void Heap::SetLastUsage(Serial serial) {
-        mLastUsage = serial;
-    }
-
-    uint64_t Heap::GetLastSubmission() const {
-        return mLastSubmission;
-    }
-
-    void Heap::SetLastSubmission(Serial serial) {
-        mLastSubmission = serial;
-    }
-
-    uint64_t Heap::GetSize() const {
-        return mSize;
-    }
-
-    bool Heap::IsInResidencyLRUCache() const {
-        return IsInList();
-    }
-
-    void Heap::IncrementResidencyLock() {
-        mResidencyLockRefCount++;
-    }
-
-    void Heap::DecrementResidencyLock() {
-        mResidencyLockRefCount--;
-    }
-
-    bool Heap::IsResidencyLocked() const {
-        if (mResidencyLockRefCount == 0) {
-            return false;
-        }
-
-        return true;
+        return mD3d12Heap.Get();
     }
 
 }}  // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/HeapD3D12.h b/src/dawn_native/d3d12/HeapD3D12.h
index 71a4a0f..715ffcd 100644
--- a/src/dawn_native/d3d12/HeapD3D12.h
+++ b/src/dawn_native/d3d12/HeapD3D12.h
@@ -15,67 +15,25 @@
 #ifndef DAWNNATIVE_D3D12_HEAPD3D12_H_
 #define DAWNNATIVE_D3D12_HEAPD3D12_H_
 
-#include "common/LinkedList.h"
-#include "common/Serial.h"
-#include "dawn_native/D3D12Backend.h"
 #include "dawn_native/ResourceHeap.h"
+#include "dawn_native/d3d12/PageableD3D12.h"
 #include "dawn_native/d3d12/d3d12_platform.h"
 
 namespace dawn_native { namespace d3d12 {
 
     class Device;
 
-    // This class is used to represent heap allocations, but also serves as a node within the
-    // ResidencyManager's LRU cache. This node is inserted into the LRU-cache when it is first
-    // allocated, and any time it is scheduled to be used by the GPU. This node is removed from the
-    // LRU cache when it is evicted from resident memory due to budget constraints, or when the heap
-    // is destroyed.
-    class Heap : public ResourceHeapBase, public LinkNode<Heap> {
+    // This class is used to represent ID3D12Heap allocations, as well as an implicit heap
+    // representing a directly allocated resource. It inherits from Pageable because each Heap must
+    // be represented in the ResidencyManager.
+    class Heap : public ResourceHeapBase, public Pageable {
       public:
         Heap(ComPtr<ID3D12Pageable> d3d12Pageable, MemorySegment memorySegment, uint64_t size);
-        ~Heap();
 
         ID3D12Heap* GetD3D12Heap() const;
-        ID3D12Pageable* GetD3D12Pageable() const;
-        MemorySegment GetMemorySegment() const;
-
-        // We set mLastRecordingSerial to denote the serial this heap was last recorded to be used.
-        // We must check this serial against the current serial when recording heap usages to ensure
-        // we do not process residency for this heap multiple times.
-        Serial GetLastUsage() const;
-        void SetLastUsage(Serial serial);
-
-        // The residency manager must know the last serial that any portion of the heap was
-        // submitted to be used so that we can ensure this heap stays resident in memory at least
-        // until that serial has completed.
-        uint64_t GetLastSubmission() const;
-        void SetLastSubmission(Serial serial);
-
-        uint64_t GetSize() const;
-
-        bool IsInResidencyLRUCache() const;
-
-        // In some scenarios, such as async buffer mapping, we must lock residency to ensure the
-        // heap cannot be evicted. Because multiple buffers may be mapped in a single heap, we must
-        // track the number of resources currently locked.
-        void IncrementResidencyLock();
-        void DecrementResidencyLock();
-        bool IsResidencyLocked() const;
 
       private:
-        ComPtr<ID3D12Pageable> mD3d12Pageable;
-        MemorySegment mMemorySegment;
-        // mLastUsage denotes the last time this heap was recorded for use.
-        Serial mLastUsage = 0;
-        // mLastSubmission denotes the last time this heap was submitted to the GPU. Note that
-        // although this variable often contains the same value as mLastUsage, it can differ in some
-        // situations. When some asynchronous APIs (like SetSubData) are called, mLastUsage is
-        // updated upon the call, but the backend operation is deferred until the next submission
-        // to the GPU. This makes mLastSubmission unique from mLastUsage, and allows us to
-        // accurately identify when heaps are evictable.
-        Serial mLastSubmission = 0;
-        uint32_t mResidencyLockRefCount = 0;
-        uint64_t mSize = 0;
+        ComPtr<ID3D12Heap> mD3d12Heap;
     };
 }}  // namespace dawn_native::d3d12
 
diff --git a/src/dawn_native/d3d12/PageableD3D12.cpp b/src/dawn_native/d3d12/PageableD3D12.cpp
new file mode 100644
index 0000000..5884780
--- /dev/null
+++ b/src/dawn_native/d3d12/PageableD3D12.cpp
@@ -0,0 +1,76 @@
+// 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/PageableD3D12.h"
+
+namespace dawn_native { namespace d3d12 {
+    Pageable::Pageable(ComPtr<ID3D12Pageable> d3d12Pageable,
+                       MemorySegment memorySegment,
+                       uint64_t size)
+        : mD3d12Pageable(std::move(d3d12Pageable)), mMemorySegment(memorySegment), mSize(size) {
+    }
+
+    // When a pageable is destroyed, it no longer resides in resident memory, so we must evict
+    // it from the LRU cache. If this heap is not manually removed from the LRU-cache, the
+    // ResidencyManager will attempt to use it after it has been deallocated.
+    Pageable::~Pageable() {
+        if (IsInResidencyLRUCache()) {
+            RemoveFromList();
+        }
+    }
+
+    ID3D12Pageable* Pageable::GetD3D12Pageable() const {
+        return mD3d12Pageable.Get();
+    }
+
+    Serial Pageable::GetLastUsage() const {
+        return mLastUsage;
+    }
+
+    void Pageable::SetLastUsage(Serial serial) {
+        mLastUsage = serial;
+    }
+
+    uint64_t Pageable::GetLastSubmission() const {
+        return mLastSubmission;
+    }
+
+    void Pageable::SetLastSubmission(Serial serial) {
+        mLastSubmission = serial;
+    }
+
+    MemorySegment Pageable::GetMemorySegment() const {
+        return mMemorySegment;
+    }
+
+    uint64_t Pageable::GetSize() const {
+        return mSize;
+    }
+
+    bool Pageable::IsInResidencyLRUCache() const {
+        return IsInList();
+    }
+
+    void Pageable::IncrementResidencyLock() {
+        mResidencyLockRefCount++;
+    }
+
+    void Pageable::DecrementResidencyLock() {
+        mResidencyLockRefCount--;
+    }
+
+    bool Pageable::IsResidencyLocked() const {
+        return mResidencyLockRefCount != 0;
+    }
+}}  // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/PageableD3D12.h b/src/dawn_native/d3d12/PageableD3D12.h
new file mode 100644
index 0000000..4729b4b
--- /dev/null
+++ b/src/dawn_native/d3d12/PageableD3D12.h
@@ -0,0 +1,80 @@
+// 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_PAGEABLED3D12_H_
+#define DAWNNATIVE_D3D12_PAGEABLED3D12_H_
+
+#include "common/LinkedList.h"
+#include "common/Serial.h"
+#include "dawn_native/D3D12Backend.h"
+#include "dawn_native/d3d12/d3d12_platform.h"
+
+namespace dawn_native { namespace d3d12 {
+    // This class is used to represent ID3D12Pageable allocations, and also serves as a node within
+    // the ResidencyManager's LRU cache. This node is inserted into the LRU-cache when it is first
+    // allocated, and any time it is scheduled to be used by the GPU. This node is removed from the
+    // LRU cache when it is evicted from resident memory due to budget constraints, or when the
+    // pageable allocation is released.
+    class Pageable : public LinkNode<Pageable> {
+      public:
+        Pageable(ComPtr<ID3D12Pageable> d3d12Pageable, MemorySegment memorySegment, uint64_t size);
+        ~Pageable();
+
+        ID3D12Pageable* GetD3D12Pageable() const;
+
+        // We set mLastRecordingSerial to denote the serial this pageable was last recorded to be
+        // used. We must check this serial against the current serial when recording usages to
+        // ensure we do not process residency for this pageable multiple times.
+        Serial GetLastUsage() const;
+        void SetLastUsage(Serial serial);
+
+        // The residency manager must know the last serial that any portion of the pageable was
+        // submitted to be used so that we can ensure this pageable stays resident in memory at
+        // least until that serial has completed.
+        uint64_t GetLastSubmission() const;
+        void SetLastSubmission(Serial serial);
+
+        MemorySegment GetMemorySegment() const;
+
+        uint64_t GetSize() const;
+
+        bool IsInResidencyLRUCache() const;
+
+        // In some scenarios, such as async buffer mapping or descriptor heaps, we must lock
+        // residency to ensure the pageable cannot be evicted. Because multiple buffers may be
+        // mapped in a single heap, we must track the number of resources currently locked.
+        void IncrementResidencyLock();
+        void DecrementResidencyLock();
+        bool IsResidencyLocked() const;
+
+      protected:
+        ComPtr<ID3D12Pageable> mD3d12Pageable;
+
+      private:
+        // mLastUsage denotes the last time this pageable was recorded for use.
+        Serial mLastUsage = 0;
+        // mLastSubmission denotes the last time this pageable was submitted to the GPU. Note that
+        // although this variable often contains the same value as mLastUsage, it can differ in some
+        // situations. When some asynchronous APIs (like SetSubData) are called, mLastUsage is
+        // updated upon the call, but the backend operation is deferred until the next submission
+        // to the GPU. This makes mLastSubmission unique from mLastUsage, and allows us to
+        // accurately identify when a pageable can be evicted.
+        Serial mLastSubmission = 0;
+        MemorySegment mMemorySegment;
+        uint32_t mResidencyLockRefCount = 0;
+        uint64_t mSize = 0;
+    };
+}}  // namespace dawn_native::d3d12
+
+#endif
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
index c65d56a..9ba15df 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
@@ -144,12 +144,12 @@
 
     // Removes a heap from the LRU and returns the least recently used heap when possible. Returns
     // nullptr when nothing further can be evicted.
-    ResultOrError<Heap*> ResidencyManager::RemoveSingleEntryFromLRU(
+    ResultOrError<Pageable*> ResidencyManager::RemoveSingleEntryFromLRU(
         MemorySegmentInfo* memorySegment) {
         ASSERT(!memorySegment->lruCache.empty());
-        Heap* heap = memorySegment->lruCache.head()->value();
+        Pageable* pageable = memorySegment->lruCache.head()->value();
 
-        Serial lastSubmissionSerial = heap->GetLastSubmission();
+        Serial lastSubmissionSerial = pageable->GetLastSubmission();
 
         // If the next candidate for eviction was inserted into the LRU during the current serial,
         // it is because more memory is being used in a single command list than is available.
@@ -164,8 +164,8 @@
             DAWN_TRY(mDevice->WaitForSerial(lastSubmissionSerial));
         }
 
-        heap->RemoveFromList();
-        return heap;
+        pageable->RemoveFromList();
+        return pageable;
     }
 
     MaybeError ResidencyManager::EnsureCanAllocate(uint64_t allocationSize,
@@ -197,16 +197,16 @@
         uint64_t sizeNeededToBeUnderBudget = memoryUsageAfterMakeResident - memorySegment->budget;
         uint64_t sizeEvicted = 0;
         while (sizeEvicted < sizeNeededToBeUnderBudget) {
-            Heap* heap;
-            DAWN_TRY_ASSIGN(heap, RemoveSingleEntryFromLRU(memorySegment));
+            Pageable* pageable;
+            DAWN_TRY_ASSIGN(pageable, RemoveSingleEntryFromLRU(memorySegment));
 
             // If no heap was returned, then nothing more can be evicted.
-            if (heap == nullptr) {
+            if (pageable == nullptr) {
                 break;
             }
 
-            sizeEvicted += heap->GetSize();
-            resourcesToEvict.push_back(heap->GetD3D12Pageable());
+            sizeEvicted += pageable->GetSize();
+            resourcesToEvict.push_back(pageable->GetD3D12Pageable());
         }
 
         if (resourcesToEvict.size() > 0) {
@@ -287,13 +287,13 @@
 
     // Inserts a heap at the bottom of the LRU. The passed heap must be resident or scheduled to
     // become resident within the current serial.
-    void ResidencyManager::TrackResidentAllocation(Heap* heap) {
+    void ResidencyManager::TrackResidentAllocation(Pageable* pageable) {
         if (!mResidencyManagementEnabled) {
             return;
         }
 
-        ASSERT(heap->IsInList() == false);
-        GetMemorySegmentInfo(heap->GetMemorySegment())->lruCache.Append(heap);
+        ASSERT(pageable->IsInList() == false);
+        GetMemorySegmentInfo(pageable->GetMemorySegment())->lruCache.Append(pageable);
     }
 
     // Places an artifical cap on Dawn's budget so we can test in a predictable manner. If used,
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.h b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
index abd6add..1d97d4d 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.h
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
@@ -27,6 +27,7 @@
 
     class Device;
     class Heap;
+    class Pageable;
 
     class ResidencyManager {
       public:
@@ -41,14 +42,14 @@
         uint64_t SetExternalMemoryReservation(MemorySegment segment,
                                               uint64_t requestedReservationSize);
 
-        void TrackResidentAllocation(Heap* heap);
+        void TrackResidentAllocation(Pageable* pageable);
 
         void RestrictBudgetForTesting(uint64_t artificialBudgetCap);
 
       private:
         struct MemorySegmentInfo {
             const DXGI_MEMORY_SEGMENT_GROUP dxgiSegment;
-            LinkedList<Heap> lruCache = {};
+            LinkedList<Pageable> lruCache = {};
             uint64_t budget = 0;
             uint64_t usage = 0;
             uint64_t externalReservation = 0;
@@ -62,7 +63,7 @@
 
         MemorySegmentInfo* GetMemorySegmentInfo(MemorySegment memorySegment);
         MaybeError EnsureCanMakeResident(uint64_t allocationSize, MemorySegmentInfo* memorySegment);
-        ResultOrError<Heap*> RemoveSingleEntryFromLRU(MemorySegmentInfo* memorySegment);
+        ResultOrError<Pageable*> RemoveSingleEntryFromLRU(MemorySegmentInfo* memorySegment);
         void UpdateVideoMemoryInfo();
         void UpdateMemorySegmentInfo(MemorySegmentInfo* segmentInfo);
 
diff --git a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
index 9c36ac5..5a3015b 100644
--- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
+++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
@@ -77,7 +77,7 @@
             return false;
         }
 
-        ID3D12DescriptorHeap* descriptorHeap = mHeap.Get();
+        ID3D12DescriptorHeap* descriptorHeap = mHeap->GetD3D12DescriptorHeap();
 
         const uint64_t heapOffset = mSizeIncrement * startOffset;
 
@@ -99,7 +99,7 @@
     }
 
     ID3D12DescriptorHeap* ShaderVisibleDescriptorAllocator::GetShaderVisibleHeap() const {
-        return mHeap.Get();
+        return mHeap->GetD3D12DescriptorHeap();
     }
 
     void ShaderVisibleDescriptorAllocator::Tick(uint64_t completedSerial) {
@@ -108,7 +108,7 @@
 
     // Creates a GPU descriptor heap that manages descriptors in a FIFO queue.
     MaybeError ShaderVisibleDescriptorAllocator::AllocateAndSwitchShaderVisibleHeap() {
-        ComPtr<ID3D12DescriptorHeap> heap;
+        std::unique_ptr<ShaderVisibleDescriptorHeap> descriptorHeap;
         // 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.
@@ -119,7 +119,7 @@
 
         // Recycle existing heap if possible.
         if (!mPool.empty() && mPool.front().heapSerial <= mDevice->GetCompletedCommandSerial()) {
-            heap = std::move(mPool.front().heap);
+            descriptorHeap = std::move(mPool.front().heap);
             mPool.pop_front();
         }
 
@@ -129,19 +129,23 @@
         const uint32_t descriptorCount = GetD3D12ShaderVisibleHeapSize(
             mHeapType, mDevice->IsToggleEnabled(Toggle::UseD3D12SmallShaderVisibleHeapForTesting));
 
-        if (heap == nullptr) {
+        if (descriptorHeap == nullptr) {
+            ComPtr<ID3D12DescriptorHeap> d3d12DescriptorHeap;
             D3D12_DESCRIPTOR_HEAP_DESC heapDescriptor;
             heapDescriptor.Type = mHeapType;
             heapDescriptor.NumDescriptors = descriptorCount;
             heapDescriptor.Flags = GetD3D12HeapFlags(mHeapType);
             heapDescriptor.NodeMask = 0;
-            DAWN_TRY(CheckOutOfMemoryHRESULT(mDevice->GetD3D12Device()->CreateDescriptorHeap(
-                                                 &heapDescriptor, IID_PPV_ARGS(&heap)),
-                                             "ID3D12Device::CreateDescriptorHeap"));
+            DAWN_TRY(
+                CheckOutOfMemoryHRESULT(mDevice->GetD3D12Device()->CreateDescriptorHeap(
+                                            &heapDescriptor, IID_PPV_ARGS(&d3d12DescriptorHeap)),
+                                        "ID3D12Device::CreateDescriptorHeap"));
+            descriptorHeap = std::make_unique<ShaderVisibleDescriptorHeap>(
+                std::move(d3d12DescriptorHeap), mSizeIncrement * descriptorCount);
         }
 
         // Create a FIFO buffer from the recently created heap.
-        mHeap = std::move(heap);
+        mHeap = std::move(descriptorHeap);
         mAllocator = RingBufferAllocator(descriptorCount);
 
         // Invalidate all bindgroup allocations on previously bound heaps by incrementing the heap
@@ -171,4 +175,15 @@
         return (allocation.GetLastUsageSerial() > mDevice->GetCompletedCommandSerial() &&
                 allocation.GetHeapSerial() == mHeapSerial);
     }
+
+    ShaderVisibleDescriptorHeap::ShaderVisibleDescriptorHeap(
+        ComPtr<ID3D12DescriptorHeap> d3d12DescriptorHeap,
+        uint64_t size)
+        : Pageable(d3d12DescriptorHeap, MemorySegment::Local, size),
+          mD3d12DescriptorHeap(std::move(d3d12DescriptorHeap)) {
+    }
+
+    ID3D12DescriptorHeap* ShaderVisibleDescriptorHeap::GetD3D12DescriptorHeap() const {
+        return mD3d12DescriptorHeap.Get();
+    }
 }}  // 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 be4e839..da8a5e3 100644
--- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
+++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
@@ -17,6 +17,7 @@
 
 #include "dawn_native/Error.h"
 #include "dawn_native/RingBufferAllocator.h"
+#include "dawn_native/d3d12/PageableD3D12.h"
 #include "dawn_native/d3d12/d3d12_platform.h"
 
 #include <list>
@@ -32,6 +33,16 @@
     class Device;
     class GPUDescriptorHeapAllocation;
 
+    class ShaderVisibleDescriptorHeap : public Pageable {
+      public:
+        ShaderVisibleDescriptorHeap(ComPtr<ID3D12DescriptorHeap> d3d12DescriptorHeap,
+                                    uint64_t size);
+        ID3D12DescriptorHeap* GetD3D12DescriptorHeap() const;
+
+      private:
+        ComPtr<ID3D12DescriptorHeap> mD3d12DescriptorHeap;
+    };
+
     class ShaderVisibleDescriptorAllocator {
       public:
         static ResultOrError<std::unique_ptr<ShaderVisibleDescriptorAllocator>> Create(
@@ -62,10 +73,10 @@
       private:
         struct SerialDescriptorHeap {
             Serial heapSerial;
-            ComPtr<ID3D12DescriptorHeap> heap;
+            std::unique_ptr<ShaderVisibleDescriptorHeap> heap;
         };
 
-        ComPtr<ID3D12DescriptorHeap> mHeap;
+        std::unique_ptr<ShaderVisibleDescriptorHeap> mHeap;
         RingBufferAllocator mAllocator;
         std::list<SerialDescriptorHeap> mPool;
         D3D12_DESCRIPTOR_HEAP_TYPE mHeapType;