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, ©);
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