1 // Copyright 2019 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/d3d12/ResourceAllocatorManagerD3D12.h"
16 
17 #include "dawn_native/d3d12/D3D12Error.h"
18 #include "dawn_native/d3d12/DeviceD3D12.h"
19 #include "dawn_native/d3d12/HeapAllocatorD3D12.h"
20 #include "dawn_native/d3d12/HeapD3D12.h"
21 #include "dawn_native/d3d12/ResidencyManagerD3D12.h"
22 #include "dawn_native/d3d12/UtilsD3D12.h"
23 
24 namespace dawn_native { namespace d3d12 {
25     namespace {
GetMemorySegment(Device * device,D3D12_HEAP_TYPE heapType)26         MemorySegment GetMemorySegment(Device* device, D3D12_HEAP_TYPE heapType) {
27             if (device->GetDeviceInfo().isUMA) {
28                 return MemorySegment::Local;
29             }
30 
31             D3D12_HEAP_PROPERTIES heapProperties =
32                 device->GetD3D12Device()->GetCustomHeapProperties(0, heapType);
33 
34             if (heapProperties.MemoryPoolPreference == D3D12_MEMORY_POOL_L1) {
35                 return MemorySegment::Local;
36             }
37 
38             return MemorySegment::NonLocal;
39         }
40 
GetD3D12HeapType(ResourceHeapKind resourceHeapKind)41         D3D12_HEAP_TYPE GetD3D12HeapType(ResourceHeapKind resourceHeapKind) {
42             switch (resourceHeapKind) {
43                 case Readback_OnlyBuffers:
44                 case Readback_AllBuffersAndTextures:
45                     return D3D12_HEAP_TYPE_READBACK;
46                 case Default_AllBuffersAndTextures:
47                 case Default_OnlyBuffers:
48                 case Default_OnlyNonRenderableOrDepthTextures:
49                 case Default_OnlyRenderableOrDepthTextures:
50                     return D3D12_HEAP_TYPE_DEFAULT;
51                 case Upload_OnlyBuffers:
52                 case Upload_AllBuffersAndTextures:
53                     return D3D12_HEAP_TYPE_UPLOAD;
54                 case EnumCount:
55                     UNREACHABLE();
56             }
57         }
58 
GetD3D12HeapFlags(ResourceHeapKind resourceHeapKind)59         D3D12_HEAP_FLAGS GetD3D12HeapFlags(ResourceHeapKind resourceHeapKind) {
60             switch (resourceHeapKind) {
61                 case Default_AllBuffersAndTextures:
62                 case Readback_AllBuffersAndTextures:
63                 case Upload_AllBuffersAndTextures:
64                     return D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES;
65                 case Default_OnlyBuffers:
66                 case Readback_OnlyBuffers:
67                 case Upload_OnlyBuffers:
68                     return D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
69                 case Default_OnlyNonRenderableOrDepthTextures:
70                     return D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES;
71                 case Default_OnlyRenderableOrDepthTextures:
72                     return D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES;
73                 case EnumCount:
74                     UNREACHABLE();
75             }
76         }
77 
GetResourceHeapKind(D3D12_RESOURCE_DIMENSION dimension,D3D12_HEAP_TYPE heapType,D3D12_RESOURCE_FLAGS flags,uint32_t resourceHeapTier)78         ResourceHeapKind GetResourceHeapKind(D3D12_RESOURCE_DIMENSION dimension,
79                                              D3D12_HEAP_TYPE heapType,
80                                              D3D12_RESOURCE_FLAGS flags,
81                                              uint32_t resourceHeapTier) {
82             if (resourceHeapTier >= 2) {
83                 switch (heapType) {
84                     case D3D12_HEAP_TYPE_UPLOAD:
85                         return Upload_AllBuffersAndTextures;
86                     case D3D12_HEAP_TYPE_DEFAULT:
87                         return Default_AllBuffersAndTextures;
88                     case D3D12_HEAP_TYPE_READBACK:
89                         return Readback_AllBuffersAndTextures;
90                     default:
91                         UNREACHABLE();
92                 }
93             }
94 
95             switch (dimension) {
96                 case D3D12_RESOURCE_DIMENSION_BUFFER: {
97                     switch (heapType) {
98                         case D3D12_HEAP_TYPE_UPLOAD:
99                             return Upload_OnlyBuffers;
100                         case D3D12_HEAP_TYPE_DEFAULT:
101                             return Default_OnlyBuffers;
102                         case D3D12_HEAP_TYPE_READBACK:
103                             return Readback_OnlyBuffers;
104                         default:
105                             UNREACHABLE();
106                     }
107                     break;
108                 }
109                 case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
110                 case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
111                 case D3D12_RESOURCE_DIMENSION_TEXTURE3D: {
112                     switch (heapType) {
113                         case D3D12_HEAP_TYPE_DEFAULT: {
114                             if ((flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) ||
115                                 (flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)) {
116                                 return Default_OnlyRenderableOrDepthTextures;
117                             }
118                             return Default_OnlyNonRenderableOrDepthTextures;
119                         }
120 
121                         default:
122                             UNREACHABLE();
123                     }
124                     break;
125                 }
126                 default:
127                     UNREACHABLE();
128             }
129         }
130 
GetResourcePlacementAlignment(ResourceHeapKind resourceHeapKind,uint32_t sampleCount,uint64_t requestedAlignment)131         uint64_t GetResourcePlacementAlignment(ResourceHeapKind resourceHeapKind,
132                                                uint32_t sampleCount,
133                                                uint64_t requestedAlignment) {
134             switch (resourceHeapKind) {
135                 // Small resources can take advantage of smaller alignments. For example,
136                 // if the most detailed mip can fit under 64KB, 4KB alignments can be used.
137                 // Must be non-depth or without render-target to use small resource alignment.
138                 // This also applies to MSAA textures (4MB => 64KB).
139                 //
140                 // Note: Only known to be used for small textures; however, MSDN suggests
141                 // it could be extended for more cases. If so, this could default to always
142                 // attempt small resource placement.
143                 // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_resource_desc
144                 case Default_OnlyNonRenderableOrDepthTextures:
145                     return (sampleCount > 1) ? D3D12_SMALL_MSAA_RESOURCE_PLACEMENT_ALIGNMENT
146                                              : D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT;
147                 default:
148                     return requestedAlignment;
149             }
150         }
151 
IsClearValueOptimizable(const D3D12_RESOURCE_DESC & resourceDescriptor)152         bool IsClearValueOptimizable(const D3D12_RESOURCE_DESC& resourceDescriptor) {
153             // Optimized clear color cannot be set on buffers, non-render-target/depth-stencil
154             // textures, or typeless resources
155             // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource
156             // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createplacedresource
157             return !IsTypeless(resourceDescriptor.Format) &&
158                    resourceDescriptor.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER &&
159                    (resourceDescriptor.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET |
160                                                 D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0;
161         }
162 
163     }  // namespace
164 
ResourceAllocatorManager(Device * device)165     ResourceAllocatorManager::ResourceAllocatorManager(Device* device) : mDevice(device) {
166         mResourceHeapTier = (mDevice->IsToggleEnabled(Toggle::UseD3D12ResourceHeapTier2))
167                                 ? mDevice->GetDeviceInfo().resourceHeapTier
168                                 : 1;
169 
170         for (uint32_t i = 0; i < ResourceHeapKind::EnumCount; i++) {
171             const ResourceHeapKind resourceHeapKind = static_cast<ResourceHeapKind>(i);
172             mHeapAllocators[i] = std::make_unique<HeapAllocator>(
173                 mDevice, GetD3D12HeapType(resourceHeapKind), GetD3D12HeapFlags(resourceHeapKind),
174                 GetMemorySegment(device, GetD3D12HeapType(resourceHeapKind)));
175             mPooledHeapAllocators[i] =
176                 std::make_unique<PooledResourceMemoryAllocator>(mHeapAllocators[i].get());
177             mSubAllocatedResourceAllocators[i] = std::make_unique<BuddyMemoryAllocator>(
178                 kMaxHeapSize, kMinHeapSize, mPooledHeapAllocators[i].get());
179         }
180     }
181 
AllocateMemory(D3D12_HEAP_TYPE heapType,const D3D12_RESOURCE_DESC & resourceDescriptor,D3D12_RESOURCE_STATES initialUsage)182     ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::AllocateMemory(
183         D3D12_HEAP_TYPE heapType,
184         const D3D12_RESOURCE_DESC& resourceDescriptor,
185         D3D12_RESOURCE_STATES initialUsage) {
186         // In order to suppress a warning in the D3D12 debug layer, we need to specify an
187         // optimized clear value. As there are no negative consequences when picking a mismatched
188         // clear value, we use zero as the optimized clear value. This also enables fast clears on
189         // some architectures.
190         D3D12_CLEAR_VALUE zero{};
191         D3D12_CLEAR_VALUE* optimizedClearValue = nullptr;
192         if (IsClearValueOptimizable(resourceDescriptor)) {
193             zero.Format = resourceDescriptor.Format;
194             optimizedClearValue = &zero;
195         }
196 
197         // TODO(bryan.bernhart@intel.com): Conditionally disable sub-allocation.
198         // For very large resources, there is no benefit to suballocate.
199         // For very small resources, it is inefficent to suballocate given the min. heap
200         // size could be much larger then the resource allocation.
201         // Attempt to satisfy the request using sub-allocation (placed resource in a heap).
202         ResourceHeapAllocation subAllocation;
203         DAWN_TRY_ASSIGN(subAllocation, CreatePlacedResource(heapType, resourceDescriptor,
204                                                             optimizedClearValue, initialUsage));
205         if (subAllocation.GetInfo().mMethod != AllocationMethod::kInvalid) {
206             return std::move(subAllocation);
207         }
208 
209         // If sub-allocation fails, fall-back to direct allocation (committed resource).
210         ResourceHeapAllocation directAllocation;
211         DAWN_TRY_ASSIGN(directAllocation,
212                         CreateCommittedResource(heapType, resourceDescriptor, optimizedClearValue,
213                                                 initialUsage));
214         if (directAllocation.GetInfo().mMethod != AllocationMethod::kInvalid) {
215             return std::move(directAllocation);
216         }
217 
218         // If direct allocation fails, the system is probably out of memory.
219         return DAWN_OUT_OF_MEMORY_ERROR("Allocation failed");
220     }
221 
Tick(ExecutionSerial completedSerial)222     void ResourceAllocatorManager::Tick(ExecutionSerial completedSerial) {
223         for (ResourceHeapAllocation& allocation :
224              mAllocationsToDelete.IterateUpTo(completedSerial)) {
225             if (allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated) {
226                 FreeMemory(allocation);
227             }
228         }
229         mAllocationsToDelete.ClearUpTo(completedSerial);
230     }
231 
DeallocateMemory(ResourceHeapAllocation & allocation)232     void ResourceAllocatorManager::DeallocateMemory(ResourceHeapAllocation& allocation) {
233         if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) {
234             return;
235         }
236 
237         mAllocationsToDelete.Enqueue(allocation, mDevice->GetPendingCommandSerial());
238 
239         // Directly allocated ResourceHeapAllocations are created with a heap object that must be
240         // manually deleted upon deallocation. See ResourceAllocatorManager::CreateCommittedResource
241         // for more information.
242         if (allocation.GetInfo().mMethod == AllocationMethod::kDirect) {
243             delete allocation.GetResourceHeap();
244         }
245 
246         // Invalidate the allocation immediately in case one accidentally
247         // calls DeallocateMemory again using the same allocation.
248         allocation.Invalidate();
249 
250         ASSERT(allocation.GetD3D12Resource() == nullptr);
251     }
252 
FreeMemory(ResourceHeapAllocation & allocation)253     void ResourceAllocatorManager::FreeMemory(ResourceHeapAllocation& allocation) {
254         ASSERT(allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated);
255 
256         D3D12_HEAP_PROPERTIES heapProp;
257         allocation.GetD3D12Resource()->GetHeapProperties(&heapProp, nullptr);
258 
259         const D3D12_RESOURCE_DESC resourceDescriptor = allocation.GetD3D12Resource()->GetDesc();
260 
261         const size_t resourceHeapKindIndex =
262             GetResourceHeapKind(resourceDescriptor.Dimension, heapProp.Type,
263                                 resourceDescriptor.Flags, mResourceHeapTier);
264 
265         mSubAllocatedResourceAllocators[resourceHeapKindIndex]->Deallocate(allocation);
266     }
267 
CreatePlacedResource(D3D12_HEAP_TYPE heapType,const D3D12_RESOURCE_DESC & requestedResourceDescriptor,const D3D12_CLEAR_VALUE * optimizedClearValue,D3D12_RESOURCE_STATES initialUsage)268     ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreatePlacedResource(
269         D3D12_HEAP_TYPE heapType,
270         const D3D12_RESOURCE_DESC& requestedResourceDescriptor,
271         const D3D12_CLEAR_VALUE* optimizedClearValue,
272         D3D12_RESOURCE_STATES initialUsage) {
273         const ResourceHeapKind resourceHeapKind =
274             GetResourceHeapKind(requestedResourceDescriptor.Dimension, heapType,
275                                 requestedResourceDescriptor.Flags, mResourceHeapTier);
276 
277         D3D12_RESOURCE_DESC resourceDescriptor = requestedResourceDescriptor;
278         resourceDescriptor.Alignment = GetResourcePlacementAlignment(
279             resourceHeapKind, requestedResourceDescriptor.SampleDesc.Count,
280             requestedResourceDescriptor.Alignment);
281 
282         // TODO(bryan.bernhart): Figure out how to compute the alignment without calling this
283         // twice.
284         D3D12_RESOURCE_ALLOCATION_INFO resourceInfo =
285             mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor);
286 
287         // If the requested resource alignment was rejected, let D3D tell us what the
288         // required alignment is for this resource.
289         if (resourceDescriptor.Alignment != 0 &&
290             resourceDescriptor.Alignment != resourceInfo.Alignment) {
291             resourceDescriptor.Alignment = 0;
292             resourceInfo =
293                 mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor);
294         }
295 
296         // If d3d tells us the resource size is invalid, treat the error as OOM.
297         // Otherwise, creating the resource could cause a device loss (too large).
298         // This is because NextPowerOfTwo(UINT64_MAX) overflows and proceeds to
299         // incorrectly allocate a mismatched size.
300         if (resourceInfo.SizeInBytes == 0 ||
301             resourceInfo.SizeInBytes == std::numeric_limits<uint64_t>::max()) {
302             return DAWN_OUT_OF_MEMORY_ERROR("Resource allocation size was invalid.");
303         }
304 
305         BuddyMemoryAllocator* allocator =
306             mSubAllocatedResourceAllocators[static_cast<size_t>(resourceHeapKind)].get();
307 
308         ResourceMemoryAllocation allocation;
309         DAWN_TRY_ASSIGN(allocation,
310                         allocator->Allocate(resourceInfo.SizeInBytes, resourceInfo.Alignment));
311         if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) {
312             return ResourceHeapAllocation{};  // invalid
313         }
314 
315         Heap* heap = ToBackend(allocation.GetResourceHeap());
316 
317         // Before calling CreatePlacedResource, we must ensure the target heap is resident.
318         // CreatePlacedResource will fail if it is not.
319         DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(heap));
320 
321         // With placed resources, a single heap can be reused.
322         // The resource placed at an offset is only reclaimed
323         // upon Tick or after the last command list using the resource has completed
324         // on the GPU. This means the same physical memory is not reused
325         // within the same command-list and does not require additional synchronization (aliasing
326         // barrier).
327         // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createplacedresource
328         ComPtr<ID3D12Resource> placedResource;
329         DAWN_TRY(CheckOutOfMemoryHRESULT(
330             mDevice->GetD3D12Device()->CreatePlacedResource(
331                 heap->GetD3D12Heap(), allocation.GetOffset(), &resourceDescriptor, initialUsage,
332                 optimizedClearValue, IID_PPV_ARGS(&placedResource)),
333             "ID3D12Device::CreatePlacedResource"));
334 
335         // After CreatePlacedResource has finished, the heap can be unlocked from residency. This
336         // will insert it into the residency LRU.
337         mDevice->GetResidencyManager()->UnlockAllocation(heap);
338 
339         return ResourceHeapAllocation{allocation.GetInfo(), allocation.GetOffset(),
340                                       std::move(placedResource), heap};
341     }
342 
CreateCommittedResource(D3D12_HEAP_TYPE heapType,const D3D12_RESOURCE_DESC & resourceDescriptor,const D3D12_CLEAR_VALUE * optimizedClearValue,D3D12_RESOURCE_STATES initialUsage)343     ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreateCommittedResource(
344         D3D12_HEAP_TYPE heapType,
345         const D3D12_RESOURCE_DESC& resourceDescriptor,
346         const D3D12_CLEAR_VALUE* optimizedClearValue,
347         D3D12_RESOURCE_STATES initialUsage) {
348         D3D12_HEAP_PROPERTIES heapProperties;
349         heapProperties.Type = heapType;
350         heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
351         heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
352         heapProperties.CreationNodeMask = 0;
353         heapProperties.VisibleNodeMask = 0;
354 
355         // If d3d tells us the resource size is invalid, treat the error as OOM.
356         // Otherwise, creating the resource could cause a device loss (too large).
357         // This is because NextPowerOfTwo(UINT64_MAX) overflows and proceeds to
358         // incorrectly allocate a mismatched size.
359         D3D12_RESOURCE_ALLOCATION_INFO resourceInfo =
360             mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor);
361         if (resourceInfo.SizeInBytes == 0 ||
362             resourceInfo.SizeInBytes == std::numeric_limits<uint64_t>::max()) {
363             return DAWN_OUT_OF_MEMORY_ERROR("Resource allocation size was invalid.");
364         }
365 
366         if (resourceInfo.SizeInBytes > kMaxHeapSize) {
367             return ResourceHeapAllocation{};  // Invalid
368         }
369 
370         // CreateCommittedResource will implicitly make the created resource resident. We must
371         // ensure enough free memory exists before allocating to avoid an out-of-memory error when
372         // overcommitted.
373         DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanAllocate(
374             resourceInfo.SizeInBytes, GetMemorySegment(mDevice, heapType)));
375 
376         // Note: Heap flags are inferred by the resource descriptor and do not need to be explicitly
377         // provided to CreateCommittedResource.
378         ComPtr<ID3D12Resource> committedResource;
379         DAWN_TRY(CheckOutOfMemoryHRESULT(
380             mDevice->GetD3D12Device()->CreateCommittedResource(
381                 &heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDescriptor, initialUsage,
382                 optimizedClearValue, IID_PPV_ARGS(&committedResource)),
383             "ID3D12Device::CreateCommittedResource"));
384 
385         // When using CreateCommittedResource, D3D12 creates an implicit heap that contains the
386         // resource allocation. Because Dawn's memory residency management occurs at the resource
387         // heap granularity, every directly allocated ResourceHeapAllocation also stores a Heap
388         // object. This object is created manually, and must be deleted manually upon deallocation
389         // of the committed resource.
390         Heap* heap = new Heap(committedResource, GetMemorySegment(mDevice, heapType),
391                               resourceInfo.SizeInBytes);
392 
393         // Calling CreateCommittedResource implicitly calls MakeResident on the resource. We must
394         // track this to avoid calling MakeResident a second time.
395         mDevice->GetResidencyManager()->TrackResidentAllocation(heap);
396 
397         AllocationInfo info;
398         info.mMethod = AllocationMethod::kDirect;
399 
400         return ResourceHeapAllocation{info,
401                                       /*offset*/ 0, std::move(committedResource), heap};
402     }
403 
DestroyPool()404     void ResourceAllocatorManager::DestroyPool() {
405         for (auto& alloc : mPooledHeapAllocators) {
406             alloc->DestroyPool();
407         }
408     }
409 
410 }}  // namespace dawn_native::d3d12
411