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 if (!mDevice->IsToggleEnabled(Toggle::DisableResourceSuballocation)) { 203 ResourceHeapAllocation subAllocation; 204 DAWN_TRY_ASSIGN(subAllocation, CreatePlacedResource(heapType, resourceDescriptor, 205 optimizedClearValue, initialUsage)); 206 if (subAllocation.GetInfo().mMethod != AllocationMethod::kInvalid) { 207 return std::move(subAllocation); 208 } 209 } 210 211 // If sub-allocation fails, fall-back to direct allocation (committed resource). 212 ResourceHeapAllocation directAllocation; 213 DAWN_TRY_ASSIGN(directAllocation, 214 CreateCommittedResource(heapType, resourceDescriptor, optimizedClearValue, 215 initialUsage)); 216 if (directAllocation.GetInfo().mMethod != AllocationMethod::kInvalid) { 217 return std::move(directAllocation); 218 } 219 220 // If direct allocation fails, the system is probably out of memory. 221 return DAWN_OUT_OF_MEMORY_ERROR("Allocation failed"); 222 } 223 Tick(ExecutionSerial completedSerial)224 void ResourceAllocatorManager::Tick(ExecutionSerial completedSerial) { 225 for (ResourceHeapAllocation& allocation : 226 mAllocationsToDelete.IterateUpTo(completedSerial)) { 227 if (allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated) { 228 FreeMemory(allocation); 229 } 230 } 231 mAllocationsToDelete.ClearUpTo(completedSerial); 232 233 //dawn cve2022-2399 234 mHeapsToDelete.ClearUpTo(completedSerial); 235 } 236 DeallocateMemory(ResourceHeapAllocation & allocation)237 void ResourceAllocatorManager::DeallocateMemory(ResourceHeapAllocation& allocation) { 238 if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) { 239 return; 240 } 241 242 mAllocationsToDelete.Enqueue(allocation, mDevice->GetPendingCommandSerial()); 243 244 // Directly allocated ResourceHeapAllocations are created with a heap object that must be 245 // manually deleted upon deallocation. See ResourceAllocatorManager::CreateCommittedResource 246 // for more information. Acquire this heap as a unique_ptr and add it to the queue of heaps 247 // to delete. It cannot be deleted immediately because it may be in use by in-flight or 248 // pending commands. 249 if (allocation.GetInfo().mMethod == AllocationMethod::kDirect) { 250 //dawn cve2022-2399 251 mHeapsToDelete.Enqueue(std::unique_ptr<ResourceHeapBase>(allocation.GetResourceHeap()), 252 mDevice->GetPendingCommandSerial()); 253 } 254 255 // Invalidate the allocation immediately in case one accidentally 256 // calls DeallocateMemory again using the same allocation. 257 allocation.Invalidate(); 258 259 ASSERT(allocation.GetD3D12Resource() == nullptr); 260 } 261 FreeMemory(ResourceHeapAllocation & allocation)262 void ResourceAllocatorManager::FreeMemory(ResourceHeapAllocation& allocation) { 263 ASSERT(allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated); 264 265 D3D12_HEAP_PROPERTIES heapProp; 266 allocation.GetD3D12Resource()->GetHeapProperties(&heapProp, nullptr); 267 268 const D3D12_RESOURCE_DESC resourceDescriptor = allocation.GetD3D12Resource()->GetDesc(); 269 270 const size_t resourceHeapKindIndex = 271 GetResourceHeapKind(resourceDescriptor.Dimension, heapProp.Type, 272 resourceDescriptor.Flags, mResourceHeapTier); 273 274 mSubAllocatedResourceAllocators[resourceHeapKindIndex]->Deallocate(allocation); 275 } 276 CreatePlacedResource(D3D12_HEAP_TYPE heapType,const D3D12_RESOURCE_DESC & requestedResourceDescriptor,const D3D12_CLEAR_VALUE * optimizedClearValue,D3D12_RESOURCE_STATES initialUsage)277 ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreatePlacedResource( 278 D3D12_HEAP_TYPE heapType, 279 const D3D12_RESOURCE_DESC& requestedResourceDescriptor, 280 const D3D12_CLEAR_VALUE* optimizedClearValue, 281 D3D12_RESOURCE_STATES initialUsage) { 282 const ResourceHeapKind resourceHeapKind = 283 GetResourceHeapKind(requestedResourceDescriptor.Dimension, heapType, 284 requestedResourceDescriptor.Flags, mResourceHeapTier); 285 286 D3D12_RESOURCE_DESC resourceDescriptor = requestedResourceDescriptor; 287 resourceDescriptor.Alignment = GetResourcePlacementAlignment( 288 resourceHeapKind, requestedResourceDescriptor.SampleDesc.Count, 289 requestedResourceDescriptor.Alignment); 290 291 // TODO(bryan.bernhart): Figure out how to compute the alignment without calling this 292 // twice. 293 D3D12_RESOURCE_ALLOCATION_INFO resourceInfo = 294 mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor); 295 296 // If the requested resource alignment was rejected, let D3D tell us what the 297 // required alignment is for this resource. 298 if (resourceDescriptor.Alignment != 0 && 299 resourceDescriptor.Alignment != resourceInfo.Alignment) { 300 resourceDescriptor.Alignment = 0; 301 resourceInfo = 302 mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor); 303 } 304 305 // If d3d tells us the resource size is invalid, treat the error as OOM. 306 // Otherwise, creating the resource could cause a device loss (too large). 307 // This is because NextPowerOfTwo(UINT64_MAX) overflows and proceeds to 308 // incorrectly allocate a mismatched size. 309 if (resourceInfo.SizeInBytes == 0 || 310 resourceInfo.SizeInBytes == std::numeric_limits<uint64_t>::max()) { 311 return DAWN_OUT_OF_MEMORY_ERROR("Resource allocation size was invalid."); 312 } 313 314 BuddyMemoryAllocator* allocator = 315 mSubAllocatedResourceAllocators[static_cast<size_t>(resourceHeapKind)].get(); 316 317 ResourceMemoryAllocation allocation; 318 DAWN_TRY_ASSIGN(allocation, 319 allocator->Allocate(resourceInfo.SizeInBytes, resourceInfo.Alignment)); 320 if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) { 321 return ResourceHeapAllocation{}; // invalid 322 } 323 324 Heap* heap = ToBackend(allocation.GetResourceHeap()); 325 326 // Before calling CreatePlacedResource, we must ensure the target heap is resident. 327 // CreatePlacedResource will fail if it is not. 328 DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(heap)); 329 330 // With placed resources, a single heap can be reused. 331 // The resource placed at an offset is only reclaimed 332 // upon Tick or after the last command list using the resource has completed 333 // on the GPU. This means the same physical memory is not reused 334 // within the same command-list and does not require additional synchronization (aliasing 335 // barrier). 336 // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createplacedresource 337 ComPtr<ID3D12Resource> placedResource; 338 DAWN_TRY(CheckOutOfMemoryHRESULT( 339 mDevice->GetD3D12Device()->CreatePlacedResource( 340 heap->GetD3D12Heap(), allocation.GetOffset(), &resourceDescriptor, initialUsage, 341 optimizedClearValue, IID_PPV_ARGS(&placedResource)), 342 "ID3D12Device::CreatePlacedResource")); 343 344 // After CreatePlacedResource has finished, the heap can be unlocked from residency. This 345 // will insert it into the residency LRU. 346 mDevice->GetResidencyManager()->UnlockAllocation(heap); 347 348 return ResourceHeapAllocation{allocation.GetInfo(), allocation.GetOffset(), 349 std::move(placedResource), heap}; 350 } 351 CreateCommittedResource(D3D12_HEAP_TYPE heapType,const D3D12_RESOURCE_DESC & resourceDescriptor,const D3D12_CLEAR_VALUE * optimizedClearValue,D3D12_RESOURCE_STATES initialUsage)352 ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreateCommittedResource( 353 D3D12_HEAP_TYPE heapType, 354 const D3D12_RESOURCE_DESC& resourceDescriptor, 355 const D3D12_CLEAR_VALUE* optimizedClearValue, 356 D3D12_RESOURCE_STATES initialUsage) { 357 D3D12_HEAP_PROPERTIES heapProperties; 358 heapProperties.Type = heapType; 359 heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; 360 heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; 361 heapProperties.CreationNodeMask = 0; 362 heapProperties.VisibleNodeMask = 0; 363 364 // If d3d tells us the resource size is invalid, treat the error as OOM. 365 // Otherwise, creating the resource could cause a device loss (too large). 366 // This is because NextPowerOfTwo(UINT64_MAX) overflows and proceeds to 367 // incorrectly allocate a mismatched size. 368 D3D12_RESOURCE_ALLOCATION_INFO resourceInfo = 369 mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor); 370 if (resourceInfo.SizeInBytes == 0 || 371 resourceInfo.SizeInBytes == std::numeric_limits<uint64_t>::max()) { 372 return DAWN_OUT_OF_MEMORY_ERROR("Resource allocation size was invalid."); 373 } 374 375 if (resourceInfo.SizeInBytes > kMaxHeapSize) { 376 return ResourceHeapAllocation{}; // Invalid 377 } 378 379 // CreateCommittedResource will implicitly make the created resource resident. We must 380 // ensure enough free memory exists before allocating to avoid an out-of-memory error when 381 // overcommitted. 382 DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanAllocate( 383 resourceInfo.SizeInBytes, GetMemorySegment(mDevice, heapType))); 384 385 // Note: Heap flags are inferred by the resource descriptor and do not need to be explicitly 386 // provided to CreateCommittedResource. 387 ComPtr<ID3D12Resource> committedResource; 388 DAWN_TRY(CheckOutOfMemoryHRESULT( 389 mDevice->GetD3D12Device()->CreateCommittedResource( 390 &heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDescriptor, initialUsage, 391 optimizedClearValue, IID_PPV_ARGS(&committedResource)), 392 "ID3D12Device::CreateCommittedResource")); 393 394 // When using CreateCommittedResource, D3D12 creates an implicit heap that contains the 395 // resource allocation. Because Dawn's memory residency management occurs at the resource 396 // heap granularity, every directly allocated ResourceHeapAllocation also stores a Heap 397 // object. This object is created manually, and must be deleted manually upon deallocation 398 // of the committed resource. 399 Heap* heap = new Heap(committedResource, GetMemorySegment(mDevice, heapType), 400 resourceInfo.SizeInBytes); 401 402 // Calling CreateCommittedResource implicitly calls MakeResident on the resource. We must 403 // track this to avoid calling MakeResident a second time. 404 mDevice->GetResidencyManager()->TrackResidentAllocation(heap); 405 406 AllocationInfo info; 407 info.mMethod = AllocationMethod::kDirect; 408 409 return ResourceHeapAllocation{info, 410 /*offset*/ 0, std::move(committedResource), heap}; 411 } 412 DestroyPool()413 void ResourceAllocatorManager::DestroyPool() { 414 for (auto& alloc : mPooledHeapAllocators) { 415 alloc->DestroyPool(); 416 } 417 } 418 419 }} // namespace dawn_native::d3d12 420