• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "dawn_native/D3D12Backend.h"
16 #include "dawn_native/d3d12/BufferD3D12.h"
17 #include "dawn_native/d3d12/DeviceD3D12.h"
18 #include "dawn_native/d3d12/ResidencyManagerD3D12.h"
19 #include "dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h"
20 #include "tests/DawnTest.h"
21 #include "utils/ComboRenderPipelineDescriptor.h"
22 #include "utils/WGPUHelpers.h"
23 
24 #include <vector>
25 
26 constexpr uint32_t kRestrictedBudgetSize = 100000000;         // 100MB
27 constexpr uint32_t kDirectlyAllocatedResourceSize = 5000000;  // 5MB
28 constexpr uint32_t kSuballocatedResourceSize = 1000000;       // 1MB
29 constexpr uint32_t kSourceBufferSize = 4;                     // 4B
30 
31 constexpr wgpu::BufferUsage kMapReadBufferUsage =
32     wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
33 constexpr wgpu::BufferUsage kMapWriteBufferUsage =
34     wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
35 constexpr wgpu::BufferUsage kNonMappableBufferUsage = wgpu::BufferUsage::CopyDst;
36 
37 class D3D12ResidencyTestBase : public DawnTest {
38   protected:
SetUp()39     void SetUp() override {
40         DawnTest::SetUp();
41         DAWN_TEST_UNSUPPORTED_IF(UsesWire());
42 
43         // Restrict Dawn's budget to create an artificial budget.
44         dawn_native::d3d12::Device* d3dDevice =
45             dawn_native::d3d12::ToBackend(dawn_native::FromAPI((device.Get())));
46         d3dDevice->GetResidencyManager()->RestrictBudgetForTesting(kRestrictedBudgetSize);
47 
48         // Initialize a source buffer on the GPU to serve as a source to quickly copy data to other
49         // buffers.
50         constexpr uint32_t one = 1;
51         mSourceBuffer =
52             utils::CreateBufferFromData(device, &one, sizeof(one), wgpu::BufferUsage::CopySrc);
53     }
54 
AllocateBuffers(uint32_t bufferSize,uint32_t numberOfBuffers,wgpu::BufferUsage usage)55     std::vector<wgpu::Buffer> AllocateBuffers(uint32_t bufferSize,
56                                               uint32_t numberOfBuffers,
57                                               wgpu::BufferUsage usage) {
58         std::vector<wgpu::Buffer> buffers;
59 
60         for (uint64_t i = 0; i < numberOfBuffers; i++) {
61             buffers.push_back(CreateBuffer(bufferSize, usage));
62         }
63 
64         return buffers;
65     }
66 
CreateBuffer(uint32_t bufferSize,wgpu::BufferUsage usage)67     wgpu::Buffer CreateBuffer(uint32_t bufferSize, wgpu::BufferUsage usage) {
68         wgpu::BufferDescriptor descriptor;
69 
70         descriptor.size = bufferSize;
71         descriptor.usage = usage;
72 
73         return device.CreateBuffer(&descriptor);
74     }
75 
TouchBuffers(uint32_t beginIndex,uint32_t numBuffers,const std::vector<wgpu::Buffer> & bufferSet)76     void TouchBuffers(uint32_t beginIndex,
77                       uint32_t numBuffers,
78                       const std::vector<wgpu::Buffer>& bufferSet) {
79         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
80 
81         // Perform a copy on the range of buffers to ensure the are moved to dedicated GPU memory.
82         for (uint32_t i = beginIndex; i < beginIndex + numBuffers; i++) {
83             encoder.CopyBufferToBuffer(mSourceBuffer, 0, bufferSet[i], 0, kSourceBufferSize);
84         }
85         wgpu::CommandBuffer copy = encoder.Finish();
86         queue.Submit(1, &copy);
87     }
88 
89     wgpu::Buffer mSourceBuffer;
90 };
91 
92 class D3D12ResourceResidencyTests : public D3D12ResidencyTestBase {
93   protected:
CheckAllocationMethod(wgpu::Buffer buffer,dawn_native::AllocationMethod allocationMethod) const94     bool CheckAllocationMethod(wgpu::Buffer buffer,
95                                dawn_native::AllocationMethod allocationMethod) const {
96         dawn_native::d3d12::Buffer* d3dBuffer =
97             dawn_native::d3d12::ToBackend(dawn_native::FromAPI((buffer.Get())));
98         return d3dBuffer->CheckAllocationMethodForTesting(allocationMethod);
99     }
100 
CheckIfBufferIsResident(wgpu::Buffer buffer) const101     bool CheckIfBufferIsResident(wgpu::Buffer buffer) const {
102         dawn_native::d3d12::Buffer* d3dBuffer =
103             dawn_native::d3d12::ToBackend(dawn_native::FromAPI((buffer.Get())));
104         return d3dBuffer->CheckIsResidentForTesting();
105     }
106 
IsUMA() const107     bool IsUMA() const {
108         return dawn_native::d3d12::ToBackend(dawn_native::FromAPI(device.Get()))
109             ->GetDeviceInfo()
110             .isUMA;
111     }
112 };
113 
114 class D3D12DescriptorResidencyTests : public D3D12ResidencyTestBase {};
115 
116 // Check that resources existing on suballocated heaps are made resident and evicted correctly.
TEST_P(D3D12ResourceResidencyTests,OvercommitSmallResources)117 TEST_P(D3D12ResourceResidencyTests, OvercommitSmallResources) {
118     // TODO(http://crbug.com/dawn/416): Tests fails on Intel HD 630 bot.
119     DAWN_SUPPRESS_TEST_IF(IsIntel() && IsBackendValidationEnabled());
120 
121     // Create suballocated buffers to fill half the budget.
122     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
123         kSuballocatedResourceSize, ((kRestrictedBudgetSize / 2) / kSuballocatedResourceSize),
124         kNonMappableBufferUsage);
125 
126     // Check that all the buffers allocated are resident. Also make sure they were suballocated
127     // internally.
128     for (uint32_t i = 0; i < bufferSet1.size(); i++) {
129         EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[i]));
130         EXPECT_TRUE(
131             CheckAllocationMethod(bufferSet1[i], dawn_native::AllocationMethod::kSubAllocated));
132     }
133 
134     // Create enough directly-allocated buffers to use the entire budget.
135     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
136         kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
137         kNonMappableBufferUsage);
138 
139     // Check that everything in bufferSet1 is now evicted.
140     for (uint32_t i = 0; i < bufferSet1.size(); i++) {
141         EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[i]));
142     }
143 
144     // Touch one of the non-resident buffers. This should cause the buffer to become resident.
145     constexpr uint32_t indexOfBufferInSet1 = 5;
146     TouchBuffers(indexOfBufferInSet1, 1, bufferSet1);
147     // Check that this buffer is now resident.
148     EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1]));
149 
150     // Touch everything in bufferSet2 again to evict the buffer made resident in the previous
151     // operation.
152     TouchBuffers(0, bufferSet2.size(), bufferSet2);
153     // Check that indexOfBufferInSet1 was evicted.
154     EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1]));
155 }
156 
157 // Check that resources existing on directly allocated heaps are made resident and evicted
158 // correctly.
TEST_P(D3D12ResourceResidencyTests,OvercommitLargeResources)159 TEST_P(D3D12ResourceResidencyTests, OvercommitLargeResources) {
160     // Create directly-allocated buffers to fill half the budget.
161     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
162         kDirectlyAllocatedResourceSize,
163         ((kRestrictedBudgetSize / 2) / kDirectlyAllocatedResourceSize), kNonMappableBufferUsage);
164 
165     // Check that all the allocated buffers are resident. Also make sure they were directly
166     // allocated internally.
167     for (uint32_t i = 0; i < bufferSet1.size(); i++) {
168         EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[i]));
169         EXPECT_TRUE(CheckAllocationMethod(bufferSet1[i], dawn_native::AllocationMethod::kDirect));
170     }
171 
172     // Create enough directly-allocated buffers to use the entire budget.
173     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
174         kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
175         kNonMappableBufferUsage);
176 
177     // Check that everything in bufferSet1 is now evicted.
178     for (uint32_t i = 0; i < bufferSet1.size(); i++) {
179         EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[i]));
180     }
181 
182     // Touch one of the non-resident buffers. This should cause the buffer to become resident.
183     constexpr uint32_t indexOfBufferInSet1 = 1;
184     TouchBuffers(indexOfBufferInSet1, 1, bufferSet1);
185     EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1]));
186 
187     // Touch everything in bufferSet2 again to evict the buffer made resident in the previous
188     // operation.
189     TouchBuffers(0, bufferSet2.size(), bufferSet2);
190     // Check that indexOfBufferInSet1 was evicted.
191     EXPECT_FALSE(CheckIfBufferIsResident(bufferSet1[indexOfBufferInSet1]));
192 }
193 
194 // Check that calling MapAsync for reading makes the buffer resident and keeps it locked resident.
TEST_P(D3D12ResourceResidencyTests,AsyncMappedBufferRead)195 TEST_P(D3D12ResourceResidencyTests, AsyncMappedBufferRead) {
196     // Create a mappable buffer.
197     wgpu::Buffer buffer = CreateBuffer(4, kMapReadBufferUsage);
198 
199     uint32_t data = 12345;
200     queue.WriteBuffer(buffer, 0, &data, sizeof(uint32_t));
201 
202     // The mappable buffer should be resident.
203     EXPECT_TRUE(CheckIfBufferIsResident(buffer));
204 
205     // Create and touch enough buffers to use the entire budget.
206     std::vector<wgpu::Buffer> bufferSet = AllocateBuffers(
207         kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
208         kMapReadBufferUsage);
209     TouchBuffers(0, bufferSet.size(), bufferSet);
210 
211     // The mappable buffer should have been evicted.
212     EXPECT_FALSE(CheckIfBufferIsResident(buffer));
213 
214     // Calling MapAsync for reading should make the buffer resident.
215     bool done = false;
216     buffer.MapAsync(
217         wgpu::MapMode::Read, 0, sizeof(uint32_t),
218         [](WGPUBufferMapAsyncStatus status, void* userdata) {
219             ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
220             *static_cast<bool*>(userdata) = true;
221         },
222         &done);
223     EXPECT_TRUE(CheckIfBufferIsResident(buffer));
224 
225     while (!done) {
226         WaitABit();
227     }
228 
229     // Touch enough resources such that the entire budget is used. The mappable buffer should remain
230     // locked resident.
231     TouchBuffers(0, bufferSet.size(), bufferSet);
232     EXPECT_TRUE(CheckIfBufferIsResident(buffer));
233 
234     // Unmap the buffer, allocate and touch enough resources such that the entire budget is used.
235     // This should evict the mappable buffer.
236     buffer.Unmap();
237     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
238         kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
239         kMapReadBufferUsage);
240     TouchBuffers(0, bufferSet2.size(), bufferSet2);
241     EXPECT_FALSE(CheckIfBufferIsResident(buffer));
242 }
243 
244 // Check that calling MapAsync for writing makes the buffer resident and keeps it locked resident.
TEST_P(D3D12ResourceResidencyTests,AsyncMappedBufferWrite)245 TEST_P(D3D12ResourceResidencyTests, AsyncMappedBufferWrite) {
246     // Create a mappable buffer.
247     wgpu::Buffer buffer = CreateBuffer(4, kMapWriteBufferUsage);
248     // The mappable buffer should be resident.
249     EXPECT_TRUE(CheckIfBufferIsResident(buffer));
250 
251     // Create and touch enough buffers to use the entire budget.
252     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
253         kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
254         kMapReadBufferUsage);
255     TouchBuffers(0, bufferSet1.size(), bufferSet1);
256 
257     // The mappable buffer should have been evicted.
258     EXPECT_FALSE(CheckIfBufferIsResident(buffer));
259 
260     // Calling MapAsync for writing should make the buffer resident.
261     bool done = false;
262     buffer.MapAsync(
263         wgpu::MapMode::Write, 0, sizeof(uint32_t),
264         [](WGPUBufferMapAsyncStatus status, void* userdata) {
265             ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
266             *static_cast<bool*>(userdata) = true;
267         },
268         &done);
269     EXPECT_TRUE(CheckIfBufferIsResident(buffer));
270 
271     while (!done) {
272         WaitABit();
273     }
274 
275     // Touch enough resources such that the entire budget is used. The mappable buffer should remain
276     // locked resident.
277     TouchBuffers(0, bufferSet1.size(), bufferSet1);
278     EXPECT_TRUE(CheckIfBufferIsResident(buffer));
279 
280     // Unmap the buffer, allocate and touch enough resources such that the entire budget is used.
281     // This should evict the mappable buffer.
282     buffer.Unmap();
283     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
284         kDirectlyAllocatedResourceSize, kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
285         kMapReadBufferUsage);
286     TouchBuffers(0, bufferSet2.size(), bufferSet2);
287     EXPECT_FALSE(CheckIfBufferIsResident(buffer));
288 }
289 
290 // Check that overcommitting in a single submit works, then make sure the budget is enforced after.
TEST_P(D3D12ResourceResidencyTests,OvercommitInASingleSubmit)291 TEST_P(D3D12ResourceResidencyTests, OvercommitInASingleSubmit) {
292     // Create enough buffers to exceed the budget
293     constexpr uint32_t numberOfBuffersToOvercommit = 5;
294     std::vector<wgpu::Buffer> bufferSet1 = AllocateBuffers(
295         kDirectlyAllocatedResourceSize,
296         (kRestrictedBudgetSize / kDirectlyAllocatedResourceSize) + numberOfBuffersToOvercommit,
297         kNonMappableBufferUsage);
298     // Touch the buffers, which creates an overcommitted command list.
299     TouchBuffers(0, bufferSet1.size(), bufferSet1);
300     // Ensure that all of these buffers are resident, even though we're exceeding the budget.
301     for (uint32_t i = 0; i < bufferSet1.size(); i++) {
302         EXPECT_TRUE(CheckIfBufferIsResident(bufferSet1[i]));
303     }
304 
305     // Allocate another set of buffers that exceeds the budget.
306     std::vector<wgpu::Buffer> bufferSet2 = AllocateBuffers(
307         kDirectlyAllocatedResourceSize,
308         (kRestrictedBudgetSize / kDirectlyAllocatedResourceSize) + numberOfBuffersToOvercommit,
309         kNonMappableBufferUsage);
310     // Ensure the first <numberOfBuffersToOvercommit> buffers in the second buffer set were evicted,
311     // since they shouldn't fit in the budget.
312     for (uint32_t i = 0; i < numberOfBuffersToOvercommit; i++) {
313         EXPECT_FALSE(CheckIfBufferIsResident(bufferSet2[i]));
314     }
315 }
316 
TEST_P(D3D12ResourceResidencyTests,SetExternalReservation)317 TEST_P(D3D12ResourceResidencyTests, SetExternalReservation) {
318     // Set an external reservation of 20% the budget. We should succesfully reserve the amount we
319     // request.
320     uint64_t amountReserved = dawn_native::d3d12::SetExternalMemoryReservation(
321         device.Get(), kRestrictedBudgetSize * .2, dawn_native::d3d12::MemorySegment::Local);
322     EXPECT_EQ(amountReserved, kRestrictedBudgetSize * .2);
323 
324     // If we're on a non-UMA device, we should also check the NON_LOCAL memory segment.
325     if (!IsUMA()) {
326         amountReserved = dawn_native::d3d12::SetExternalMemoryReservation(
327             device.Get(), kRestrictedBudgetSize * .2, dawn_native::d3d12::MemorySegment::NonLocal);
328         EXPECT_EQ(amountReserved, kRestrictedBudgetSize * .2);
329     }
330 }
331 
332 // Checks that when a descriptor heap is bound, it is locked resident. Also checks that when a
333 // previous descriptor heap becomes unbound, it is unlocked, placed in the LRU and can be evicted.
TEST_P(D3D12DescriptorResidencyTests,SwitchedViewHeapResidency)334 TEST_P(D3D12DescriptorResidencyTests, SwitchedViewHeapResidency) {
335     // TODO(crbug.com/dawn/739):
336     // unknown file: error: SEH exception with code 0x87d thrown in the test body.
337     DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsWARP() && IsBackendValidationEnabled());
338 
339     utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
340 
341     // Fill in a view heap with "view only" bindgroups (1x view per group) by creating a
342     // view bindgroup each draw. After HEAP_SIZE + 1 draws, the heaps must switch over.
343     renderPipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
344             [[stage(vertex)]] fn main(
345                 [[builtin(vertex_index)]] VertexIndex : u32
346             ) -> [[builtin(position)]] vec4<f32> {
347                 var pos = array<vec2<f32>, 3>(
348                     vec2<f32>(-1.0,  1.0),
349                     vec2<f32>( 1.0,  1.0),
350                     vec2<f32>(-1.0, -1.0)
351                 );
352                 return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
353             })");
354 
355     renderPipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
356             [[block]] struct U {
357                 color : vec4<f32>;
358             };
359             [[group(0), binding(0)]] var<uniform> colorBuffer : U;
360 
361             [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
362                 return colorBuffer.color;
363             })");
364 
365     wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&renderPipelineDescriptor);
366     constexpr uint32_t kSize = 512;
367     utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kSize, kSize);
368 
369     wgpu::Sampler sampler = device.CreateSampler();
370 
371     dawn_native::d3d12::Device* d3dDevice =
372         dawn_native::d3d12::ToBackend(dawn_native::FromAPI(device.Get()));
373 
374     dawn_native::d3d12::ShaderVisibleDescriptorAllocator* allocator =
375         d3dDevice->GetViewShaderVisibleDescriptorAllocator();
376     const uint64_t heapSize = allocator->GetShaderVisibleHeapSizeForTesting();
377 
378     const dawn_native::d3d12::HeapVersionID heapSerial =
379         allocator->GetShaderVisibleHeapSerialForTesting();
380 
381     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
382     {
383         wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
384 
385         pass.SetPipeline(renderPipeline);
386 
387         std::array<float, 4> redColor = {1, 0, 0, 1};
388         wgpu::Buffer uniformBuffer = utils::CreateBufferFromData(
389             device, &redColor, sizeof(redColor), wgpu::BufferUsage::Uniform);
390 
391         for (uint32_t i = 0; i < heapSize + 1; ++i) {
392             pass.SetBindGroup(0, utils::MakeBindGroup(device, renderPipeline.GetBindGroupLayout(0),
393                                                       {{0, uniformBuffer, 0, sizeof(redColor)}}));
394             pass.Draw(3);
395         }
396 
397         pass.EndPass();
398     }
399 
400     wgpu::CommandBuffer commands = encoder.Finish();
401     queue.Submit(1, &commands);
402 
403     // Check the heap serial to ensure the heap has switched.
404     EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(),
405               heapSerial + dawn_native::d3d12::HeapVersionID(1));
406 
407     // Check that currrently bound ShaderVisibleHeap is locked resident.
408     EXPECT_TRUE(allocator->IsShaderVisibleHeapLockedResidentForTesting());
409     // Check that the previously bound ShaderVisibleHeap was unlocked and was placed in the LRU
410     // cache.
411     EXPECT_TRUE(allocator->IsLastShaderVisibleHeapInLRUForTesting());
412     // Allocate enough buffers to exceed the budget, which will purge everything from the Residency
413     // LRU.
414     AllocateBuffers(kDirectlyAllocatedResourceSize,
415                     kRestrictedBudgetSize / kDirectlyAllocatedResourceSize,
416                     kNonMappableBufferUsage);
417     // Check that currrently bound ShaderVisibleHeap remained locked resident.
418     EXPECT_TRUE(allocator->IsShaderVisibleHeapLockedResidentForTesting());
419     // Check that the previously bound ShaderVisibleHeap has been evicted from the LRU cache.
420     EXPECT_FALSE(allocator->IsLastShaderVisibleHeapInLRUForTesting());
421 }
422 
423 DAWN_INSTANTIATE_TEST(D3D12ResourceResidencyTests, D3D12Backend());
424 DAWN_INSTANTIATE_TEST(D3D12DescriptorResidencyTests,
425                       D3D12Backend({"use_d3d12_small_shader_visible_heap"}));
426