Try To Recover From MakeResident Failure

Per MSDN recommendations, Dawn should handle MakeResident failures by
evicting some more and attempting MakeResident again.

Bug: dawn:193
Change-Id: I0a9d326dcd000360f6eafb5691efb4987a77e8d5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22280
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Brandon Jones <brandon1.jones@intel.com>
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
index 9f9d964..eec7160 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
@@ -37,11 +37,11 @@
 
         // If the heap isn't already resident, make it resident.
         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"));
+            uint64_t size = pageable->GetSize();
+
+            DAWN_TRY(MakeAllocationsResident(GetMemorySegmentInfo(pageable->GetMemorySegment()),
+                                             size, 1, &d3d12Pageable));
         }
 
         // Since we can't evict the heap, it's unnecessary to track the heap in the LRU Cache.
@@ -181,14 +181,19 @@
             return {};
         }
 
-        return EnsureCanMakeResident(allocationSize, GetMemorySegmentInfo(memorySegment));
+        uint64_t bytesEvicted;
+        DAWN_TRY_ASSIGN(bytesEvicted,
+                        EnsureCanMakeResident(allocationSize, GetMemorySegmentInfo(memorySegment)));
+
+        return {};
     }
 
     // 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) {
+    // memory, we should evict until there is. Returns the number of bytes evicted.
+    ResultOrError<uint64_t> ResidencyManager::EnsureCanMakeResident(
+        uint64_t sizeToMakeResident,
+        MemorySegmentInfo* memorySegment) {
         ASSERT(mResidencyManagementEnabled);
 
         UpdateMemorySegmentInfo(memorySegment);
@@ -197,7 +202,7 @@
 
         // Return when we can call MakeResident and remain under budget.
         if (memoryUsageAfterMakeResident < memorySegment->budget) {
-            return {};
+            return 0;
         }
 
         std::vector<ID3D12Pageable*> resourcesToEvict;
@@ -222,7 +227,7 @@
                 "Evicting resident heaps to free memory"));
         }
 
-        return {};
+        return sizeEvicted;
     }
 
     // Given a list of heaps that are pending usage, this function will estimate memory needed,
@@ -233,7 +238,8 @@
             return {};
         }
 
-        std::vector<ID3D12Pageable*> heapsToMakeResident;
+        std::vector<ID3D12Pageable*> localHeapsToMakeResident;
+        std::vector<ID3D12Pageable*> nonLocalHeapsToMakeResident;
         uint64_t localSizeToMakeResident = 0;
         uint64_t nonLocalSizeToMakeResident = 0;
 
@@ -251,11 +257,12 @@
                 // update its position in the LRU.
                 heap->RemoveFromList();
             } else {
-                heapsToMakeResident.push_back(heap->GetD3D12Pageable());
                 if (heap->GetMemorySegment() == MemorySegment::Local) {
                     localSizeToMakeResident += heap->GetSize();
+                    localHeapsToMakeResident.push_back(heap->GetD3D12Pageable());
                 } else {
                     nonLocalSizeToMakeResident += heap->GetSize();
+                    nonLocalHeapsToMakeResident.push_back(heap->GetD3D12Pageable());
                 }
             }
 
@@ -270,25 +277,56 @@
         }
 
         if (localSizeToMakeResident > 0) {
-            DAWN_TRY(EnsureCanMakeResident(localSizeToMakeResident, &mVideoMemoryInfo.local));
+            return MakeAllocationsResident(&mVideoMemoryInfo.local, localSizeToMakeResident,
+                                           localHeapsToMakeResident.size(),
+                                           localHeapsToMakeResident.data());
         }
 
         if (nonLocalSizeToMakeResident > 0) {
             ASSERT(!mDevice->GetDeviceInfo().isUMA);
-            DAWN_TRY(EnsureCanMakeResident(nonLocalSizeToMakeResident, &mVideoMemoryInfo.nonLocal));
+            return MakeAllocationsResident(&mVideoMemoryInfo.nonLocal, nonLocalSizeToMakeResident,
+                                           nonLocalHeapsToMakeResident.size(),
+                                           nonLocalHeapsToMakeResident.data());
         }
 
-        if (heapsToMakeResident.size() != 0) {
-            // 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
-            // 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"));
+        return {};
+    }
+
+    MaybeError ResidencyManager::MakeAllocationsResident(MemorySegmentInfo* segment,
+                                                         uint64_t sizeToMakeResident,
+                                                         uint64_t numberOfObjectsToMakeResident,
+                                                         ID3D12Pageable** allocations) {
+        uint64_t bytesEvicted;
+        DAWN_TRY_ASSIGN(bytesEvicted, EnsureCanMakeResident(sizeToMakeResident, segment));
+
+        // 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
+        // the EnqueueMakeResident function (which is not available on all Windows 10
+        // platforms).
+        HRESULT hr =
+            mDevice->GetD3D12Device()->MakeResident(numberOfObjectsToMakeResident, allocations);
+
+        // A MakeResident call can fail if there's not enough available memory. This
+        // could occur when there's significant fragmentation or if the allocation size
+        // estimates are incorrect. We may be able to continue execution by evicting some
+        // more memory and calling MakeResident again.
+        while (FAILED(hr)) {
+            constexpr uint32_t kAdditonalSizeToEvict = 50000000;  // 50MB
+
+            uint64_t sizeEvicted = 0;
+
+            DAWN_TRY_ASSIGN(sizeEvicted, EnsureCanMakeResident(kAdditonalSizeToEvict, segment));
+
+            // If nothing can be evicted after MakeResident has failed, we cannot continue
+            // execution and must throw a fatal error.
+            if (sizeEvicted == 0) {
+                return DAWN_OUT_OF_MEMORY_ERROR(
+                    "MakeResident has failed due to excessive video memory usage.");
+            }
+
+            hr =
+                mDevice->GetD3D12Device()->MakeResident(numberOfObjectsToMakeResident, allocations);
         }
 
         return {};
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.h b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
index 632abc3..304a211 100644
--- a/src/dawn_native/d3d12/ResidencyManagerD3D12.h
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
@@ -62,8 +62,13 @@
         };
 
         MemorySegmentInfo* GetMemorySegmentInfo(MemorySegment memorySegment);
-        MaybeError EnsureCanMakeResident(uint64_t allocationSize, MemorySegmentInfo* memorySegment);
+        ResultOrError<uint64_t> EnsureCanMakeResident(uint64_t allocationSize,
+                                                      MemorySegmentInfo* memorySegment);
         ResultOrError<Pageable*> RemoveSingleEntryFromLRU(MemorySegmentInfo* memorySegment);
+        MaybeError MakeAllocationsResident(MemorySegmentInfo* segment,
+                                           uint64_t sizeToMakeResident,
+                                           uint64_t numberOfObjectsToMakeResident,
+                                           ID3D12Pageable** allocations);
         void UpdateVideoMemoryInfo();
         void UpdateMemorySegmentInfo(MemorySegmentInfo* segmentInfo);