Non-Local Residency 2: Implement Non-Local Management Logic

Implements logic for managing the NON_LOCAL memory segment for UPLOAD
and READBACK heaps on Non-UMA devices.

Bug: dawn:193
Change-Id: I2426bf6b5f7a7ccd4420f830f344379af9faf73c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/19901
Reviewed-by: Rafael Cintron <rafael.cintron@microsoft.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Brandon Jones <brandon1.jones@intel.com>
diff --git a/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp b/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp
index a038714..756c907 100644
--- a/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp
+++ b/src/dawn_native/d3d12/HeapAllocatorD3D12.cpp
@@ -22,8 +22,12 @@
 
     HeapAllocator::HeapAllocator(Device* device,
                                  D3D12_HEAP_TYPE heapType,
-                                 D3D12_HEAP_FLAGS heapFlags)
-        : mDevice(device), mHeapType(heapType), mHeapFlags(heapFlags) {
+                                 D3D12_HEAP_FLAGS heapFlags,
+                                 MemorySegment memorySegment)
+        : mDevice(device),
+          mHeapType(heapType),
+          mHeapFlags(heapFlags),
+          mMemorySegment(memorySegment) {
     }
 
     ResultOrError<std::unique_ptr<ResourceHeapBase>> HeapAllocator::AllocateResourceHeap(
@@ -44,7 +48,7 @@
 
         // CreateHeap will implicitly make the created heap resident. We must ensure enough free
         // memory exists before allocating to avoid an out-of-memory error when overcommitted.
-        DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanMakeResident(size));
+        DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanAllocate(size, mMemorySegment));
 
         ComPtr<ID3D12Heap> d3d12Heap;
         DAWN_TRY(CheckOutOfMemoryHRESULT(
@@ -52,7 +56,7 @@
             "ID3D12Device::CreateHeap"));
 
         std::unique_ptr<ResourceHeapBase> heapBase =
-            std::make_unique<Heap>(std::move(d3d12Heap), heapDesc.Properties.Type, size);
+            std::make_unique<Heap>(std::move(d3d12Heap), mMemorySegment, size);
 
         // Calling CreateHeap implicitly calls MakeResident on the new heap. We must track this to
         // avoid calling MakeResident a second time.
diff --git a/src/dawn_native/d3d12/HeapAllocatorD3D12.h b/src/dawn_native/d3d12/HeapAllocatorD3D12.h
index 34b435d..53254c5 100644
--- a/src/dawn_native/d3d12/HeapAllocatorD3D12.h
+++ b/src/dawn_native/d3d12/HeapAllocatorD3D12.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_D3D12_HEAPALLOCATORD3D12_H_
 #define DAWNNATIVE_D3D12_HEAPALLOCATORD3D12_H_
 
+#include "dawn_native/D3D12Backend.h"
 #include "dawn_native/ResourceHeapAllocator.h"
 #include "dawn_native/d3d12/d3d12_platform.h"
 
@@ -25,7 +26,10 @@
     // Wrapper to allocate a D3D12 heap.
     class HeapAllocator : public ResourceHeapAllocator {
       public:
-        HeapAllocator(Device* device, D3D12_HEAP_TYPE heapType, D3D12_HEAP_FLAGS heapFlags);
+        HeapAllocator(Device* device,
+                      D3D12_HEAP_TYPE heapType,
+                      D3D12_HEAP_FLAGS heapFlags,
+                      MemorySegment memorySegment);
         ~HeapAllocator() override = default;
 
         ResultOrError<std::unique_ptr<ResourceHeapBase>> AllocateResourceHeap(
@@ -36,6 +40,7 @@
         Device* mDevice;
         D3D12_HEAP_TYPE mHeapType;
         D3D12_HEAP_FLAGS mHeapFlags;
+        MemorySegment mMemorySegment;
     };
 
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/HeapD3D12.cpp b/src/dawn_native/d3d12/HeapD3D12.cpp
index a8edcda..3d3f04f 100644
--- a/src/dawn_native/d3d12/HeapD3D12.cpp
+++ b/src/dawn_native/d3d12/HeapD3D12.cpp
@@ -15,22 +15,23 @@
 #include "dawn_native/d3d12/HeapD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
-    Heap::Heap(ComPtr<ID3D12Pageable> d3d12Pageable, D3D12_HEAP_TYPE d3d12HeapType, uint64_t size)
-        : mD3d12Pageable(std::move(d3d12Pageable)), mD3d12HeapType(d3d12HeapType), mSize(size) {
+    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
+        // 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();
         }
     }
 
-    // This function should only be used when mD3D12Pageable was initialized from a ID3D12Pageable
-    // that was initially created as an ID3D12Heap (i.e. SubAllocation). If the ID3D12Pageable was
-    // initially created as an ID3D12Resource (i.e. DirectAllocation), then use GetD3D12Pageable().
+    // This function should only be used when mD3D12Pageable was initialized from a
+    // ID3D12Pageable that was initially created as an ID3D12Heap (i.e. SubAllocation). If the
+    // ID3D12Pageable was initially created as an ID3D12Resource (i.e. DirectAllocation), then
+    // use GetD3D12Pageable().
     ComPtr<ID3D12Heap> Heap::GetD3D12Heap() const {
         ComPtr<ID3D12Heap> heap;
         HRESULT result = mD3d12Pageable.As(&heap);
@@ -42,8 +43,8 @@
         return mD3d12Pageable;
     }
 
-    D3D12_HEAP_TYPE Heap::GetD3D12HeapType() const {
-        return mD3d12HeapType;
+    MemorySegment Heap::GetMemorySegment() const {
+        return mMemorySegment;
     }
 
     Serial Heap::GetLastUsage() const {
diff --git a/src/dawn_native/d3d12/HeapD3D12.h b/src/dawn_native/d3d12/HeapD3D12.h
index 3ec17a4..1ce108d 100644
--- a/src/dawn_native/d3d12/HeapD3D12.h
+++ b/src/dawn_native/d3d12/HeapD3D12.h
@@ -17,11 +17,14 @@
 
 #include "common/LinkedList.h"
 #include "common/Serial.h"
+#include "dawn_native/D3D12Backend.h"
 #include "dawn_native/ResourceHeap.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
@@ -29,12 +32,12 @@
     // is destroyed.
     class Heap : public ResourceHeapBase, public LinkNode<Heap> {
       public:
-        Heap(ComPtr<ID3D12Pageable> d3d12Pageable, D3D12_HEAP_TYPE heapType, uint64_t size);
+        Heap(ComPtr<ID3D12Pageable> d3d12Pageable, MemorySegment memorySegment, uint64_t size);
         ~Heap();
 
         ComPtr<ID3D12Heap> GetD3D12Heap() const;
         ComPtr<ID3D12Pageable> GetD3D12Pageable() const;
-        D3D12_HEAP_TYPE GetD3D12HeapType() 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
@@ -61,7 +64,7 @@
 
       private:
         ComPtr<ID3D12Pageable> mD3d12Pageable;
-        D3D12_HEAP_TYPE mD3d12HeapType;
+        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
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
index be91612..dd7002d 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
@@ -1,4 +1,3 @@
-#include "ResidencyManagerD3D12.h"
 // Copyright 2020 The Dawn Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -36,18 +35,13 @@
             return {};
         }
 
-        // Depending on device architecture, the heap may not need tracked.
-        if (!ShouldTrackHeap(heap)) {
-            return {};
-        }
-
         // If the heap isn't already resident, make it resident.
         if (!heap->IsInResidencyLRUCache() && !heap->IsResidencyLocked()) {
-            DAWN_TRY(EnsureCanMakeResident(heap->GetSize()));
+            DAWN_TRY(EnsureCanMakeResident(heap->GetSize(),
+                                           GetMemorySegmentInfo(heap->GetMemorySegment())));
             ID3D12Pageable* pageable = heap->GetD3D12Pageable().Get();
             DAWN_TRY(CheckHRESULT(mDevice->GetD3D12Device()->MakeResident(1, &pageable),
-                                  "Making a scheduled-to-be-used resource resident in "
-                                  "device local memory"));
+                                  "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.
@@ -67,19 +61,31 @@
             return;
         }
 
-        // Depending on device architecture, the heap may not need tracked.
-        if (!ShouldTrackHeap(heap)) {
-            return;
-        }
-
         ASSERT(heap->IsResidencyLocked());
         ASSERT(!heap->IsInResidencyLRUCache());
         heap->DecrementResidencyLock();
 
+        // If another lock still exists on the heap, nothing further should be done.
+        if (heap->IsResidencyLocked()) {
+            return;
+        }
+
         // When all locks have been removed, the resource remains resident and becomes tracked in
-        // the LRU.
-        if (!heap->IsResidencyLocked()) {
-            mLRUCache.Append(heap);
+        // the corresponding LRU.
+        TrackResidentAllocation(heap);
+    }
+
+    // Returns the appropriate MemorySegmentInfo for a given MemorySegment.
+    ResidencyManager::MemorySegmentInfo* ResidencyManager::GetMemorySegmentInfo(
+        MemorySegment memorySegment) {
+        switch (memorySegment) {
+            case MemorySegment::Local:
+                return &mVideoMemoryInfo.local;
+            case MemorySegment::NonLocal:
+                ASSERT(!mDevice->GetDeviceInfo().isUMA);
+                return &mVideoMemoryInfo.nonLocal;
+            default:
+                UNREACHABLE();
         }
     }
 
@@ -88,17 +94,7 @@
     // that the requested reservation when under pressure.
     uint64_t ResidencyManager::SetExternalMemoryReservation(MemorySegment segment,
                                                             uint64_t requestedReservationSize) {
-        MemorySegmentInfo* segmentInfo = nullptr;
-        switch (segment) {
-            case MemorySegment::Local:
-                segmentInfo = &mVideoMemoryInfo.local;
-                break;
-            case MemorySegment::NonLocal:
-                segmentInfo = &mVideoMemoryInfo.nonLocal;
-                break;
-            default:
-                UNREACHABLE();
-        }
+        MemorySegmentInfo* segmentInfo = GetMemorySegmentInfo(segment);
 
         segmentInfo->externalRequest = requestedReservationSize;
 
@@ -146,11 +142,13 @@
             (queryVideoMemoryInfo.Budget - segmentInfo->externalReservation) * kBudgetCap;
     }
 
-    // Removes from the LRU and returns the least recently used heap when possible. Returns nullptr
-    // when nothing further can be evicted.
-    ResultOrError<Heap*> ResidencyManager::RemoveSingleEntryFromLRU() {
-        ASSERT(!mLRUCache.empty());
-        Heap* heap = mLRUCache.head()->value();
+    // 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(
+        MemorySegmentInfo* memorySegment) {
+        ASSERT(!memorySegment->lruCache.empty());
+        Heap* heap = memorySegment->lruCache.head()->value();
+
         Serial lastSubmissionSerial = heap->GetLastSubmission();
 
         // If the next candidate for eviction was inserted into the LRU during the current serial,
@@ -170,30 +168,37 @@
         return heap;
     }
 
-    // Any time we need to make something resident in local memory, we must check that we have
-    // enough free memory to make the new object resident while also staying within our budget.
-    // If there isn't enough memory, we should evict until there is.
-    MaybeError ResidencyManager::EnsureCanMakeResident(uint64_t sizeToMakeResident) {
+    MaybeError ResidencyManager::EnsureCanAllocate(uint64_t allocationSize,
+                                                   MemorySegment memorySegment) {
         if (!mResidencyManagementEnabled) {
             return {};
         }
 
-        UpdateMemorySegmentInfo(&mVideoMemoryInfo.local);
+        return EnsureCanMakeResident(allocationSize, GetMemorySegmentInfo(memorySegment));
+    }
 
-        uint64_t memoryUsageAfterMakeResident = sizeToMakeResident + mVideoMemoryInfo.local.usage;
+    // Any time we need to make something resident, we must check that we have enough free memory to
+    // make the new object resident while also staying within budget. If there isn't enough
+    // memory, we should evict until there is.
+    MaybeError ResidencyManager::EnsureCanMakeResident(uint64_t sizeToMakeResident,
+                                                       MemorySegmentInfo* memorySegment) {
+        ASSERT(mResidencyManagementEnabled);
+
+        UpdateMemorySegmentInfo(memorySegment);
+
+        uint64_t memoryUsageAfterMakeResident = sizeToMakeResident + memorySegment->usage;
 
         // Return when we can call MakeResident and remain under budget.
-        if (memoryUsageAfterMakeResident < mVideoMemoryInfo.local.budget) {
+        if (memoryUsageAfterMakeResident < memorySegment->budget) {
             return {};
         }
 
         std::vector<ID3D12Pageable*> resourcesToEvict;
-        uint64_t sizeNeededToBeUnderBudget =
-            memoryUsageAfterMakeResident - mVideoMemoryInfo.local.budget;
+        uint64_t sizeNeededToBeUnderBudget = memoryUsageAfterMakeResident - memorySegment->budget;
         uint64_t sizeEvicted = 0;
         while (sizeEvicted < sizeNeededToBeUnderBudget) {
             Heap* heap;
-            DAWN_TRY_ASSIGN(heap, RemoveSingleEntryFromLRU());
+            DAWN_TRY_ASSIGN(heap, RemoveSingleEntryFromLRU(memorySegment));
 
             // If no heap was returned, then nothing more can be evicted.
             if (heap == nullptr) {
@@ -207,29 +212,12 @@
         if (resourcesToEvict.size() > 0) {
             DAWN_TRY(CheckHRESULT(
                 mDevice->GetD3D12Device()->Evict(resourcesToEvict.size(), resourcesToEvict.data()),
-                "Evicting resident heaps to free device local memory"));
+                "Evicting resident heaps to free memory"));
         }
 
         return {};
     }
 
-    // Ensure that we are only tracking heaps that exist in DXGI_MEMORY_SEGMENT_LOCAL.
-    bool ResidencyManager::ShouldTrackHeap(Heap* heap) const {
-        D3D12_HEAP_PROPERTIES heapProperties =
-            mDevice->GetD3D12Device()->GetCustomHeapProperties(0, heap->GetD3D12HeapType());
-
-        if (mDevice->GetDeviceInfo().isUMA) {
-            // On UMA devices, MEMORY_POOL_L0 corresponds to MEMORY_SEGMENT_LOCAL, so we must track
-            // heaps in MEMORY_POOL_L0. For UMA, all heaps types exist in MEMORY_POOL_L0.
-            return heapProperties.MemoryPoolPreference == D3D12_MEMORY_POOL_L0;
-        }
-
-        // On non-UMA devices, MEMORY_POOL_L1 corresponds to MEMORY_SEGMENT_LOCAL, so only track the
-        // heap if it is in MEMORY_POOL_L1. For non-UMA, DEFAULT heaps exist in MEMORY_POOL_L1,
-        // while READBACK and UPLOAD heaps exist in MEMORY_POOL_L0.
-        return heapProperties.MemoryPoolPreference == D3D12_MEMORY_POOL_L1;
-    }
-
     // Given a list of heaps that are pending usage, this function will estimate memory needed,
     // evict resources until enough space is available, then make resident any heaps scheduled for
     // usage.
@@ -239,17 +227,13 @@
         }
 
         std::vector<ID3D12Pageable*> heapsToMakeResident;
-        uint64_t sizeToMakeResident = 0;
+        uint64_t localSizeToMakeResident = 0;
+        uint64_t nonLocalSizeToMakeResident = 0;
 
         Serial pendingCommandSerial = mDevice->GetPendingCommandSerial();
         for (size_t i = 0; i < heapCount; i++) {
             Heap* heap = heaps[i];
 
-            // Depending on device architecture, the heap may not need tracked.
-            if (!ShouldTrackHeap(heap)) {
-                continue;
-            }
-
             // Heaps that are locked resident are not tracked in the LRU cache.
             if (heap->IsResidencyLocked()) {
                 continue;
@@ -261,7 +245,11 @@
                 heap->RemoveFromList();
             } else {
                 heapsToMakeResident.push_back(heap->GetD3D12Pageable().Get());
-                sizeToMakeResident += heap->GetSize();
+                if (heap->GetMemorySegment() == MemorySegment::Local) {
+                    localSizeToMakeResident += heap->GetSize();
+                } else {
+                    nonLocalSizeToMakeResident += heap->GetSize();
+                }
             }
 
             // If we submit a command list to the GPU, we must ensure that heaps referenced by that
@@ -270,12 +258,20 @@
             // eligible for eviction, even though some evictions may be possible.
             heap->SetLastSubmission(pendingCommandSerial);
 
-            mLRUCache.Append(heap);
+            // Insert the heap into the appropriate LRU.
+            TrackResidentAllocation(heap);
+        }
+
+        if (localSizeToMakeResident > 0) {
+            DAWN_TRY(EnsureCanMakeResident(localSizeToMakeResident, &mVideoMemoryInfo.local));
+        }
+
+        if (nonLocalSizeToMakeResident > 0) {
+            ASSERT(!mDevice->GetDeviceInfo().isUMA);
+            DAWN_TRY(EnsureCanMakeResident(nonLocalSizeToMakeResident, &mVideoMemoryInfo.nonLocal));
         }
 
         if (heapsToMakeResident.size() != 0) {
-            DAWN_TRY(EnsureCanMakeResident(sizeToMakeResident));
-
             // Note that MakeResident is a synchronous function and can add a significant
             // overhead to command recording. In the future, it may be possible to decrease this
             // overhead by using MakeResident on a secondary thread, or by instead making use of
@@ -283,32 +279,28 @@
             // platforms).
             DAWN_TRY(CheckHRESULT(mDevice->GetD3D12Device()->MakeResident(
                                       heapsToMakeResident.size(), heapsToMakeResident.data()),
-                                  "Making scheduled-to-be-used resources resident in "
-                                  "device local memory"));
+                                  "Making scheduled-to-be-used resources resident"));
         }
 
         return {};
     }
 
-    // When a new heap is allocated, the heap will be made resident upon creation. We must track
-    // when this happens to avoid calling MakeResident a second time.
+    // 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) {
         if (!mResidencyManagementEnabled) {
             return;
         }
 
-        // Depending on device architecture and heap type, the heap may not need tracked.
-        if (!ShouldTrackHeap(heap)) {
-            return;
-        }
-
-        mLRUCache.Append(heap);
+        ASSERT(heap->IsInList() == false);
+        GetMemorySegmentInfo(heap->GetMemorySegment())->lruCache.Append(heap);
     }
 
     // Places an artifical cap on Dawn's budget so we can test in a predictable manner. If used,
     // this function must be called before any resources have been created.
     void ResidencyManager::RestrictBudgetForTesting(uint64_t artificialBudgetCap) {
-        ASSERT(mLRUCache.empty());
+        ASSERT(mVideoMemoryInfo.local.lruCache.empty());
+        ASSERT(mVideoMemoryInfo.nonLocal.lruCache.empty());
         ASSERT(!mRestrictBudgetForTesting);
 
         mRestrictBudgetForTesting = true;
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.h b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
index 29d4e1d..abd6add 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.h
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
@@ -34,7 +34,8 @@
 
         MaybeError LockHeap(Heap* heap);
         void UnlockHeap(Heap* heap);
-        MaybeError EnsureCanMakeResident(uint64_t allocationSize);
+
+        MaybeError EnsureCanAllocate(uint64_t allocationSize, MemorySegment memorySegment);
         MaybeError EnsureHeapsAreResident(Heap** heaps, size_t heapCount);
 
         uint64_t SetExternalMemoryReservation(MemorySegment segment,
@@ -47,24 +48,25 @@
       private:
         struct MemorySegmentInfo {
             const DXGI_MEMORY_SEGMENT_GROUP dxgiSegment;
-            uint64_t budget;
-            uint64_t usage;
-            uint64_t externalReservation;
-            uint64_t externalRequest;
+            LinkedList<Heap> lruCache = {};
+            uint64_t budget = 0;
+            uint64_t usage = 0;
+            uint64_t externalReservation = 0;
+            uint64_t externalRequest = 0;
         };
 
         struct VideoMemoryInfo {
-            MemorySegmentInfo local = {DXGI_MEMORY_SEGMENT_GROUP_LOCAL, 0, 0, 0, 0};
-            MemorySegmentInfo nonLocal = {DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, 0, 0, 0, 0};
+            MemorySegmentInfo local = {DXGI_MEMORY_SEGMENT_GROUP_LOCAL};
+            MemorySegmentInfo nonLocal = {DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL};
         };
 
-        ResultOrError<Heap*> RemoveSingleEntryFromLRU();
-        bool ShouldTrackHeap(Heap* heap) const;
+        MemorySegmentInfo* GetMemorySegmentInfo(MemorySegment memorySegment);
+        MaybeError EnsureCanMakeResident(uint64_t allocationSize, MemorySegmentInfo* memorySegment);
+        ResultOrError<Heap*> RemoveSingleEntryFromLRU(MemorySegmentInfo* memorySegment);
         void UpdateVideoMemoryInfo();
         void UpdateMemorySegmentInfo(MemorySegmentInfo* segmentInfo);
 
         Device* mDevice;
-        LinkedList<Heap> mLRUCache;
         bool mResidencyManagementEnabled = false;
         bool mRestrictBudgetForTesting = false;
         VideoMemoryInfo mVideoMemoryInfo = {};
diff --git a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
index f881b8d..f9c9316 100644
--- a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
@@ -22,6 +22,21 @@
 
 namespace dawn_native { namespace d3d12 {
     namespace {
+        MemorySegment GetMemorySegment(Device* device, D3D12_HEAP_TYPE heapType) {
+            if (device->GetDeviceInfo().isUMA) {
+                return MemorySegment::Local;
+            }
+
+            D3D12_HEAP_PROPERTIES heapProperties =
+                device->GetD3D12Device()->GetCustomHeapProperties(0, heapType);
+
+            if (heapProperties.MemoryPoolPreference == D3D12_MEMORY_POOL_L1) {
+                return MemorySegment::Local;
+            }
+
+            return MemorySegment::NonLocal;
+        }
+
         D3D12_HEAP_TYPE GetD3D12HeapType(ResourceHeapKind resourceHeapKind) {
             switch (resourceHeapKind) {
                 case Readback_OnlyBuffers:
@@ -143,7 +158,8 @@
         for (uint32_t i = 0; i < ResourceHeapKind::EnumCount; i++) {
             const ResourceHeapKind resourceHeapKind = static_cast<ResourceHeapKind>(i);
             mHeapAllocators[i] = std::make_unique<HeapAllocator>(
-                mDevice, GetD3D12HeapType(resourceHeapKind), GetD3D12HeapFlags(resourceHeapKind));
+                mDevice, GetD3D12HeapType(resourceHeapKind), GetD3D12HeapFlags(resourceHeapKind),
+                GetMemorySegment(device, GetD3D12HeapType(resourceHeapKind)));
             mSubAllocatedResourceAllocators[i] = std::make_unique<BuddyMemoryAllocator>(
                 kMaxHeapSize, kMinHeapSize, mHeapAllocators[i].get());
         }
@@ -310,7 +326,8 @@
         // CreateCommittedResource will implicitly make the created resource resident. We must
         // ensure enough free memory exists before allocating to avoid an out-of-memory error when
         // overcommitted.
-        DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanMakeResident(resourceInfo.SizeInBytes));
+        DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanAllocate(
+            resourceInfo.SizeInBytes, GetMemorySegment(mDevice, heapType)));
 
         // Note: Heap flags are inferred by the resource descriptor and do not need to be explicitly
         // provided to CreateCommittedResource.
@@ -326,7 +343,8 @@
         // heap granularity, every directly allocated ResourceHeapAllocation also stores a Heap
         // object. This object is created manually, and must be deleted manually upon deallocation
         // of the committed resource.
-        Heap* heap = new Heap(committedResource, heapType, resourceInfo.SizeInBytes);
+        Heap* heap = new Heap(committedResource, GetMemorySegment(mDevice, heapType),
+                              resourceInfo.SizeInBytes);
 
         // Calling CreateCommittedResource implicitly calls MakeResident on the resource. We must
         // track this to avoid calling MakeResident a second time.
diff --git a/src/tests/white_box/D3D12ResidencyTests.cpp b/src/tests/white_box/D3D12ResidencyTests.cpp
index 4386cdd..5bb3ea8 100644
--- a/src/tests/white_box/D3D12ResidencyTests.cpp
+++ b/src/tests/white_box/D3D12ResidencyTests.cpp
@@ -26,6 +26,12 @@
 constexpr uint32_t kSuballocatedResourceSize = 1000000;       // 1MB
 constexpr uint32_t kSourceBufferSize = 4;                     // 4B
 
+constexpr wgpu::BufferUsage kMapReadBufferUsage =
+    wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
+constexpr wgpu::BufferUsage kMapWriteBufferUsage =
+    wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
+constexpr wgpu::BufferUsage kNonMappableBufferUsage = wgpu::BufferUsage::CopyDst;
+
 class D3D12ResidencyTests : public DawnTest {
   protected:
     void TestSetUp() override {
@@ -43,11 +49,13 @@
             utils::CreateBufferFromData(device, &one, sizeof(one), wgpu::BufferUsage::CopySrc);
     }
 
-    std::vector<wgpu::Buffer> AllocateBuffers(uint32_t bufferSize, uint32_t numberOfBuffers) {
+    std::vector<wgpu::Buffer> AllocateBuffers(uint32_t bufferSize,
+                                              uint32_t numberOfBuffers,
+                                              wgpu::BufferUsage usage) {
         std::vector<wgpu::Buffer> buffers;
 
         for (uint64_t i = 0; i < numberOfBuffers; i++) {
-            buffers.push_back(CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst));
+            buffers.push_back(CreateBuffer(bufferSize, usage));
         }
 
         return buffers;
@@ -121,7 +129,8 @@
 TEST_P(D3D12ResidencyTests, OvercommitSmallResources) {
     // Create suballocated buffers to fill half the budget.
     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
-        kSuballocatedResourceSize, ((kRestrictedBudgetSize / 2) / kSuballocatedResourceSize));
+        kSuballocatedResourceSize, ((kRestrictedBudgetSize / 2) / kSuballocatedResourceSize),
+        kNonMappableBufferUsage);
 
     // Check that all the buffers allocated are resident. Also make sure they were suballocated
     // internally.
@@ -133,7 +142,8 @@
 
     // Create enough directly-allocated buffers to use the entire budget.
     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
-        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize);
+        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
+        kNonMappableBufferUsage);
 
     // Check that everything in bufferSet1 is now evicted.
     for (uint32_t i = 0; i < bufferSet1.size(); i++) {
@@ -157,9 +167,9 @@
 // correctly.
 TEST_P(D3D12ResidencyTests, OvercommitLargeResources) {
     // Create directly-allocated buffers to fill half the budget.
-    std::vector<wgpu::Buffer> bufferSet1 =
-        AllocateBuffers(kDirectlyAllocatedResourceSize,
-                        ((kRestrictedBudgetSize / 2) / kDirectlyAllocatedResourceSize));
+    std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
+        kDirectlyAllocatedResourceSize,
+        ((kRestrictedBudgetSize / 2) / kDirectlyAllocatedResourceSize), kNonMappableBufferUsage);
 
     // Check that all the allocated buffers are resident. Also make sure they were directly
     // allocated internally.
@@ -170,7 +180,8 @@
 
     // Create enough directly-allocated buffers to use the entire budget.
     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
-        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize);
+        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
+        kNonMappableBufferUsage);
 
     // Check that everything in bufferSet1 is now evicted.
     for (uint32_t i = 0; i < bufferSet1.size(); i++) {
@@ -191,12 +202,8 @@
 
 // Check that calling MapReadAsync makes the buffer resident and keeps it locked resident.
 TEST_P(D3D12ResidencyTests, AsyncMappedBufferRead) {
-    // Dawn currently only manages LOCAL_MEMORY. Mappable buffers exist in NON_LOCAL_MEMORY on
-    // discrete devices.
-    DAWN_SKIP_TEST_IF(!IsUMA());
-
     // Create a mappable buffer.
-    wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst);
+    wgpu::Buffer buffer = CreateBuffer(4, kMapReadBufferUsage);
 
     uint32_t data = 12345;
     buffer.SetSubData(0, sizeof(uint32_t), &data);
@@ -206,7 +213,8 @@
 
     // Create and touch enough buffers to use the entire budget.
     std::vector<wgpu::Buffer> bufferSet = AllocateBuffers(
-        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize);
+        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
+        kMapReadBufferUsage);
     TouchBuffers(0, bufferSet.size(), bufferSet);
 
     // The mappable buffer should have been evicted.
@@ -229,25 +237,23 @@
     // This should evict the mappable buffer.
     buffer.Unmap();
     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
-        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize);
+        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
+        kMapReadBufferUsage);
     TouchBuffers(0, bufferSet2.size(), bufferSet2);
     EXPECT_FALSE(CheckIfBufferIsResident(buffer));
 }
 
 // Check that calling MapWriteAsync makes the buffer resident and keeps it locked resident.
 TEST_P(D3D12ResidencyTests, AsyncMappedBufferWrite) {
-    // Dawn currently only manages LOCAL_MEMORY. Mappable buffers exist in NON_LOCAL_MEMORY on
-    // discrete devices.
-    DAWN_SKIP_TEST_IF(!IsUMA());
-
     // Create a mappable buffer.
-    wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc);
+    wgpu::Buffer buffer = CreateBuffer(4, kMapWriteBufferUsage);
     // The mappable buffer should be resident.
     EXPECT_TRUE(CheckIfBufferIsResident(buffer));
 
     // Create and touch enough buffers to use the entire budget.
     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
-        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize);
+        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
+        kMapReadBufferUsage);
     TouchBuffers(0, bufferSet1.size(), bufferSet1);
 
     // The mappable buffer should have been evicted.
@@ -270,7 +276,8 @@
     // This should evict the mappable buffer.
     buffer.Unmap();
     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
-        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize);
+        kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
+        kMapReadBufferUsage);
     TouchBuffers(0, bufferSet2.size(), bufferSet2);
     EXPECT_FALSE(CheckIfBufferIsResident(buffer));
 }
@@ -281,7 +288,8 @@
     constexpr uint32_t numberOfBuffersToOvercommit = 5;
     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
         kDirectlyAllocatedResourceSize,
-        (kRestrictedBudgetSize / kDirectlyAllocatedResourceSize) + numberOfBuffersToOvercommit);
+        (kRestrictedBudgetSize / kDirectlyAllocatedResourceSize) + numberOfBuffersToOvercommit,
+        kNonMappableBufferUsage);
     // Touch the buffers, which creates an overcommitted command list.
     TouchBuffers(0, bufferSet1.size(), bufferSet1);
     // Ensure that all of these buffers are resident, even though we're exceeding the budget.
@@ -292,7 +300,8 @@
     // Allocate another set of buffers that exceeds the budget.
     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
         kDirectlyAllocatedResourceSize,
-        (kRestrictedBudgetSize / kDirectlyAllocatedResourceSize) + numberOfBuffersToOvercommit);
+        (kRestrictedBudgetSize / kDirectlyAllocatedResourceSize) + numberOfBuffersToOvercommit,
+        kNonMappableBufferUsage);
     // Ensure the first <numberOfBuffersToOvercommit> buffers in the second buffer set were evicted,
     // since they shouldn't fit in the budget.
     for (uint32_t i = 0; i < numberOfBuffersToOvercommit; i++) {