Descriptor Residency 2: Add Management Logic and Test

Adds logic to lock residency for bound descriptor heaps, then unlock and
insert into the LRU cache when no longer bound. Adds a basic functional
test.

Bug: dawn:193
Change-Id: Idfaaee6b873374c07a0b94b1982ad65353218799
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/21400
Commit-Queue: Brandon Jones <brandon1.jones@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index ba79052..8b42c95 100644
--- a/src/dawn_native/d3d12/BufferD3D12.cpp
+++ b/src/dawn_native/d3d12/BufferD3D12.cpp
@@ -242,7 +242,7 @@
         // The mapped buffer can be accessed at any time, so it must be locked to ensure it is never
         // evicted. This buffer should already have been made resident when it was created.
         Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockHeap(heap));
+        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockAllocation(heap));
 
         mWrittenMappedRange = {0, static_cast<size_t>(GetSize())};
         DAWN_TRY(CheckHRESULT(GetD3D12Resource()->Map(0, &mWrittenMappedRange,
@@ -256,7 +256,7 @@
         // The mapped buffer can be accessed at any time, so we must make the buffer resident and
         // lock it to ensure it is never evicted.
         Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockHeap(heap));
+        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockAllocation(heap));
 
         mWrittenMappedRange = {};
         D3D12_RANGE readRange = {0, static_cast<size_t>(GetSize())};
@@ -272,7 +272,7 @@
         // The mapped buffer can be accessed at any time, so we must make the buffer resident and
         // lock it to ensure it is never evicted.
         Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockHeap(heap));
+        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockAllocation(heap));
 
         mWrittenMappedRange = {0, static_cast<size_t>(GetSize())};
         DAWN_TRY(CheckHRESULT(GetD3D12Resource()->Map(0, &mWrittenMappedRange,
@@ -288,7 +288,7 @@
         // When buffers are mapped, they are locked to keep them in resident memory. We must unlock
         // them when they are unmapped.
         Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-        ToBackend(GetDevice())->GetResidencyManager()->UnlockHeap(heap);
+        ToBackend(GetDevice())->GetResidencyManager()->UnlockAllocation(heap);
         mWrittenMappedRange = {};
         mMappedData = nullptr;
     }
@@ -302,7 +302,7 @@
         // reference on its heap.
         if (IsMapped()) {
             Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-            ToBackend(GetDevice())->GetResidencyManager()->UnlockHeap(heap);
+            ToBackend(GetDevice())->GetResidencyManager()->UnlockAllocation(heap);
         }
 
         ToBackend(GetDevice())->DeallocateMemory(mResourceAllocation);
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index b46000e..7983b98 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -84,14 +84,6 @@
         // Initialize backend services
         mCommandAllocatorManager = std::make_unique<CommandAllocatorManager>(this);
 
-        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;
              countIndex++) {
@@ -115,6 +107,15 @@
         mResidencyManager = std::make_unique<ResidencyManager>(this);
         mResourceAllocatorManager = std::make_unique<ResourceAllocatorManager>(this);
 
+        // ShaderVisibleDescriptorAllocators use the ResidencyManager and must be initialized after.
+        DAWN_TRY_ASSIGN(
+            mSamplerShaderVisibleDescriptorAllocator,
+            ShaderVisibleDescriptorAllocator::Create(this, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER));
+
+        DAWN_TRY_ASSIGN(
+            mViewShaderVisibleDescriptorAllocator,
+            ShaderVisibleDescriptorAllocator::Create(this, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
+
         DAWN_TRY(NextSerial());
 
         // Initialize indirect commands
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
index 9ba15df..b790a80 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
@@ -30,49 +30,49 @@
     }
 
     // Increments number of locks on a heap to ensure the heap remains resident.
-    MaybeError ResidencyManager::LockHeap(Heap* heap) {
+    MaybeError ResidencyManager::LockAllocation(Pageable* pageable) {
         if (!mResidencyManagementEnabled) {
             return {};
         }
 
         // If the heap isn't already resident, make it resident.
-        if (!heap->IsInResidencyLRUCache() && !heap->IsResidencyLocked()) {
-            DAWN_TRY(EnsureCanMakeResident(heap->GetSize(),
-                                           GetMemorySegmentInfo(heap->GetMemorySegment())));
-            ID3D12Pageable* pageable = heap->GetD3D12Pageable();
-            DAWN_TRY(CheckHRESULT(mDevice->GetD3D12Device()->MakeResident(1, &pageable),
+        if (!pageable->IsInResidencyLRUCache() && !pageable->IsResidencyLocked()) {
+            DAWN_TRY(EnsureCanMakeResident(pageable->GetSize(),
+                                           GetMemorySegmentInfo(pageable->GetMemorySegment())));
+            ID3D12Pageable* d3d12Pageable = pageable->GetD3D12Pageable();
+            DAWN_TRY(CheckHRESULT(mDevice->GetD3D12Device()->MakeResident(1, &d3d12Pageable),
                                   "Making a scheduled-to-be-used resource resident"));
         }
 
         // Since we can't evict the heap, it's unnecessary to track the heap in the LRU Cache.
-        if (heap->IsInResidencyLRUCache()) {
-            heap->RemoveFromList();
+        if (pageable->IsInResidencyLRUCache()) {
+            pageable->RemoveFromList();
         }
 
-        heap->IncrementResidencyLock();
+        pageable->IncrementResidencyLock();
 
         return {};
     }
 
     // Decrements number of locks on a heap. When the number of locks becomes zero, the heap is
     // inserted into the LRU cache and becomes eligible for eviction.
-    void ResidencyManager::UnlockHeap(Heap* heap) {
+    void ResidencyManager::UnlockAllocation(Pageable* pageable) {
         if (!mResidencyManagementEnabled) {
             return;
         }
 
-        ASSERT(heap->IsResidencyLocked());
-        ASSERT(!heap->IsInResidencyLRUCache());
-        heap->DecrementResidencyLock();
+        ASSERT(pageable->IsResidencyLocked());
+        ASSERT(!pageable->IsInResidencyLRUCache());
+        pageable->DecrementResidencyLock();
 
         // If another lock still exists on the heap, nothing further should be done.
-        if (heap->IsResidencyLocked()) {
+        if (pageable->IsResidencyLocked()) {
             return;
         }
 
         // When all locks have been removed, the resource remains resident and becomes tracked in
         // the corresponding LRU.
-        TrackResidentAllocation(heap);
+        TrackResidentAllocation(pageable);
     }
 
     // Returns the appropriate MemorySegmentInfo for a given MemorySegment.
@@ -277,6 +277,8 @@
             // overhead by using MakeResident on a secondary thread, or by instead making use of
             // the EnqueueMakeResident function (which is not available on all Windows 10
             // platforms).
+            // TODO(brandon1.jones@intel.com): If MakeResident fails, try evicting some more and
+            // call MakeResident again.
             DAWN_TRY(CheckHRESULT(mDevice->GetD3D12Device()->MakeResident(
                                       heapsToMakeResident.size(), heapsToMakeResident.data()),
                                   "Making scheduled-to-be-used resources resident"));
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.h b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
index 1d97d4d..632abc3 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.h
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
@@ -33,8 +33,8 @@
       public:
         ResidencyManager(Device* device);
 
-        MaybeError LockHeap(Heap* heap);
-        void UnlockHeap(Heap* heap);
+        MaybeError LockAllocation(Pageable* pageable);
+        void UnlockAllocation(Pageable* pageable);
 
         MaybeError EnsureCanAllocate(uint64_t allocationSize, MemorySegment memorySegment);
         MaybeError EnsureHeapsAreResident(Heap** heaps, size_t heapCount);
diff --git a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
index 7f47386..1b01a07 100644
--- a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
@@ -282,7 +282,7 @@
 
         // Before calling CreatePlacedResource, we must ensure the target heap is resident.
         // CreatePlacedResource will fail if it is not.
-        DAWN_TRY(mDevice->GetResidencyManager()->LockHeap(heap));
+        DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(heap));
 
         // With placed resources, a single heap can be reused.
         // The resource placed at an offset is only reclaimed
@@ -300,7 +300,7 @@
 
         // After CreatePlacedResource has finished, the heap can be unlocked from residency. This
         // will insert it into the residency LRU.
-        mDevice->GetResidencyManager()->UnlockHeap(heap);
+        mDevice->GetResidencyManager()->UnlockAllocation(heap);
 
         return ResourceHeapAllocation{allocation.GetInfo(), allocation.GetOffset(),
                                       std::move(placedResource), heap};
diff --git a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
index 5a3015b..258d5c3 100644
--- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
+++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp
@@ -16,6 +16,7 @@
 #include "dawn_native/d3d12/D3D12Error.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h"
+#include "dawn_native/d3d12/ResidencyManagerD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
 
@@ -114,6 +115,7 @@
         // users.
         // TODO(dawn:256): Consider periodically triming to avoid OOM.
         if (mHeap != nullptr) {
+            mDevice->GetResidencyManager()->UnlockAllocation(mHeap.get());
             mPool.push_back({mDevice->GetPendingCommandSerial(), std::move(mHeap)});
         }
 
@@ -130,6 +132,14 @@
             mHeapType, mDevice->IsToggleEnabled(Toggle::UseD3D12SmallShaderVisibleHeapForTesting));
 
         if (descriptorHeap == nullptr) {
+            // The size in bytes of a descriptor heap is best calculated by the increment size
+            // multiplied by the number of descriptors. In practice, this is only an estimate and
+            // the actual size may vary depending on the driver.
+            const uint64_t kSize = mSizeIncrement * descriptorCount;
+
+            DAWN_TRY(
+                mDevice->GetResidencyManager()->EnsureCanAllocate(kSize, MemorySegment::Local));
+
             ComPtr<ID3D12DescriptorHeap> d3d12DescriptorHeap;
             D3D12_DESCRIPTOR_HEAP_DESC heapDescriptor;
             heapDescriptor.Type = mHeapType;
@@ -141,9 +151,10 @@
                                             &heapDescriptor, IID_PPV_ARGS(&d3d12DescriptorHeap)),
                                         "ID3D12Device::CreateDescriptorHeap"));
             descriptorHeap = std::make_unique<ShaderVisibleDescriptorHeap>(
-                std::move(d3d12DescriptorHeap), mSizeIncrement * descriptorCount);
+                std::move(d3d12DescriptorHeap), kSize);
         }
 
+        DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(descriptorHeap.get()));
         // Create a FIFO buffer from the recently created heap.
         mHeap = std::move(descriptorHeap);
         mAllocator = RingBufferAllocator(descriptorCount);
@@ -168,6 +179,15 @@
         return mPool.size();
     }
 
+    bool ShaderVisibleDescriptorAllocator::IsShaderVisibleHeapLockedResidentForTesting() const {
+        return mHeap->IsResidencyLocked();
+    }
+
+    bool ShaderVisibleDescriptorAllocator::IsLastShaderVisibleHeapInLRUForTesting() const {
+        ASSERT(!mPool.empty());
+        return mPool.back().heap->IsInResidencyLRUCache();
+    }
+
     bool ShaderVisibleDescriptorAllocator::IsAllocationStillValid(
         const GPUDescriptorHeapAllocation& allocation) const {
         // Consider valid if allocated for the pending submit and the shader visible heaps
diff --git a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
index da8a5e3..aec20a3 100644
--- a/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
+++ b/src/dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h
@@ -67,6 +67,8 @@
         Serial GetShaderVisibleHeapSerialForTesting() const;
         uint64_t GetShaderVisibleHeapSizeForTesting() const;
         uint64_t GetShaderVisiblePoolSizeForTesting() const;
+        bool IsShaderVisibleHeapLockedResidentForTesting() const;
+        bool IsLastShaderVisibleHeapInLRUForTesting() const;
 
         bool IsAllocationStillValid(const GPUDescriptorHeapAllocation& allocation) const;
 
diff --git a/src/dawn_native/d3d12/StagingBufferD3D12.cpp b/src/dawn_native/d3d12/StagingBufferD3D12.cpp
index 7df5a67..b3aec3f 100644
--- a/src/dawn_native/d3d12/StagingBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/StagingBufferD3D12.cpp
@@ -44,8 +44,8 @@
 
         // The mapped buffer can be accessed at any time, so it must be locked to ensure it is never
         // evicted. This buffer should already have been made resident when it was created.
-        DAWN_TRY(
-            mDevice->GetResidencyManager()->LockHeap(ToBackend(mUploadHeap.GetResourceHeap())));
+        DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(
+            ToBackend(mUploadHeap.GetResourceHeap())));
 
         return CheckHRESULT(GetResource()->Map(0, nullptr, &mMappedPointer), "ID3D12Resource::Map");
     }
@@ -59,7 +59,7 @@
 
         // The underlying heap was locked in residency upon creation. We must unlock it when this
         // buffer becomes unmapped.
-        mDevice->GetResidencyManager()->UnlockHeap(ToBackend(mUploadHeap.GetResourceHeap()));
+        mDevice->GetResidencyManager()->UnlockAllocation(ToBackend(mUploadHeap.GetResourceHeap()));
 
         // Invalidate the CPU virtual address & flush cache (if needed).
         GetResource()->Unmap(0, nullptr);
diff --git a/src/tests/white_box/D3D12ResidencyTests.cpp b/src/tests/white_box/D3D12ResidencyTests.cpp
index 0a272d1..e58e48a 100644
--- a/src/tests/white_box/D3D12ResidencyTests.cpp
+++ b/src/tests/white_box/D3D12ResidencyTests.cpp
@@ -16,7 +16,9 @@
 #include "dawn_native/d3d12/BufferD3D12.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/ResidencyManagerD3D12.h"
+#include "dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h"
 #include "tests/DawnTest.h"
+#include "utils/ComboRenderPipelineDescriptor.h"
 #include "utils/WGPUHelpers.h"
 
 #include <vector>
@@ -32,7 +34,7 @@
     wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
 constexpr wgpu::BufferUsage kNonMappableBufferUsage = wgpu::BufferUsage::CopyDst;
 
-class D3D12ResidencyTests : public DawnTest {
+class D3D12ResidencyTestBase : public DawnTest {
   protected:
     void SetUp() override {
         DawnTest::SetUp();
@@ -62,23 +64,6 @@
         return buffers;
     }
 
-    bool CheckIfBufferIsResident(wgpu::Buffer buffer) const {
-        dawn_native::d3d12::Buffer* d3dBuffer =
-            reinterpret_cast<dawn_native::d3d12::Buffer*>(buffer.Get());
-        return d3dBuffer->CheckIsResidentForTesting();
-    }
-
-    bool CheckAllocationMethod(wgpu::Buffer buffer,
-                               dawn_native::AllocationMethod allocationMethod) const {
-        dawn_native::d3d12::Buffer* d3dBuffer =
-            reinterpret_cast<dawn_native::d3d12::Buffer*>(buffer.Get());
-        return d3dBuffer->CheckAllocationMethodForTesting(allocationMethod);
-    }
-
-    bool IsUMA() const {
-        return reinterpret_cast<dawn_native::d3d12::Device*>(device.Get())->GetDeviceInfo().isUMA;
-    }
-
     wgpu::Buffer CreateBuffer(uint32_t bufferSize, wgpu::BufferUsage usage) {
         wgpu::BufferDescriptor descriptor;
 
@@ -88,26 +73,6 @@
         return device.CreateBuffer(&descriptor);
     }
 
-    static void MapReadCallback(WGPUBufferMapAsyncStatus status,
-                                const void* data,
-                                uint64_t,
-                                void* userdata) {
-        ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
-        ASSERT_NE(nullptr, data);
-
-        static_cast<D3D12ResidencyTests*>(userdata)->mMappedReadData = data;
-    }
-
-    static void MapWriteCallback(WGPUBufferMapAsyncStatus status,
-                                 void* data,
-                                 uint64_t,
-                                 void* userdata) {
-        ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
-        ASSERT_NE(nullptr, data);
-
-        static_cast<D3D12ResidencyTests*>(userdata)->mMappedWriteData = data;
-    }
-
     void TouchBuffers(uint32_t beginIndex,
                       uint32_t numBuffers,
                       const std::vector<wgpu::Buffer>& bufferSet) {
@@ -126,8 +91,50 @@
     const void* mMappedReadData = nullptr;
 };
 
+class D3D12ResourceResidencyTests : public D3D12ResidencyTestBase {
+  protected:
+    bool CheckAllocationMethod(wgpu::Buffer buffer,
+                               dawn_native::AllocationMethod allocationMethod) const {
+        dawn_native::d3d12::Buffer* d3dBuffer =
+            reinterpret_cast<dawn_native::d3d12::Buffer*>(buffer.Get());
+        return d3dBuffer->CheckAllocationMethodForTesting(allocationMethod);
+    }
+
+    bool CheckIfBufferIsResident(wgpu::Buffer buffer) const {
+        dawn_native::d3d12::Buffer* d3dBuffer =
+            reinterpret_cast<dawn_native::d3d12::Buffer*>(buffer.Get());
+        return d3dBuffer->CheckIsResidentForTesting();
+    }
+
+    bool IsUMA() const {
+        return reinterpret_cast<dawn_native::d3d12::Device*>(device.Get())->GetDeviceInfo().isUMA;
+    }
+
+    static void MapReadCallback(WGPUBufferMapAsyncStatus status,
+                                const void* data,
+                                uint64_t,
+                                void* userdata) {
+        ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
+        ASSERT_NE(nullptr, data);
+
+        static_cast<D3D12ResourceResidencyTests*>(userdata)->mMappedReadData = data;
+    }
+
+    static void MapWriteCallback(WGPUBufferMapAsyncStatus status,
+                                 void* data,
+                                 uint64_t,
+                                 void* userdata) {
+        ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
+        ASSERT_NE(nullptr, data);
+
+        static_cast<D3D12ResourceResidencyTests*>(userdata)->mMappedWriteData = data;
+    }
+};
+
+class D3D12DescriptorResidencyTests : public D3D12ResidencyTestBase {};
+
 // Check that resources existing on suballocated heaps are made resident and evicted correctly.
-TEST_P(D3D12ResidencyTests, OvercommitSmallResources) {
+TEST_P(D3D12ResourceResidencyTests, OvercommitSmallResources) {
     // TODO(http://crbug.com/dawn/416): Tests fails on Intel HD 630 bot.
     DAWN_SKIP_TEST_IF(IsIntel() && IsBackendValidationEnabled());
 
@@ -169,7 +176,7 @@
 
 // Check that resources existing on directly allocated heaps are made resident and evicted
 // correctly.
-TEST_P(D3D12ResidencyTests, OvercommitLargeResources) {
+TEST_P(D3D12ResourceResidencyTests, OvercommitLargeResources) {
     // Create directly-allocated buffers to fill half the budget.
     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
         kDirectlyAllocatedResourceSize,
@@ -205,7 +212,7 @@
 }
 
 // Check that calling MapReadAsync makes the buffer resident and keeps it locked resident.
-TEST_P(D3D12ResidencyTests, AsyncMappedBufferRead) {
+TEST_P(D3D12ResourceResidencyTests, AsyncMappedBufferRead) {
     // Create a mappable buffer.
     wgpu::Buffer buffer = CreateBuffer(4, kMapReadBufferUsage);
 
@@ -248,7 +255,7 @@
 }
 
 // Check that calling MapWriteAsync makes the buffer resident and keeps it locked resident.
-TEST_P(D3D12ResidencyTests, AsyncMappedBufferWrite) {
+TEST_P(D3D12ResourceResidencyTests, AsyncMappedBufferWrite) {
     // Create a mappable buffer.
     wgpu::Buffer buffer = CreateBuffer(4, kMapWriteBufferUsage);
     // The mappable buffer should be resident.
@@ -287,7 +294,7 @@
 }
 
 // Check that overcommitting in a single submit works, then make sure the budget is enforced after.
-TEST_P(D3D12ResidencyTests, OvercommitInASingleSubmit) {
+TEST_P(D3D12ResourceResidencyTests, OvercommitInASingleSubmit) {
     // Create enough buffers to exceed the budget
     constexpr uint32_t numberOfBuffersToOvercommit = 5;
     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
@@ -313,7 +320,7 @@
     }
 }
 
-TEST_P(D3D12ResidencyTests, SetExternalReservation) {
+TEST_P(D3D12ResourceResidencyTests, SetExternalReservation) {
     // Set an external reservation of 20% the budget. We should succesfully reserve the amount we
     // request.
     uint64_t amountReserved = dawn_native::d3d12::SetExternalMemoryReservation(
@@ -328,4 +335,89 @@
     }
 }
 
-DAWN_INSTANTIATE_TEST(D3D12ResidencyTests, D3D12Backend());
+// Checks that when a descriptor heap is bound, it is locked resident. Also checks that when a
+// previous descriptor heap becomes unbound, it is unlocked, placed in the LRU and can be evicted.
+TEST_P(D3D12DescriptorResidencyTests, SwitchedViewHeapResidency) {
+    utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
+
+    // Fill in a view heap with "view only" bindgroups (1x view per group) by creating a
+    // view bindgroup each draw. After HEAP_SIZE + 1 draws, the heaps must switch over.
+    renderPipelineDescriptor.vertexStage.module =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        void main() {
+            const vec2 pos[3] = vec2[3](vec2(-1.f, 1.f), vec2(1.f, 1.f), vec2(-1.f, -1.f));
+            gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
+        })");
+
+    renderPipelineDescriptor.cFragmentStage.module =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout (location = 0) out vec4 fragColor;
+        layout (set = 0, binding = 0) uniform colorBuffer {
+            vec4 color;
+        };
+        void main() {
+            fragColor = color;
+        })");
+
+    wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&renderPipelineDescriptor);
+    constexpr uint32_t kSize = 512;
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kSize, kSize);
+
+    wgpu::SamplerDescriptor samplerDesc = utils::GetDefaultSamplerDescriptor();
+    wgpu::Sampler sampler = device.CreateSampler(&samplerDesc);
+
+    dawn_native::d3d12::Device* d3dDevice =
+        reinterpret_cast<dawn_native::d3d12::Device*>(device.Get());
+
+    dawn_native::d3d12::ShaderVisibleDescriptorAllocator* allocator =
+        d3dDevice->GetViewShaderVisibleDescriptorAllocator();
+    const uint64_t heapSize = allocator->GetShaderVisibleHeapSizeForTesting();
+
+    const Serial heapSerial = allocator->GetShaderVisibleHeapSerialForTesting();
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+
+        pass.SetPipeline(renderPipeline);
+
+        std::array<float, 4> redColor = {1, 0, 0, 1};
+        wgpu::Buffer uniformBuffer = utils::CreateBufferFromData(
+            device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform);
+
+        for (uint32_t i = 0; i < heapSize + 1; ++i) {
+            pass.SetBindGroup(0, utils::MakeBindGroup(device, renderPipeline.GetBindGroupLayout(0),
+                                                      {{0, uniformBuffer, 0, sizeof(redColor)}}));
+            pass.Draw(3);
+        }
+
+        pass.EndPass();
+    }
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    // Check the heap serial to ensure the heap has switched.
+    EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + 1);
+
+    // Check that currrently bound ShaderVisibleHeap is locked resident.
+    EXPECT_TRUE(allocator->IsShaderVisibleHeapLockedResidentForTesting());
+    // Check that the previously bound ShaderVisibleHeap was unlocked and was placed in the LRU
+    // cache.
+    EXPECT_TRUE(allocator->IsLastShaderVisibleHeapInLRUForTesting());
+    // Allocate enough buffers to exceed the budget, which will purge everything from the Residency
+    // LRU.
+    AllocateBuffers(kDirectlyAllocatedResourceSize,
+                    kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
+                    kNonMappableBufferUsage);
+    // Check that currrently bound ShaderVisibleHeap remained locked resident.
+    EXPECT_TRUE(allocator->IsShaderVisibleHeapLockedResidentForTesting());
+    // Check that the previously bound ShaderVisibleHeap has been evicted from the LRU cache.
+    EXPECT_FALSE(allocator->IsLastShaderVisibleHeapInLRUForTesting());
+}
+
+DAWN_INSTANTIATE_TEST(D3D12ResourceResidencyTests, D3D12Backend());
+DAWN_INSTANTIATE_TEST(D3D12DescriptorResidencyTests,
+                      D3D12Backend({"use_d3d12_small_shader_visible_heap"}));