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(crbug.com/dawn/849): 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