Residency 4: Add Facilities For Budgeting Device Memory

Use D3D12's QueryDeviceVideoMemoryInfo to get the OS-determined process
budget. Also introduces an export for reserving some amount of process
memory - which keeps Dawn from using the entire process's budget.

Bug: dawn:193
Change-Id: I6c17bd703d7cb24759bcee89c03add46944fec8c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16383
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 9f0cee4..6a073a5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -325,6 +325,8 @@
       "src/dawn_native/d3d12/RenderPassBuilderD3D12.h",
       "src/dawn_native/d3d12/RenderPipelineD3D12.cpp",
       "src/dawn_native/d3d12/RenderPipelineD3D12.h",
+      "src/dawn_native/d3d12/ResidencyManagerD3D12.cpp",
+      "src/dawn_native/d3d12/ResidencyManagerD3D12.h",
       "src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp",
       "src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.h",
       "src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp",
diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt
index 2bab3e5..117a631 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -198,6 +198,8 @@
         "d3d12/RenderPassBuilderD3D12.h"
         "d3d12/RenderPipelineD3D12.cpp"
         "d3d12/RenderPipelineD3D12.h"
+        "d3d12/ResidencyManagerD3D12.cpp"
+        "d3d12/ResidencyManagerD3D12.h"
         "d3d12/ResourceAllocatorManagerD3D12.cpp"
         "d3d12/ResourceAllocatorManagerD3D12.h"
         "d3d12/ResourceHeapAllocationD3D12.cpp"
diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp
index 4e002d2..5984095 100644
--- a/src/dawn_native/Toggles.cpp
+++ b/src/dawn_native/Toggles.cpp
@@ -83,6 +83,12 @@
               "versions of Windows prior to build 1809, or when this toggle is turned off, Dawn "
               "will emulate a render pass.",
               "https://crbug.com/dawn/36"}},
+            {Toggle::UseD3D12ResidencyManagement,
+             {"use_d3d12_residency_management",
+              "Enable residency management. This allows page-in and page-out of resource heaps in "
+              "GPU memory. This component improves overcommitted performance by keeping the most "
+              "recently used resources local to the GPU. Turning this component off can cause "
+              "allocation failures when application memory exceeds physical device memory."}},
             {Toggle::SkipValidation,
              {"skip_validation", "Skip expensive validation of Dawn commands.",
               "https://crbug.com/dawn/271"}},
diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h
index c9d7352..c05363b 100644
--- a/src/dawn_native/Toggles.h
+++ b/src/dawn_native/Toggles.h
@@ -32,6 +32,7 @@
         UseTemporaryBufferInCompressedTextureToTextureCopy,
         UseD3D12ResourceHeapTier2,
         UseD3D12RenderPass,
+        UseD3D12ResidencyManagement,
         SkipValidation,
         UseSpvc,
         UseSpvcParser,
diff --git a/src/dawn_native/d3d12/AdapterD3D12.cpp b/src/dawn_native/d3d12/AdapterD3D12.cpp
index d9a07c6..487cef9 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn_native/d3d12/AdapterD3D12.cpp
@@ -34,7 +34,7 @@
         }
     };
 
-    Adapter::Adapter(Backend* backend, ComPtr<IDXGIAdapter1> hardwareAdapter)
+    Adapter::Adapter(Backend* backend, ComPtr<IDXGIAdapter3> hardwareAdapter)
         : AdapterBase(backend->GetInstance(), wgpu::BackendType::D3D12),
           mHardwareAdapter(hardwareAdapter),
           mBackend(backend) {
@@ -44,7 +44,7 @@
         return mDeviceInfo;
     }
 
-    IDXGIAdapter1* Adapter::GetHardwareAdapter() const {
+    IDXGIAdapter3* Adapter::GetHardwareAdapter() const {
         return mHardwareAdapter.Get();
     }
 
diff --git a/src/dawn_native/d3d12/AdapterD3D12.h b/src/dawn_native/d3d12/AdapterD3D12.h
index 6c085f0..6c2d4f1 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.h
+++ b/src/dawn_native/d3d12/AdapterD3D12.h
@@ -26,11 +26,11 @@
 
     class Adapter : public AdapterBase {
       public:
-        Adapter(Backend* backend, ComPtr<IDXGIAdapter1> hardwareAdapter);
+        Adapter(Backend* backend, ComPtr<IDXGIAdapter3> hardwareAdapter);
         virtual ~Adapter() = default;
 
         const D3D12DeviceInfo& GetDeviceInfo() const;
-        IDXGIAdapter1* GetHardwareAdapter() const;
+        IDXGIAdapter3* GetHardwareAdapter() const;
         Backend* GetBackend() const;
         ComPtr<ID3D12Device> GetDevice() const;
 
@@ -40,7 +40,7 @@
         ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
         void InitializeSupportedExtensions();
 
-        ComPtr<IDXGIAdapter1> mHardwareAdapter;
+        ComPtr<IDXGIAdapter3> mHardwareAdapter;
         ComPtr<ID3D12Device> mD3d12Device;
 
         Backend* mBackend;
diff --git a/src/dawn_native/d3d12/BackendD3D12.cpp b/src/dawn_native/d3d12/BackendD3D12.cpp
index 81dfdb2..40e424d 100644
--- a/src/dawn_native/d3d12/BackendD3D12.cpp
+++ b/src/dawn_native/d3d12/BackendD3D12.cpp
@@ -106,7 +106,12 @@
 
             ASSERT(dxgiAdapter != nullptr);
 
-            std::unique_ptr<Adapter> adapter = std::make_unique<Adapter>(this, dxgiAdapter);
+            ComPtr<IDXGIAdapter3> dxgiAdapter3;
+            HRESULT result = dxgiAdapter.As(&dxgiAdapter3);
+            ASSERT(SUCCEEDED(result));
+
+            std::unique_ptr<Adapter> adapter =
+                std::make_unique<Adapter>(this, std::move(dxgiAdapter3));
             if (GetInstance()->ConsumedError(adapter->Initialize())) {
                 continue;
             }
diff --git a/src/dawn_native/d3d12/D3D12Backend.cpp b/src/dawn_native/d3d12/D3D12Backend.cpp
index 8adea50..d9cbf74 100644
--- a/src/dawn_native/d3d12/D3D12Backend.cpp
+++ b/src/dawn_native/d3d12/D3D12Backend.cpp
@@ -20,6 +20,7 @@
 #include "common/SwapChainUtils.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/NativeSwapChainImplD3D12.h"
+#include "dawn_native/d3d12/ResidencyManagerD3D12.h"
 #include "dawn_native/d3d12/TextureD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
@@ -50,6 +51,13 @@
         : ExternalImageDescriptor(ExternalImageDescriptorType::DXGISharedHandle) {
     }
 
+    uint64_t SetExternalMemoryReservation(WGPUDevice device, uint64_t requestedReservationSize) {
+        Device* backendDevice = reinterpret_cast<Device*>(device);
+
+        return backendDevice->GetResidencyManager()->SetExternalMemoryReservation(
+            requestedReservationSize);
+    }
+
     WGPUTexture WrapSharedHandle(WGPUDevice device,
                                  const ExternalImageDescriptorDXGISharedHandle* descriptor) {
         Device* backendDevice = reinterpret_cast<Device*>(device);
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 91cc8f6..2558a60 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -32,6 +32,7 @@
 #include "dawn_native/d3d12/PlatformFunctions.h"
 #include "dawn_native/d3d12/QueueD3D12.h"
 #include "dawn_native/d3d12/RenderPipelineD3D12.h"
+#include "dawn_native/d3d12/ResidencyManagerD3D12.h"
 #include "dawn_native/d3d12/ResourceAllocatorManagerD3D12.h"
 #include "dawn_native/d3d12/SamplerD3D12.h"
 #include "dawn_native/d3d12/ShaderModuleD3D12.h"
@@ -79,6 +80,7 @@
         DAWN_TRY(mShaderVisibleDescriptorAllocator->Initialize());
 
         mMapRequestTracker = std::make_unique<MapRequestTracker>(this);
+        mResidencyManager = std::make_unique<ResidencyManager>(this);
         mResourceAllocatorManager = std::make_unique<ResourceAllocatorManager>(this);
 
         DAWN_TRY(NextSerial());
@@ -154,6 +156,10 @@
         return mCommandAllocatorManager.get();
     }
 
+    ResidencyManager* Device::GetResidencyManager() const {
+        return mResidencyManager.get();
+    }
+
     ResultOrError<CommandRecordingContext*> Device::GetPendingCommandContext() {
         // Callers of GetPendingCommandList do so to record commands. Only reserve a command
         // allocator when it is needed so we don't submit empty command lists
@@ -403,6 +409,7 @@
         const bool useResourceHeapTier2 = (GetDeviceInfo().resourceHeapTier >= 2);
         SetToggle(Toggle::UseD3D12ResourceHeapTier2, useResourceHeapTier2);
         SetToggle(Toggle::UseD3D12RenderPass, GetDeviceInfo().supportsRenderPass);
+        SetToggle(Toggle::UseD3D12ResidencyManagement, false);
     }
 
     MaybeError Device::WaitForIdleForDestruction() {
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 311f150..b844d7b 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -34,6 +34,7 @@
     class MapRequestTracker;
     class PlatformFunctions;
     class ResourceAllocatorManager;
+    class ResidencyManager;
 
 #define ASSERT_SUCCESS(hr)            \
     {                                 \
@@ -66,6 +67,7 @@
         DescriptorHeapAllocator* GetDescriptorHeapAllocator() const;
         MapRequestTracker* GetMapRequestTracker() const;
         CommandAllocatorManager* GetCommandAllocatorManager() const;
+        ResidencyManager* GetResidencyManager() const;
 
         const PlatformFunctions* GetFunctions() const;
         ComPtr<IDXGIFactory4> GetFactory() const;
@@ -161,6 +163,7 @@
         std::unique_ptr<DescriptorHeapAllocator> mDescriptorHeapAllocator;
         std::unique_ptr<MapRequestTracker> mMapRequestTracker;
         std::unique_ptr<ResourceAllocatorManager> mResourceAllocatorManager;
+        std::unique_ptr<ResidencyManager> mResidencyManager;
         std::unique_ptr<ShaderVisibleDescriptorAllocator> mShaderVisibleDescriptorAllocator;
     };
 
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
new file mode 100644
index 0000000..c105748
--- /dev/null
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp
@@ -0,0 +1,69 @@
+// 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/ResidencyManagerD3D12.h"
+
+#include "dawn_native/d3d12/AdapterD3D12.h"
+#include "dawn_native/d3d12/DeviceD3D12.h"
+#include "dawn_native/d3d12/Forward.h"
+#include "dawn_native/d3d12/d3d12_platform.h"
+
+namespace dawn_native { namespace d3d12 {
+
+    ResidencyManager::ResidencyManager(Device* device)
+        : mDevice(device),
+          mResidencyManagementEnabled(
+              device->IsToggleEnabled(Toggle::UseD3D12ResidencyManagement)) {
+        UpdateVideoMemoryInfo();
+    }
+
+    // Allows an application component external to Dawn to cap Dawn's residency budget to prevent
+    // competition for device local memory. Returns the amount of memory reserved, which may be less
+    // that the requested reservation when under pressure.
+    uint64_t ResidencyManager::SetExternalMemoryReservation(uint64_t requestedReservationSize) {
+        mVideoMemoryInfo.externalRequest = requestedReservationSize;
+        UpdateVideoMemoryInfo();
+        return mVideoMemoryInfo.externalReservation;
+    }
+
+    void ResidencyManager::UpdateVideoMemoryInfo() {
+        if (!mResidencyManagementEnabled) {
+            return;
+        }
+
+        DXGI_QUERY_VIDEO_MEMORY_INFO queryVideoMemoryInfo;
+        ToBackend(mDevice->GetAdapter())
+            ->GetHardwareAdapter()
+            ->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &queryVideoMemoryInfo);
+
+        // The video memory budget provided by QueryVideoMemoryInfo is defined by the operating
+        // system, and may be lower than expected in certain scenarios. Under memory pressure, we
+        // cap the external reservation to half the available budget, which prevents the external
+        // component from consuming a disproportionate share of memory and ensures that Dawn can
+        // continue to make forward progress. Note the choice to halve memory is arbitrarily chosen
+        // and subject to future experimentation.
+        mVideoMemoryInfo.externalReservation =
+            std::min(queryVideoMemoryInfo.Budget / 2, mVideoMemoryInfo.externalReservation);
+
+        // We cap Dawn's budget to 95% of the provided budget. Leaving some budget unused
+        // decreases fluctuations in the operating-system-defined budget, which improves stability
+        // for both Dawn and other applications on the system. Note the value of 95% is arbitrarily
+        // chosen and subject to future experimentation.
+        static constexpr float kBudgetCap = 0.95;
+        mVideoMemoryInfo.dawnBudget =
+            (queryVideoMemoryInfo.Budget - mVideoMemoryInfo.externalReservation) * kBudgetCap;
+        mVideoMemoryInfo.dawnUsage =
+            queryVideoMemoryInfo.CurrentUsage - mVideoMemoryInfo.externalReservation;
+    }
+}}  // namespace dawn_native::d3d12
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.h b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
new file mode 100644
index 0000000..575515a
--- /dev/null
+++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.h
@@ -0,0 +1,45 @@
+// 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_RESIDENCYMANAGERD3D12_H_
+#define DAWNNATIVE_D3D12_RESIDENCYMANAGERD3D12_H_
+
+#include "dawn_native/dawn_platform.h"
+
+namespace dawn_native { namespace d3d12 {
+
+    class Device;
+
+    class ResidencyManager {
+      public:
+        ResidencyManager(Device* device);
+        uint64_t SetExternalMemoryReservation(uint64_t requestedReservationSize);
+
+      private:
+        struct VideoMemoryInfo {
+            uint64_t dawnBudget;
+            uint64_t dawnUsage;
+            uint64_t externalReservation;
+            uint64_t externalRequest;
+        };
+        void UpdateVideoMemoryInfo();
+
+        Device* mDevice = nullptr;
+        bool mResidencyManagementEnabled = false;
+        VideoMemoryInfo mVideoMemoryInfo = {};
+    };
+
+}}  // namespace dawn_native::d3d12
+
+#endif  // DAWNNATIVE_D3D12_RESIDENCYMANAGERD3D12_H_
\ No newline at end of file
diff --git a/src/include/dawn_native/D3D12Backend.h b/src/include/dawn_native/D3D12Backend.h
index 9c20ead..229be3b 100644
--- a/src/include/dawn_native/D3D12Backend.h
+++ b/src/include/dawn_native/D3D12Backend.h
@@ -38,6 +38,9 @@
         uint64_t acquireMutexKey;
     };
 
+    DAWN_NATIVE_EXPORT uint64_t SetExternalMemoryReservation(WGPUDevice device,
+                                                             uint64_t requestedReservationSize);
+
     // Note: SharedHandle must be a handle to a texture object.
     DAWN_NATIVE_EXPORT WGPUTexture
     WrapSharedHandle(WGPUDevice device, const ExternalImageDescriptorDXGISharedHandle* descriptor);