From 4a04b19dc0a43df7cbcc66671742ec0a9fbc71b9 Mon Sep 17 00:00:00 2001 From: Bryan Bernhart Date: Wed, 29 Jun 2022 10:08:19 -0700 Subject: [PATCH] Update budget by OS budget change events. Instead of polling for latest residency usage, OS event based updates are used. Closes Update budget using OS events or non-polling based. #319 --- .github/workflows/.patches/dawn.diff | 32 ++-- src/gpgmm/common/EventMessage.h | 3 +- src/gpgmm/d3d12/ResidencyManagerD3D12.cpp | 181 +++++++++++++++++- src/gpgmm/d3d12/ResidencyManagerD3D12.h | 31 ++- .../end2end/D3D12ResidencyManagerTests.cpp | 127 ++++++++---- 5 files changed, 304 insertions(+), 70 deletions(-) diff --git a/.github/workflows/.patches/dawn.diff b/.github/workflows/.patches/dawn.diff index 354656d70..afae0b272 100644 --- a/.github/workflows/.patches/dawn.diff +++ b/.github/workflows/.patches/dawn.diff @@ -1,4 +1,4 @@ -From f2724b093745556795b76ad7214b694f3f93ed37 Mon Sep 17 00:00:00 2001 +From e41eefd8175a3cd7125699987d452dbfac72fb44 Mon Sep 17 00:00:00 2001 From: Bryan Bernhart Date: Tue, 15 Feb 2022 17:25:29 -0800 Subject: [PATCH] Use GPGMM for D3D12 backend. @@ -495,7 +495,7 @@ index 9747b41c0..50db56dbd 100644 AdapterDiscoveryOptions::AdapterDiscoveryOptions() diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp -index 90ddc3041..c82b3fb90 100644 +index 90ddc3041..8545685aa 100644 --- a/src/dawn/native/d3d12/DeviceD3D12.cpp +++ b/src/dawn/native/d3d12/DeviceD3D12.cpp @@ -130,8 +130,43 @@ MaybeError Device::Initialize(const DeviceDescriptor* descriptor) { @@ -514,32 +514,32 @@ index 90ddc3041..c82b3fb90 100644 + static_cast(adapter->GetDeviceInfo().resourceHeapTier); + allocatorDesc.PreferredResourceHeapSize = 4ll * 1024ll * 1024ll; // 4MB + -+ gpgmm::d3d12::ResidencyManager** residencyManager = nullptr; -+ if (IsToggleEnabled(Toggle::UseD3D12ResidencyManagement)) { -+ allocatorDesc.MaxVideoMemoryBudget = 0.95; // Use up to 95%. -+ residencyManager = mResidencyManager.GetAddressOf(); -+ } ++ gpgmm::d3d12::RESIDENCY_DESC residencyDesc = {}; ++ residencyDesc.Adapter = adapter->GetHardwareAdapter(); ++ residencyDesc.Device = mD3d12Device; + -+ if (residencyManager != nullptr && -+ IsToggleEnabled(Toggle::UseD3D12SmallResidencyBudgetForTesting)) { -+ allocatorDesc.Budget = 100000000; // 100MB ++ if (IsToggleEnabled(Toggle::UseD3D12SmallResidencyBudgetForTesting)) { ++ residencyDesc.UpdateBudgetByPolling = true; ++ residencyDesc.Budget = 100000000; // 100MB + allocatorDesc.Flags |= gpgmm::d3d12::ALLOCATOR_FLAG_DISABLE_MEMORY_PREFETCH; + allocatorDesc.Flags |= gpgmm::d3d12::ALLOCATOR_FLAG_ALWAYS_IN_BUDGET; + } + ++ if (IsToggleEnabled(Toggle::UseD3D12ResidencyManagement)) { ++ residencyDesc.VideoMemoryBudget = 0.95; // Use up to 95% ++ DAWN_TRY(CheckHRESULT(gpgmm::d3d12::ResidencyManager::CreateResidencyManager( ++ residencyDesc, &mResidencyManager), ++ "D3D12 create residency manager")); ++ } ++ + if (IsToggleEnabled(Toggle::DumpResourceAllocator)) { + allocatorDesc.RecordOptions.Flags |= gpgmm::d3d12::ALLOCATOR_RECORD_FLAG_ALL_EVENTS; -+ allocatorDesc.RecordOptions.MinMessageLevel = D3D12_MESSAGE_SEVERITY_MESSAGE; + allocatorDesc.RecordOptions.EventScope = gpgmm::d3d12::ALLOCATOR_RECORD_SCOPE_PER_INSTANCE; + allocatorDesc.RecordOptions.TraceFile = "dawn_resource_allocator_dump.json"; + } + -+ if (IsToggleEnabled(Toggle::RecordDetailedTimingInTraceEvents)) { -+ allocatorDesc.RecordOptions.UseDetailedTimingEvents = true; -+ } -+ + DAWN_TRY(CheckHRESULT(gpgmm::d3d12::ResourceAllocator::CreateAllocator( -+ allocatorDesc, &mResourceAllocator, residencyManager), ++ allocatorDesc, mResidencyManager.Get(), &mResourceAllocator), + "D3D12 create resource allocator")); // ShaderVisibleDescriptorAllocators use the ResidencyManager and must be initialized after. diff --git a/src/gpgmm/common/EventMessage.h b/src/gpgmm/common/EventMessage.h index 618f66979..6ed1ed42f 100644 --- a/src/gpgmm/common/EventMessage.h +++ b/src/gpgmm/common/EventMessage.h @@ -30,7 +30,8 @@ namespace gpgmm { AlignmentMismatch, AllocatorFailed, PrefetchFailed, - BudgetExceeded + BudgetExceeded, + BudgetUpdate, }; struct EventMessageInfo { diff --git a/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp b/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp index 46f0c9bd2..929e84953 100644 --- a/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp +++ b/src/gpgmm/d3d12/ResidencyManagerD3D12.cpp @@ -18,6 +18,7 @@ #include "gpgmm/common/EventMessage.h" #include "gpgmm/common/SizeClass.h" #include "gpgmm/common/TraceEvent.h" +#include "gpgmm/common/WorkerThread.h" #include "gpgmm/d3d12/ErrorD3D12.h" #include "gpgmm/d3d12/FenceD3D12.h" #include "gpgmm/d3d12/HeapD3D12.h" @@ -33,6 +34,115 @@ namespace gpgmm::d3d12 { static constexpr uint32_t kDefaultEvictBatchSize = GPGMM_MB_TO_BYTES(50); static constexpr float kDefaultVideoMemoryBudget = 0.95f; // 95% + // Creates a long-lived task to recieve and process OS budget change events. + class BudgetUpdateTask : public VoidCallback { + public: + BudgetUpdateTask(ResidencyManager* const residencyManager, ComPtr adapter) + : mResidencyManager(residencyManager), + mAdapter(std::move(adapter)), + mBudgetNotificationUpdateEvent(CreateEventW(NULL, FALSE, FALSE, NULL)), + mUnregisterAndExitEvent(CreateEventW(NULL, FALSE, FALSE, NULL)) { + ASSERT(mResidencyManager != nullptr); + ASSERT(mAdapter != nullptr); + mLastError = mAdapter->RegisterVideoMemoryBudgetChangeNotificationEvent( + mBudgetNotificationUpdateEvent, &mCookie); + } + + void operator()() override { + HRESULT hr = GetLastError(); + bool isExiting = false; + while (!isExiting && SUCCEEDED(hr)) { + // Wait on two events: one to unblock for OS budget changes, and another to unblock + // for shutdown. + HANDLE hWaitEvents[2] = {mBudgetNotificationUpdateEvent, mUnregisterAndExitEvent}; + const DWORD waitedEvent = + WaitForMultipleObjects(2, hWaitEvents, /*bWaitAll*/ false, INFINITE); + switch (waitedEvent) { + // mBudgetNotificationUpdateEvent + case (WAIT_OBJECT_0 + 0): { + hr = mResidencyManager->UpdateVideoMemorySegments(); + if (SUCCEEDED(hr)) { + gpgmm::InfoEvent("ResidencyManager", EventMessageId::BudgetUpdate) + << "Updated video budget from OS notification."; + } + break; + } + // mUnregisterAndExitEvent + case (WAIT_OBJECT_0 + 1): { + isExiting = true; + break; + } + default: { + UNREACHABLE(); + break; + } + } + } + + SetLastError(hr); + } + + HRESULT GetLastError() const { + std::lock_guard lock(mMutex); + return mLastError; + } + + // Shutdown the event loop. + bool UnregisterAndExit() { + mAdapter->UnregisterVideoMemoryBudgetChangeNotification(mCookie); + return SetEvent(mUnregisterAndExitEvent); + } + + private: + void SetLastError(HRESULT hr) { + std::lock_guard lock(mMutex); + mLastError = hr; + } + + ResidencyManager* const mResidencyManager; + ComPtr mAdapter; + + HANDLE mBudgetNotificationUpdateEvent = INVALID_HANDLE_VALUE; + HANDLE mUnregisterAndExitEvent = INVALID_HANDLE_VALUE; + + DWORD mCookie = 0; // Used to unregister from notifications. + + mutable std::mutex mMutex; // Protect access between threads for members below. + HRESULT mLastError = S_OK; + }; + + class BudgetUpdateEvent final : public Event { + public: + BudgetUpdateEvent(std::shared_ptr event, std::shared_ptr task) + : mTask(task), mEvent(event) { + } + + // Event overrides + void Wait() override { + mEvent->Wait(); + } + + bool IsSignaled() override { + return mEvent->IsSignaled(); + } + + void Signal() override { + return mEvent->Signal(); + } + + bool UnregisterAndExit() { + return mTask->UnregisterAndExit(); + } + + bool GetLastError() const { + return mTask->GetLastError(); + } + + private: + std::shared_ptr mTask; + std::shared_ptr mEvent; + }; + // static HRESULT ResidencyManager::CreateResidencyManager(const RESIDENCY_DESC& descriptor, ResidencyManager** residencyManagerOut) { @@ -54,6 +164,11 @@ namespace gpgmm::d3d12 { std::unique_ptr residencyManager = std::unique_ptr( new ResidencyManager(descriptor, std::move(residencyFence))); + // Require automatic video memory budget updates. + if (!descriptor.UpdateBudgetByPolling) { + ReturnIfFailed(residencyManager->StartBudgetNotificationUpdates()); + } + // Set the initial video memory limits per segment. ReturnIfFailed(residencyManager->UpdateVideoMemorySegments()); @@ -94,7 +209,9 @@ namespace gpgmm::d3d12 { mIsBudgetRestricted(descriptor.Budget > 0), mEvictBatchSize(descriptor.EvictBatchSize == 0 ? kDefaultEvictBatchSize : descriptor.EvictBatchSize), - mIsUMA(descriptor.IsUMA) { + mIsUMA(descriptor.IsUMA), + mIsBudgetChangeEventsDisabled(descriptor.UpdateBudgetByPolling), + mThreadPool(ThreadPool::Create()) { GPGMM_TRACE_EVENT_OBJECT_NEW(this); ASSERT(mDevice != nullptr); @@ -104,6 +221,7 @@ namespace gpgmm::d3d12 { ResidencyManager::~ResidencyManager() { GPGMM_TRACE_EVENT_OBJECT_DESTROY(this); + StopBudgetNotificationUpdates(); } const char* ResidencyManager::GetTypename() const { @@ -293,6 +411,11 @@ namespace gpgmm::d3d12 { } HRESULT ResidencyManager::UpdateVideoMemorySegments() { + std::lock_guard lock(mMutex); + return UpdateVideoMemorySegmentsInternal(); + } + + HRESULT ResidencyManager::UpdateVideoMemorySegmentsInternal() { DXGI_QUERY_VIDEO_MEMORY_INFO* queryVideoMemoryInfo = GetVideoMemoryInfo(DXGI_MEMORY_SEGMENT_GROUP_LOCAL); @@ -320,7 +443,10 @@ namespace gpgmm::d3d12 { DXGI_QUERY_VIDEO_MEMORY_INFO* videoMemorySegmentInfo = GetVideoMemoryInfo(memorySegmentGroup); - ReturnIfFailed(QueryVideoMemoryInfo(memorySegmentGroup, videoMemorySegmentInfo)); + + if (IsBudgetNotificationUpdatesDisabled()) { + ReturnIfFailed(QueryVideoMemoryInfo(memorySegmentGroup, videoMemorySegmentInfo)); + } const uint64_t currentUsageAfterEvict = evictSizeInBytes + videoMemorySegmentInfo->CurrentUsage; @@ -403,19 +529,15 @@ namespace gpgmm::d3d12 { std::lock_guard lock(mMutex); + if (count == 0) { + return E_INVALIDARG; + } + // TODO: support multiple command lists. if (count > 1) { return E_NOTIMPL; } - // Keep video memory segments up-to-date. This must always happen because tests may - // want to execute a no-op to get the current budget and usage. - ReturnIfFailed(UpdateVideoMemorySegments()); - - if (count == 0) { - return S_OK; - } - ResidencySet* residencySet = residencySets[0]; std::vector localHeapsToMakeResident; @@ -480,6 +602,13 @@ namespace gpgmm::d3d12 { ReturnIfFailed(mFence->Signal(queue)); } + // Keep video memory segments up-to-date. This must always happen because if the budget + // never changes (ie. not manually updated or through budget change events), the + // residency manager wouldn't know what to page in or out. + if (IsBudgetNotificationUpdatesDisabled()) { + ReturnIfFailed(UpdateVideoMemorySegmentsInternal()); + } + GPGMM_TRACE_EVENT_OBJECT_CALL("ResidencyManager.ExecuteCommandLists", (EXECUTE_COMMAND_LISTS_DESC{residencySets, count})); @@ -540,4 +669,36 @@ namespace gpgmm::d3d12 { return info; } + // Starts updating video memory budget from OS notifications. + // Return True if successfully registered or False if error. + HRESULT ResidencyManager::StartBudgetNotificationUpdates() { + if (mBudgetNotificationUpdateEvent == nullptr) { + std::shared_ptr task = + std::make_shared(this, mAdapter); + mBudgetNotificationUpdateEvent = std::make_shared( + ThreadPool::PostTask(mThreadPool, task, "GPGMM_ThreadBudgetChangeWorker"), task); + } + + ASSERT(mBudgetNotificationUpdateEvent != nullptr); + return mBudgetNotificationUpdateEvent->GetLastError(); + } + + bool ResidencyManager::IsBudgetNotificationUpdatesDisabled() const { + return mIsBudgetChangeEventsDisabled || + (mBudgetNotificationUpdateEvent != nullptr && + FAILED(mBudgetNotificationUpdateEvent->GetLastError())); + } + + void ResidencyManager::StopBudgetNotificationUpdates() { + if (mBudgetNotificationUpdateEvent == nullptr) { + return; + } + + const bool success = mBudgetNotificationUpdateEvent->UnregisterAndExit(); + ASSERT(success); + + mBudgetNotificationUpdateEvent->Wait(); + mBudgetNotificationUpdateEvent = nullptr; + } + } // namespace gpgmm::d3d12 diff --git a/src/gpgmm/d3d12/ResidencyManagerD3D12.h b/src/gpgmm/d3d12/ResidencyManagerD3D12.h index eb5e42bde..818fbf8fb 100644 --- a/src/gpgmm/d3d12/ResidencyManagerD3D12.h +++ b/src/gpgmm/d3d12/ResidencyManagerD3D12.h @@ -23,6 +23,10 @@ #include #include +namespace gpgmm { + class ThreadPool; +} // namespace gpgmm + namespace gpgmm::d3d12 { class Fence; @@ -88,6 +92,15 @@ namespace gpgmm::d3d12 { Optional parameter. Zero by default. */ uint64_t InitialFenceValue; + + /** \brief Disables video memory budget updates from OS notifications. + + Used for polling video memory for budget updates when event based budget + changes are not updating frequently enough or otherwise disabled by the OS. + + Optional parameter. Polling is disabled by default. + */ + bool UpdateBudgetByPolling; }; /** \struct RESIDENCY_INFO @@ -103,6 +116,8 @@ namespace gpgmm::d3d12 { uint64_t MemoryCount = 0; }; + class BudgetUpdateEvent; + /** \brief ResidencyManager tracks and maintains one or more Heap within a residency cache. A Heap is considered "resident" when it is accessible by the GPU. A Heap can be made explicitly @@ -200,6 +215,10 @@ namespace gpgmm::d3d12 { DXGI_QUERY_VIDEO_MEMORY_INFO* GetVideoMemoryInfo( const DXGI_MEMORY_SEGMENT_GROUP& memorySegmentGroup); + /** \brief Manually update the video memory budgets. + */ + HRESULT UpdateVideoMemorySegments(); + /** \brief Return the current residency manager usage. \return A RESIDENCY_INFO struct. @@ -234,8 +253,12 @@ namespace gpgmm::d3d12 { HRESULT QueryVideoMemoryInfo(const DXGI_MEMORY_SEGMENT_GROUP& memorySegmentGroup, DXGI_QUERY_VIDEO_MEMORY_INFO* pVideoMemoryInfo) const; - // Query and set the video memory limits for all segments. - HRESULT UpdateVideoMemorySegments(); + HRESULT UpdateVideoMemorySegmentsInternal(); + + HRESULT StartBudgetNotificationUpdates(); + void StopBudgetNotificationUpdates(); + + bool IsBudgetNotificationUpdatesDisabled() const; ComPtr mDevice; ComPtr mAdapter; @@ -247,11 +270,15 @@ namespace gpgmm::d3d12 { const bool mIsBudgetRestricted; const uint64_t mEvictBatchSize; const bool mIsUMA; + const bool mIsBudgetChangeEventsDisabled; VideoMemorySegment mLocalVideoMemorySegment; VideoMemorySegment mNonLocalVideoMemorySegment; std::mutex mMutex; + + std::shared_ptr mThreadPool; + std::shared_ptr mBudgetNotificationUpdateEvent; }; } // namespace gpgmm::d3d12 diff --git a/src/tests/end2end/D3D12ResidencyManagerTests.cpp b/src/tests/end2end/D3D12ResidencyManagerTests.cpp index 2e1ebef26..8a7b0b392 100644 --- a/src/tests/end2end/D3D12ResidencyManagerTests.cpp +++ b/src/tests/end2end/D3D12ResidencyManagerTests.cpp @@ -14,12 +14,16 @@ #include "tests/D3D12Test.h" +#include "gpgmm/common/SizeClass.h" + #include #include using namespace gpgmm::d3d12; +static constexpr uint64_t kDefaultBudget = GPGMM_MB_TO_BYTES(10); + class D3D12ResidencyManagerTests : public D3D12TestBase, public ::testing::Test { protected: void SetUp() override { @@ -30,11 +34,10 @@ class D3D12ResidencyManagerTests : public D3D12TestBase, public ::testing::Test D3D12TestBase::TearDown(); } - // Allocator description specific for testing residency in a controlled and predictable + // Configures allocator for testing residency in a controlled and predictable // fashion. - ALLOCATOR_DESC CreateBasicAllocatorDesc(uint64_t budget = 0) const { + ALLOCATOR_DESC CreateBasicAllocatorDesc() const { ALLOCATOR_DESC desc = D3D12TestBase::CreateBasicAllocatorDesc(); - desc.Budget = budget; // Disable pre-fetching since it will could cause over-committment unpredictably. desc.Flags |= gpgmm::d3d12::ALLOCATOR_FLAG_DISABLE_MEMORY_PREFETCH; @@ -45,16 +48,33 @@ class D3D12ResidencyManagerTests : public D3D12TestBase, public ::testing::Test return desc; } - RESIDENCY_DESC CreateBasicResidencyDesc(uint64_t budget = 0) const { - RESIDENCY_DESC desc = {}; - desc.Budget = budget; + RESIDENCY_DESC CreateBasicResidencyDesc(uint64_t budget) const { + RESIDENCY_DESC residencyDesc = {}; + + // Disable automatic budget updates, since it occurs uncontrollably by the OS. + residencyDesc.UpdateBudgetByPolling = true; + + // Specify a restricted budget, the OS budget fluctuates unpredicatbly. + residencyDesc.Budget = budget; // Required - desc.IsUMA = mIsUMA; - desc.Adapter = mAdapter; - desc.Device = mDevice; + residencyDesc.IsUMA = mIsUMA; + residencyDesc.Adapter = mAdapter; + residencyDesc.Device = mDevice; - return desc; + return residencyDesc; + } + + bool IsOverBudget(ResidencyManager* residencyManager) const { + ASSERT(residencyManager != nullptr); + + DXGI_QUERY_VIDEO_MEMORY_INFO* local = + residencyManager->GetVideoMemoryInfo(DXGI_MEMORY_SEGMENT_GROUP_LOCAL); + + DXGI_QUERY_VIDEO_MEMORY_INFO* nonLocal = + residencyManager->GetVideoMemoryInfo(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL); + + return local->Budget <= local->CurrentUsage && nonLocal->Budget <= nonLocal->CurrentUsage; } }; @@ -106,8 +126,8 @@ TEST_F(D3D12ResidencyManagerTests, CreateResidencyManager) { // Create allocator with residency, seperately. { ComPtr residencyManager; - ASSERT_SUCCEEDED(ResidencyManager::CreateResidencyManager(CreateBasicResidencyDesc(), - &residencyManager)); + ASSERT_SUCCEEDED(ResidencyManager::CreateResidencyManager( + CreateBasicResidencyDesc(kDefaultBudget), &residencyManager)); ComPtr resourceAllocator; ASSERT_SUCCEEDED(ResourceAllocator::CreateAllocator( @@ -131,7 +151,8 @@ TEST_F(D3D12ResidencyManagerTests, CreateResidencyManagerNoLeak) { // Create allocator with residency, seperately. { ComPtr residencyManager; - ResidencyManager::CreateResidencyManager(CreateBasicResidencyDesc(), &residencyManager); + ResidencyManager::CreateResidencyManager(CreateBasicResidencyDesc(kDefaultBudget), + &residencyManager); ComPtr resourceAllocator; ResourceAllocator::CreateAllocator(CreateBasicAllocatorDesc(), residencyManager.Get(), @@ -141,55 +162,79 @@ TEST_F(D3D12ResidencyManagerTests, CreateResidencyManagerNoLeak) { GPGMM_TEST_MEMORY_LEAK_END(); } -// Keeps allocating until it goes over budget. +// Keeps allocating until it goes over the limited |kDefaultBudget| size budget. TEST_F(D3D12ResidencyManagerTests, OverBudget) { - ALLOCATOR_DESC allocatorDesc = CreateBasicAllocatorDesc(10 * 1024 * 1024); + RESIDENCY_DESC residencyDesc = CreateBasicResidencyDesc(kDefaultBudget); ComPtr residencyManager; - ComPtr resourceAllocator; - ASSERT_SUCCEEDED( - ResourceAllocator::CreateAllocator(allocatorDesc, &resourceAllocator, &residencyManager)); + ASSERT_SUCCEEDED(ResidencyManager::CreateResidencyManager(residencyDesc, &residencyManager)); - DXGI_QUERY_VIDEO_MEMORY_INFO* local = - residencyManager->GetVideoMemoryInfo(DXGI_MEMORY_SEGMENT_GROUP_LOCAL); - DXGI_QUERY_VIDEO_MEMORY_INFO* nonLocal = - residencyManager->GetVideoMemoryInfo(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL); + ComPtr resourceAllocator; + ASSERT_SUCCEEDED(ResourceAllocator::CreateAllocator( + CreateBasicAllocatorDesc(), residencyManager.Get(), &resourceAllocator)); std::vector> allocations = {}; - std::vector resourceHeaps = {}; - - // Keep allocating until we reach the 10MB budget. - while (local->Budget > local->CurrentUsage || nonLocal->Budget > nonLocal->CurrentUsage) { + while (!IsOverBudget(residencyManager.Get())) { ComPtr allocation; ASSERT_SUCCEEDED(resourceAllocator->CreateResource( {}, CreateBasicBufferDesc(1), D3D12_RESOURCE_STATE_COMMON, nullptr, &allocation)); - resourceHeaps.push_back(allocation->GetMemory()); allocations.push_back(std::move(allocation)); + ASSERT_SUCCEEDED(residencyManager->UpdateVideoMemorySegments()); + } +} + +// Keeps allocating until it goes over the OS limited budget. +TEST_F(D3D12ResidencyManagerTests, OverBudgetUsingBudgetNotifications) { + constexpr uint64_t kBudgetIsDeterminedByOS = 0; + RESIDENCY_DESC residencyDesc = CreateBasicResidencyDesc(kBudgetIsDeterminedByOS); + residencyDesc.UpdateBudgetByPolling = false; + + ComPtr residencyManager; + ASSERT_SUCCEEDED(ResidencyManager::CreateResidencyManager(residencyDesc, &residencyManager)); + + ComPtr resourceAllocator; + ASSERT_SUCCEEDED(ResourceAllocator::CreateAllocator( + CreateBasicAllocatorDesc(), residencyManager.Get(), &resourceAllocator)); - // Call ExecuteCommandLists() to update the budget and current usage. - ASSERT_SUCCEEDED(residencyManager->ExecuteCommandLists(nullptr, nullptr, nullptr, 0)); + // Keep allocating until we reach the budget. + std::vector> allocations = {}; + while (!IsOverBudget(residencyManager.Get())) { + ComPtr allocation; + ASSERT_SUCCEEDED( + resourceAllocator->CreateResource({}, CreateBasicBufferDesc(GPGMM_MB_TO_BYTES(1)), + D3D12_RESOURCE_STATE_COMMON, nullptr, &allocation)); + allocations.push_back(std::move(allocation)); } +} - ASSERT_GT(resourceHeaps.size(), 1u); +// Keeps allocating until it goes over budget. +TEST_F(D3D12ResidencyManagerTests, OverBudgetAndNoGrowth) { + RESIDENCY_DESC residencyDesc = CreateBasicResidencyDesc(kDefaultBudget); - // With no budget left, the last resource heap size should not increase. - EXPECT_LE(resourceHeaps.at(allocations.size() - 1)->GetSize(), - resourceHeaps.at(allocations.size() - 2)->GetSize()); + ComPtr residencyManager; + ASSERT_SUCCEEDED(ResidencyManager::CreateResidencyManager(residencyDesc, &residencyManager)); - // But when going over budget, should evict some other resource heap. - { - RESIDENCY_INFO beforeInfo = residencyManager->GetInfo(); + ComPtr resourceAllocator; + ASSERT_SUCCEEDED(ResourceAllocator::CreateAllocator( + CreateBasicAllocatorDesc(), residencyManager.Get(), &resourceAllocator)); + std::vector> allocations = {}; + std::vector resourceHeaps = {}; + + while (!IsOverBudget(residencyManager.Get())) { ComPtr allocation; ASSERT_SUCCEEDED(resourceAllocator->CreateResource( {}, CreateBasicBufferDesc(1), D3D12_RESOURCE_STATE_COMMON, nullptr, &allocation)); - RESIDENCY_INFO afterInfo = residencyManager->GetInfo(); - - EXPECT_TRUE(allocation->IsResident()); + resourceHeaps.push_back(allocation->GetMemory()); + allocations.push_back(std::move(allocation)); - EXPECT_EQ(afterInfo.MemoryCount, beforeInfo.MemoryCount); - EXPECT_LE(afterInfo.MemoryUsage, beforeInfo.MemoryUsage); + ASSERT_SUCCEEDED(residencyManager->UpdateVideoMemorySegments()); } + + // With no budget left, the last resource heap size should not increase. + ASSERT_GT(resourceHeaps.size(), 1u); + EXPECT_LE(resourceHeaps.at(allocations.size() - 1)->GetSize(), + resourceHeaps.at(allocations.size() - 2)->GetSize()); }