diff --git a/BUILD.gn b/BUILD.gn index f8cf5b8328..3b5190b640 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -375,6 +375,7 @@ source_set("dawn_white_box_tests_sources") { if (dawn_enable_d3d12) { sources += [ "src/tests/white_box/D3D12DescriptorHeapTests.cpp", + "src/tests/white_box/D3D12ResidencyTests.cpp", "src/tests/white_box/D3D12SmallTextureTests.cpp", ] } diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp index 9de7a94c3e..2c433a600c 100644 --- a/src/dawn_native/d3d12/BufferD3D12.cpp +++ b/src/dawn_native/d3d12/BufferD3D12.cpp @@ -314,6 +314,15 @@ namespace dawn_native { namespace d3d12 { ToBackend(GetDevice())->DeallocateMemory(mResourceAllocation); } + bool Buffer::CheckIsResidentForTesting() const { + Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); + return heap->IsInList() || heap->IsResidencyLocked(); + } + + bool Buffer::CheckAllocationMethodForTesting(AllocationMethod allocationMethod) const { + return mResourceAllocation.GetInfo().mMethod == allocationMethod; + } + MapRequestTracker::MapRequestTracker(Device* device) : mDevice(device) { } diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h index 5a06ead8e1..02ec4035dd 100644 --- a/src/dawn_native/d3d12/BufferD3D12.h +++ b/src/dawn_native/d3d12/BufferD3D12.h @@ -42,6 +42,9 @@ namespace dawn_native { namespace d3d12 { void TrackUsageAndTransitionNow(CommandRecordingContext* commandContext, wgpu::BufferUsage newUsage); + bool CheckAllocationMethodForTesting(AllocationMethod allocationMethod) const; + bool CheckIsResidentForTesting() const; + private: ~Buffer() override; // Dawn API diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index dff175d4af..c08c06b091 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -414,7 +414,7 @@ namespace dawn_native { namespace d3d12 { const bool useResourceHeapTier2 = (GetDeviceInfo().resourceHeapTier >= 2); SetToggle(Toggle::UseD3D12ResourceHeapTier2, useResourceHeapTier2); SetToggle(Toggle::UseD3D12RenderPass, GetDeviceInfo().supportsRenderPass); - SetToggle(Toggle::UseD3D12ResidencyManagement, false); + SetToggle(Toggle::UseD3D12ResidencyManagement, true); // By default use the maximum shader-visible heap size allowed. SetToggle(Toggle::UseD3D12SmallShaderVisibleHeapForTesting, false); diff --git a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp index eb904f89c1..7cfd1feb8d 100644 --- a/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp +++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.cpp @@ -110,7 +110,15 @@ namespace dawn_native { namespace d3d12 { // 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); + std::min(queryVideoMemoryInfo.Budget / 2, mVideoMemoryInfo.externalRequest); + + mVideoMemoryInfo.dawnUsage = + queryVideoMemoryInfo.CurrentUsage - mVideoMemoryInfo.externalReservation; + + // If we're restricting the budget for testing, leave the budget as is. + if (mRestrictBudgetForTesting) { + return; + } // 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 @@ -119,8 +127,6 @@ namespace dawn_native { namespace d3d12 { static constexpr float kBudgetCap = 0.95; mVideoMemoryInfo.dawnBudget = (queryVideoMemoryInfo.Budget - mVideoMemoryInfo.externalReservation) * kBudgetCap; - mVideoMemoryInfo.dawnUsage = - queryVideoMemoryInfo.CurrentUsage - mVideoMemoryInfo.externalReservation; } // Removes from the LRU and returns the least recently used heap when possible. Returns nullptr @@ -280,4 +286,21 @@ namespace dawn_native { namespace d3d12 { mLRUCache.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(!mRestrictBudgetForTesting); + + mRestrictBudgetForTesting = true; + UpdateVideoMemoryInfo(); + + // Dawn has a non-zero memory usage even before any resources have been created, and this + // value can vary depending on the environment Dawn is running in. By adding this in + // addition to the artificial budget cap, we can create a predictable and reproducible + // budget for testing. + mVideoMemoryInfo.dawnBudget = mVideoMemoryInfo.dawnUsage + artificialBudgetCap; + } + }} // 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 index 4ecd9ef09f..f9370de0ed 100644 --- a/src/dawn_native/d3d12/ResidencyManagerD3D12.h +++ b/src/dawn_native/d3d12/ResidencyManagerD3D12.h @@ -38,6 +38,8 @@ namespace dawn_native { namespace d3d12 { void TrackResidentAllocation(Heap* heap); + void RestrictBudgetForTesting(uint64_t); + private: struct VideoMemoryInfo { uint64_t dawnBudget; @@ -52,6 +54,7 @@ namespace dawn_native { namespace d3d12 { Device* mDevice; LinkedList mLRUCache; bool mResidencyManagementEnabled = false; + bool mRestrictBudgetForTesting = false; VideoMemoryInfo mVideoMemoryInfo = {}; }; diff --git a/src/tests/white_box/D3D12ResidencyTests.cpp b/src/tests/white_box/D3D12ResidencyTests.cpp new file mode 100644 index 0000000000..21fca34846 --- /dev/null +++ b/src/tests/white_box/D3D12ResidencyTests.cpp @@ -0,0 +1,279 @@ +// 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/ResourceMemoryAllocation.h" +#include "dawn_native/d3d12/BufferD3D12.h" +#include "dawn_native/d3d12/DeviceD3D12.h" +#include "dawn_native/d3d12/ResidencyManagerD3D12.h" +#include "dawn_native/d3d12/ResourceHeapAllocationD3D12.h" +#include "tests/DawnTest.h" +#include "utils/WGPUHelpers.h" + +#include + +constexpr uint32_t kRestrictedBudgetSize = 100000000; // 100MB +constexpr uint32_t kDirectlyAllocatedResourceSize = 5000000; // 5MB +constexpr uint32_t kSuballocatedResourceSize = 1000000; // 1MB +constexpr uint32_t kSourceBufferSize = 4; // 4B + +class D3D12ResidencyTests : public DawnTest { + protected: + void TestSetUp() override { + DAWN_SKIP_TEST_IF(UsesWire()); + + // Restrict Dawn's budget to create an artificial budget. + dawn_native::d3d12::Device* d3dDevice = + reinterpret_cast(device.Get()); + d3dDevice->GetResidencyManager()->RestrictBudgetForTesting(kRestrictedBudgetSize); + + // Initialize a source buffer on the GPU to serve as a source to quickly copy data to other + // buffers. + constexpr uint32_t one = 1; + mSourceBuffer = + utils::CreateBufferFromData(device, &one, sizeof(one), wgpu::BufferUsage::CopySrc); + } + + std::vector AllocateBuffers(uint32_t bufferSize, uint32_t numberOfBuffers) { + std::vector buffers; + + for (uint64_t i = 0; i < numberOfBuffers; i++) { + buffers.push_back(CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst)); + } + + return buffers; + } + + bool CheckIfBufferIsResident(wgpu::Buffer buffer) const { + dawn_native::d3d12::Buffer* d3dBuffer = + reinterpret_cast(buffer.Get()); + return d3dBuffer->CheckIsResidentForTesting(); + } + + bool CheckAllocationMethod(wgpu::Buffer buffer, + dawn_native::AllocationMethod allocationMethod) const { + dawn_native::d3d12::Buffer* d3dBuffer = + reinterpret_cast(buffer.Get()); + return d3dBuffer->CheckAllocationMethodForTesting(allocationMethod); + } + + bool IsUMA() const { + return reinterpret_cast(device.Get())->GetDeviceInfo().isUMA; + } + + wgpu::Buffer CreateBuffer(uint32_t bufferSize, wgpu::BufferUsage usage) { + wgpu::BufferDescriptor descriptor; + + descriptor.size = bufferSize; + descriptor.usage = usage; + + 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(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(userdata)->mMappedWriteData = data; + } + + void TouchBuffers(uint32_t beginIndex, + uint32_t numBuffers, + const std::vector& bufferSet) { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + // Perform a copy on the range of buffers to ensure the are moved to dedicated GPU memory. + for (uint32_t i = beginIndex; i < beginIndex + numBuffers; i++) { + encoder.CopyBufferToBuffer(mSourceBuffer, 0, bufferSet[i], 0, kSourceBufferSize); + } + wgpu::CommandBuffer copy = encoder.Finish(); + queue.Submit(1, ©); + } + + wgpu::Buffer mSourceBuffer; + void* mMappedWriteData = nullptr; + const void* mMappedReadData = nullptr; +}; + +// Check that resources existing on suballocated heaps are made resident and evicted correctly. +TEST_P(D3D12ResidencyTests, OvercommitSmallResources) { + // Create suballocated buffers to fill half the budget. + std::vector bufferSet1 = AllocateBuffers( + kSuballocatedResourceSize, ((kRestrictedBudgetSize / 2) / kSuballocatedResourceSize)); + + // Check that all the buffers allocated are resident. Also make sure they were suballocated + // internally. + for (uint32_t i = 0; i < bufferSet1.size(); i++) { + EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[i])); + EXPECT_TRUE( + CheckAllocationMethod(bufferSet1[i], dawn_native::AllocationMethod::kSubAllocated)); + } + + // Create enough directly-allocated buffers to use the entire budget. + std::vector bufferSet2 = AllocateBuffers( + kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize); + + // Check that everything in bufferSet1 is now evicted. + for (uint32_t i = 0; i < bufferSet1.size(); i++) { + EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[i])); + } + + // Touch one of the non-resident buffers. This should cause the buffer to become resident. + constexpr uint32_t indexOfBufferInSet1 = 5; + TouchBuffers(indexOfBufferInSet1, 1, bufferSet1); + // Check that this buffer is now resident. + EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1])); + + // Touch everything in bufferSet2 again to evict the buffer made resident in the previous + // operation. + TouchBuffers(0, bufferSet2.size(), bufferSet2); + // Check that indexOfBufferInSet1 was evicted. + EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1])); +} + +// Check that resources existing on directly allocated heaps are made resident and evicted +// correctly. +TEST_P(D3D12ResidencyTests, OvercommitLargeResources) { + // Create directly-allocated buffers to fill half the budget. + std::vector bufferSet1 = + AllocateBuffers(kDirectlyAllocatedResourceSize, + ((kRestrictedBudgetSize / 2) / kDirectlyAllocatedResourceSize)); + + // Check that all the allocated buffers are resident. Also make sure they were directly + // allocated internally. + for (uint32_t i = 0; i < bufferSet1.size(); i++) { + EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[i])); + EXPECT_TRUE(CheckAllocationMethod(bufferSet1[i], dawn_native::AllocationMethod::kDirect)); + } + + // Create enough directly-allocated buffers to use the entire budget. + std::vector bufferSet2 = AllocateBuffers( + kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize); + + // Check that everything in bufferSet1 is now evicted. + for (uint32_t i = 0; i < bufferSet1.size(); i++) { + EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[i])); + } + + // Touch one of the non-resident buffers. This should cause the buffer to become resident. + constexpr uint32_t indexOfBufferInSet1 = 1; + TouchBuffers(indexOfBufferInSet1, 1, bufferSet1); + EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1])); + + // Touch everything in bufferSet2 again to evict the buffer made resident in the previous + // operation. + TouchBuffers(0, bufferSet2.size(), bufferSet2); + // Check that indexOfBufferInSet1 was evicted. + EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1])); +} + +// 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); + + uint32_t data = 12345; + buffer.SetSubData(0, sizeof(uint32_t), &data); + + // The mappable buffer should be resident. + EXPECT_TRUE(CheckIfBufferIsResident(buffer)); + + // Create and touch enough buffers to use the entire budget. + std::vector bufferSet = AllocateBuffers( + kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize); + TouchBuffers(0, bufferSet.size(), bufferSet); + + // The mappable buffer should have been evicted. + EXPECT_FALSE(CheckIfBufferIsResident(buffer)); + + // Calling MapReadAsync should make the buffer resident. + buffer.MapReadAsync(MapReadCallback, this); + EXPECT_TRUE(CheckIfBufferIsResident(buffer)); + + while (mMappedReadData == nullptr) { + WaitABit(); + } + + // Touch enough resources such that the entire budget is used. The mappable buffer should remain + // locked resident. + TouchBuffers(0, bufferSet.size(), bufferSet); + EXPECT_TRUE(CheckIfBufferIsResident(buffer)); + + // Unmap the buffer, allocate and touch enough resources such that the entire budget is used. + // This should evict the mappable buffer. + buffer.Unmap(); + std::vector bufferSet2 = AllocateBuffers( + kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize); + 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); + // The mappable buffer should be resident. + EXPECT_TRUE(CheckIfBufferIsResident(buffer)); + + // Create and touch enough buffers to use the entire budget. + std::vector bufferSet1 = AllocateBuffers( + kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize); + TouchBuffers(0, bufferSet1.size(), bufferSet1); + + // The mappable buffer should have been evicted. + EXPECT_FALSE(CheckIfBufferIsResident(buffer)); + + // Calling MapWriteAsync should make the buffer resident. + buffer.MapWriteAsync(MapWriteCallback, this); + EXPECT_TRUE(CheckIfBufferIsResident(buffer)); + + while (mMappedWriteData == nullptr) { + WaitABit(); + } + + // Touch enough resources such that the entire budget is used. The mappable buffer should remain + // locked resident. + TouchBuffers(0, bufferSet1.size(), bufferSet1); + EXPECT_TRUE(CheckIfBufferIsResident(buffer)); + + // Unmap the buffer, allocate and touch enough resources such that the entire budget is used. + // This should evict the mappable buffer. + buffer.Unmap(); + std::vector bufferSet2 = AllocateBuffers( + kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize); + TouchBuffers(0, bufferSet2.size(), bufferSet2); + EXPECT_FALSE(CheckIfBufferIsResident(buffer)); +} + +DAWN_INSTANTIATE_TEST(D3D12ResidencyTests, D3D12Backend()); \ No newline at end of file