| // Copyright 2019 The Dawn Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "dawn/native/d3d12/ResourceAllocatorManagerD3D12.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <utility> |
| |
| #include "dawn/native/d3d/D3DError.h" |
| #include "dawn/native/d3d12/DeviceD3D12.h" |
| #include "dawn/native/d3d12/HeapAllocatorD3D12.h" |
| #include "dawn/native/d3d12/HeapD3D12.h" |
| #include "dawn/native/d3d12/ResidencyManagerD3D12.h" |
| #include "dawn/native/d3d12/UtilsD3D12.h" |
| |
| namespace dawn::native::d3d12 { |
| namespace { |
| MemorySegment GetMemorySegment(Device* device, D3D12_HEAP_TYPE heapType) { |
| if (device->GetDeviceInfo().isUMA) { |
| return MemorySegment::Local; |
| } |
| |
| D3D12_HEAP_PROPERTIES heapProperties = |
| device->GetD3D12Device()->GetCustomHeapProperties(0, heapType); |
| |
| if (heapProperties.MemoryPoolPreference == D3D12_MEMORY_POOL_L1) { |
| return MemorySegment::Local; |
| } |
| |
| return MemorySegment::NonLocal; |
| } |
| |
| D3D12_HEAP_TYPE GetD3D12HeapType(ResourceHeapKind resourceHeapKind) { |
| switch (resourceHeapKind) { |
| case Readback_OnlyBuffers: |
| case Readback_AllBuffersAndTextures: |
| return D3D12_HEAP_TYPE_READBACK; |
| case Default_AllBuffersAndTextures: |
| case Default_OnlyBuffers: |
| case Default_OnlyNonRenderableOrDepthTextures: |
| case Default_OnlyRenderableOrDepthTextures: |
| return D3D12_HEAP_TYPE_DEFAULT; |
| case Upload_OnlyBuffers: |
| case Upload_AllBuffersAndTextures: |
| return D3D12_HEAP_TYPE_UPLOAD; |
| case EnumCount: |
| UNREACHABLE(); |
| } |
| } |
| |
| D3D12_HEAP_FLAGS GetD3D12HeapFlags(ResourceHeapKind resourceHeapKind) { |
| switch (resourceHeapKind) { |
| case Default_AllBuffersAndTextures: |
| case Readback_AllBuffersAndTextures: |
| case Upload_AllBuffersAndTextures: |
| return D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES; |
| case Default_OnlyBuffers: |
| case Readback_OnlyBuffers: |
| case Upload_OnlyBuffers: |
| return D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; |
| case Default_OnlyNonRenderableOrDepthTextures: |
| return D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES; |
| case Default_OnlyRenderableOrDepthTextures: |
| return D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; |
| case EnumCount: |
| UNREACHABLE(); |
| } |
| } |
| |
| ResourceHeapKind GetResourceHeapKind(D3D12_RESOURCE_DIMENSION dimension, |
| D3D12_HEAP_TYPE heapType, |
| D3D12_RESOURCE_FLAGS flags, |
| uint32_t resourceHeapTier) { |
| if (resourceHeapTier >= 2) { |
| switch (heapType) { |
| case D3D12_HEAP_TYPE_UPLOAD: |
| return Upload_AllBuffersAndTextures; |
| case D3D12_HEAP_TYPE_DEFAULT: |
| return Default_AllBuffersAndTextures; |
| case D3D12_HEAP_TYPE_READBACK: |
| return Readback_AllBuffersAndTextures; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| switch (dimension) { |
| case D3D12_RESOURCE_DIMENSION_BUFFER: { |
| switch (heapType) { |
| case D3D12_HEAP_TYPE_UPLOAD: |
| return Upload_OnlyBuffers; |
| case D3D12_HEAP_TYPE_DEFAULT: |
| return Default_OnlyBuffers; |
| case D3D12_HEAP_TYPE_READBACK: |
| return Readback_OnlyBuffers; |
| default: |
| UNREACHABLE(); |
| } |
| break; |
| } |
| case D3D12_RESOURCE_DIMENSION_TEXTURE1D: |
| case D3D12_RESOURCE_DIMENSION_TEXTURE2D: |
| case D3D12_RESOURCE_DIMENSION_TEXTURE3D: { |
| switch (heapType) { |
| case D3D12_HEAP_TYPE_DEFAULT: { |
| if ((flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) || |
| (flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)) { |
| return Default_OnlyRenderableOrDepthTextures; |
| } |
| return Default_OnlyNonRenderableOrDepthTextures; |
| } |
| |
| default: |
| UNREACHABLE(); |
| } |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| uint64_t GetResourcePlacementAlignment(ResourceHeapKind resourceHeapKind, |
| uint32_t sampleCount, |
| uint64_t requestedAlignment) { |
| switch (resourceHeapKind) { |
| // Small resources can take advantage of smaller alignments. For example, |
| // if the most detailed mip can fit under 64KB, 4KB alignments can be used. |
| // Must be non-depth or without render-target to use small resource alignment. |
| // This also applies to MSAA textures (4MB => 64KB). |
| // |
| // Note: Only known to be used for small textures; however, MSDN suggests |
| // it could be extended for more cases. If so, this could default to always |
| // attempt small resource placement. |
| // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_resource_desc |
| case Default_OnlyNonRenderableOrDepthTextures: |
| return (sampleCount > 1) ? D3D12_SMALL_MSAA_RESOURCE_PLACEMENT_ALIGNMENT |
| : D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT; |
| default: |
| return requestedAlignment; |
| } |
| } |
| |
| bool IsClearValueOptimizable(DeviceBase* device, const D3D12_RESOURCE_DESC& resourceDescriptor) { |
| if (device->IsToggleEnabled(Toggle::D3D12DontSetClearValueOnDepthTextureCreation)) { |
| switch (resourceDescriptor.Format) { |
| case DXGI_FORMAT_D16_UNORM: |
| case DXGI_FORMAT_D32_FLOAT: |
| case DXGI_FORMAT_D24_UNORM_S8_UINT: |
| case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: |
| return false; |
| default: |
| break; |
| } |
| } |
| |
| // Optimized clear color cannot be set on buffers, non-render-target/depth-stencil |
| // textures, or typeless resources |
| // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource |
| // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createplacedresource |
| return !d3d::IsTypeless(resourceDescriptor.Format) && |
| resourceDescriptor.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER && |
| (resourceDescriptor.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | |
| D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0; |
| } |
| |
| uint32_t GetColumnPitch(uint32_t baseHeight, uint32_t mipLevelCount) { |
| // This function returns the number of rows of block for a single layer with all mipmaps. |
| // |
| // Below is a simple diagram about texture memory layout for one single layer of a mipmap |
| // texture. For details about texture memory layout on Intel Gen12 GPU, read page 78 at |
| // https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-tgl-vol05-memory_data_formats.pdf. |
| // ---------------------------------------------- --- |
| // | | | |
| // | | |
| // | | |
| // | | |
| // | LOD 0 | |
| // | | |
| // | | |
| // | | column pitch (aka QPitch) |
| // | | |
| // | | |
| // ---------------------------------------------- |
| // | | | |
| // | | LOD2 | |
| // | LOD 1 |--------- |
| // | | LOD3 | |
| // | |------- |
| // | | . |
| // ---------------------- . | |
| // . --- |
| |
| uint32_t level1Height = 0; |
| uint32_t level2ToTailHeight = 0; |
| if (mipLevelCount >= 2) { |
| level1Height = std::max(baseHeight >> 1, 1u); |
| |
| for (uint32_t level = 2; level < mipLevelCount; ++level) { |
| level2ToTailHeight += std::max(baseHeight >> level, 1u); |
| } |
| } |
| // The height of level 2 to tail (or max) can be greater than the height of level 1. For |
| // example, if the single layer's dimension is 16x4 and it has full mipmaps, then there are 5 |
| // levels: 16x4, 8x2, 4x1, 2x1, 1x1. So level1Height is 2, while level2ToTailHeight is 1+1+1 |
| // = 3. |
| uint32_t columnPitch = baseHeight + std::max(level1Height, level2ToTailHeight); |
| |
| // The number of rows of block for a texture must be a multiple of 4. |
| return Align(columnPitch, 4); |
| } |
| |
| uint32_t ComputeExtraArraySizeForIntelGen12(uint32_t width, |
| uint32_t height, |
| uint32_t arrayLayerCount, |
| uint32_t mipLevelCount, |
| uint32_t sampleCount, |
| uint32_t colorFormatBytesPerBlock) { |
| // For details about texture memory layout on Intel Gen12 GPU, read |
| // https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-tgl-vol05-memory_data_formats.pdf. |
| // - Texture memory layout: from <Surface Memory Organizations> to |
| // <Surface Padding Requirement>. |
| // - Tile-based memory: the entire section of <Address Tiling Function Introduction>. |
| constexpr uint32_t kPageSize = 4 * 1024; |
| constexpr uint32_t kLinearAlignment = 4 * kPageSize; |
| |
| // There are two tile modes: TileYS (64KB per tile) and TileYf (4KB per tile). TileYS is used |
| // here because it may have more paddings and therefore requires more extra layers to work |
| // around the bug. |
| constexpr uint32_t kTileSize = 16 * kPageSize; |
| |
| // Tile's width and height vary according to format bit-wise (colorFormatBytesPerBlock) |
| uint32_t tileHeight = 0; |
| switch (colorFormatBytesPerBlock) { |
| case 1: |
| tileHeight = 256; |
| break; |
| case 2: |
| case 4: |
| tileHeight = 128; |
| break; |
| case 8: |
| case 16: |
| tileHeight = 64; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| uint32_t tileWidth = kTileSize / tileHeight; |
| |
| uint64_t layerxSamples = arrayLayerCount * sampleCount; |
| |
| if (layerxSamples <= 1) { |
| return 0; |
| } |
| |
| uint32_t columnPitch = GetColumnPitch(height, mipLevelCount); |
| |
| uint64_t totalWidth = width * colorFormatBytesPerBlock; |
| uint64_t totalHeight = columnPitch * layerxSamples; |
| |
| // Texture should be aligned on both tile width (512 bytes) and tile height (128 rows) on Intel |
| // Gen12 GPU |
| uint32_t mainTileCols = Align(totalWidth, tileWidth) / tileWidth; |
| uint32_t mainTileRows = Align(totalHeight, tileHeight) / tileHeight; |
| uint64_t mainTileCount = mainTileCols * mainTileRows; |
| |
| // There is a bug in Intel old drivers to compute the auxiliary memory size (auxSize) of the |
| // color texture, which is calculated from the main memory size (mainSize) of the texture. Note |
| // that memory allocation for mainSize itself is correct. But during memory allocation for |
| // auxSize, it re-caculated mainSize and did it in a wrong way. The incorrect algorithm doesn't |
| // respect alignment requirements from tile-based texture memory layout. It just simple aligned |
| // to a constant value (16K) for each sample and layer. |
| uint64_t expectedMainSize = mainTileCount * kTileSize; |
| uint64_t actualMainSize = Align(columnPitch * totalWidth, kLinearAlignment) * layerxSamples; |
| |
| // If the incorrect mainSize calculation lead to less-than-expected auxSize, texture corruption |
| // is very likely to happen for any color texture access like texture copy, rendering, sampling, |
| // etc. So we have to allocate a few more extra layers to offset the less-than-expected auxSize. |
| // However, it is fine if the incorrect mainSize calculation doesn't introduce less auxSize. For |
| // example, if correct mainSize is 3.8M, it requires 4 pages of auxSize (16K). Any incorrect |
| // mainSize between 3.0+ M and 4.0M also requires 16K auxSize according to the calculation: |
| // auxSize = Align(mainSize >> 8, kPageSize). And greater auxSize is also fine. But if mainSize |
| // is less than 3.0M, its auxSize will be less than 16K and hence texture corruption is caused. |
| uint64_t expectedAuxSize = Align(expectedMainSize >> 8, kPageSize); |
| uint64_t actualAuxSize = Align(actualMainSize >> 8, kPageSize); |
| if (actualAuxSize < expectedAuxSize) { |
| uint64_t actualMainSizePerLayer = actualMainSize / arrayLayerCount; |
| return (expectedMainSize - actualMainSize + actualMainSizePerLayer - 1) / |
| actualMainSizePerLayer; |
| } |
| return 0; |
| } |
| |
| bool ShouldAllocateAsCommittedResource(Device* device, bool forceAllocateAsCommittedResource) { |
| return forceAllocateAsCommittedResource || |
| device->IsToggleEnabled(Toggle::DisableResourceSuballocation); |
| } |
| |
| } // namespace |
| |
| ResourceAllocatorManager::ResourceAllocatorManager(Device* device) : mDevice(device) { |
| mResourceHeapTier = (mDevice->IsToggleEnabled(Toggle::UseD3D12ResourceHeapTier2)) |
| ? mDevice->GetDeviceInfo().resourceHeapTier |
| : 1; |
| |
| for (uint32_t i = 0; i < ResourceHeapKind::EnumCount; i++) { |
| const ResourceHeapKind resourceHeapKind = static_cast<ResourceHeapKind>(i); |
| mHeapAllocators[i] = std::make_unique<HeapAllocator>( |
| mDevice, GetD3D12HeapType(resourceHeapKind), GetD3D12HeapFlags(resourceHeapKind), |
| GetMemorySegment(device, GetD3D12HeapType(resourceHeapKind))); |
| mPooledHeapAllocators[i] = |
| std::make_unique<PooledResourceMemoryAllocator>(mHeapAllocators[i].get()); |
| mSubAllocatedResourceAllocators[i] = std::make_unique<BuddyMemoryAllocator>( |
| kMaxHeapSize, kMinHeapSize, mPooledHeapAllocators[i].get()); |
| } |
| } |
| |
| ResourceAllocatorManager::~ResourceAllocatorManager() { |
| // Ensure any remaining objects go through the same shutdown path as normal usage. |
| // Placed resources must be released before any heaps they reside in. |
| Tick(std::numeric_limits<ExecutionSerial>::max()); |
| DestroyPool(); |
| |
| ASSERT(mAllocationsToDelete.Empty()); |
| ASSERT(mHeapsToDelete.Empty()); |
| } |
| |
| ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::AllocateMemory( |
| D3D12_HEAP_TYPE heapType, |
| const D3D12_RESOURCE_DESC& resourceDescriptor, |
| D3D12_RESOURCE_STATES initialUsage, |
| uint32_t colorFormatBytesPerBlock, |
| bool forceAllocateAsCommittedResource) { |
| // In order to suppress a warning in the D3D12 debug layer, we need to specify an |
| // optimized clear value. As there are no negative consequences when picking a mismatched |
| // clear value, we use zero as the optimized clear value. This also enables fast clears on |
| // some architectures. |
| D3D12_CLEAR_VALUE zero{}; |
| D3D12_CLEAR_VALUE* optimizedClearValue = nullptr; |
| if (IsClearValueOptimizable(mDevice, resourceDescriptor)) { |
| zero.Format = resourceDescriptor.Format; |
| optimizedClearValue = &zero; |
| } |
| |
| // If we are allocating memory for a 2D array texture with a color format on D3D12 backend, |
| // we need to allocate extra layers on some Intel Gen12 devices, see crbug.com/dawn/949 |
| // for details. |
| D3D12_RESOURCE_DESC revisedDescriptor = resourceDescriptor; |
| if (mDevice->IsToggleEnabled(Toggle::D3D12AllocateExtraMemoryFor2DArrayColorTexture) && |
| resourceDescriptor.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && |
| resourceDescriptor.DepthOrArraySize > 1 && colorFormatBytesPerBlock > 0) { |
| // Multisample textures have one layer at most. Only non-multisample textures need the |
| // workaround. |
| ASSERT(revisedDescriptor.SampleDesc.Count <= 1); |
| revisedDescriptor.DepthOrArraySize += ComputeExtraArraySizeForIntelGen12( |
| resourceDescriptor.Width, resourceDescriptor.Height, |
| resourceDescriptor.DepthOrArraySize, resourceDescriptor.MipLevels, |
| resourceDescriptor.SampleDesc.Count, colorFormatBytesPerBlock); |
| } |
| |
| // TODO(crbug.com/dawn/849): Conditionally disable sub-allocation. |
| // For very large resources, there is no benefit to suballocate. |
| // For very small resources, it is inefficent to suballocate given the min. heap |
| // size could be much larger then the resource allocation. |
| // Attempt to satisfy the request using sub-allocation (placed resource in a heap). |
| if (!ShouldAllocateAsCommittedResource(mDevice, forceAllocateAsCommittedResource)) { |
| ResourceHeapAllocation subAllocation; |
| DAWN_TRY_ASSIGN(subAllocation, CreatePlacedResource(heapType, revisedDescriptor, |
| optimizedClearValue, initialUsage)); |
| if (subAllocation.GetInfo().mMethod != AllocationMethod::kInvalid) { |
| return std::move(subAllocation); |
| } |
| } |
| |
| // If sub-allocation fails, fall-back to direct allocation (committed resource). |
| ResourceHeapAllocation directAllocation; |
| DAWN_TRY_ASSIGN(directAllocation, CreateCommittedResource(heapType, revisedDescriptor, |
| optimizedClearValue, initialUsage)); |
| if (directAllocation.GetInfo().mMethod != AllocationMethod::kInvalid) { |
| return std::move(directAllocation); |
| } |
| |
| // If direct allocation fails, the system is probably out of memory. |
| return DAWN_OUT_OF_MEMORY_ERROR("Allocation failed"); |
| } |
| |
| void ResourceAllocatorManager::Tick(ExecutionSerial completedSerial) { |
| for (ResourceHeapAllocation& allocation : mAllocationsToDelete.IterateUpTo(completedSerial)) { |
| if (allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated) { |
| FreeMemory(allocation); |
| } |
| } |
| mAllocationsToDelete.ClearUpTo(completedSerial); |
| mHeapsToDelete.ClearUpTo(completedSerial); |
| } |
| |
| void ResourceAllocatorManager::DeallocateMemory(ResourceHeapAllocation& allocation) { |
| if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) { |
| return; |
| } |
| |
| mAllocationsToDelete.Enqueue(allocation, mDevice->GetPendingCommandSerial()); |
| |
| // Directly allocated ResourceHeapAllocations are created with a heap object that must be |
| // manually deleted upon deallocation. See ResourceAllocatorManager::CreateCommittedResource |
| // for more information. Acquire this heap as a unique_ptr and add it to the queue of heaps |
| // to delete. It cannot be deleted immediately because it may be in use by in-flight or |
| // pending commands. |
| if (allocation.GetInfo().mMethod == AllocationMethod::kDirect) { |
| mHeapsToDelete.Enqueue(std::unique_ptr<ResourceHeapBase>(allocation.GetResourceHeap()), |
| mDevice->GetPendingCommandSerial()); |
| } |
| |
| // Invalidate the allocation immediately in case one accidentally |
| // calls DeallocateMemory again using the same allocation. |
| allocation.Invalidate(); |
| |
| ASSERT(allocation.GetD3D12Resource() == nullptr); |
| } |
| |
| void ResourceAllocatorManager::FreeMemory(ResourceHeapAllocation& allocation) { |
| ASSERT(allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated); |
| |
| D3D12_HEAP_PROPERTIES heapProp; |
| allocation.GetD3D12Resource()->GetHeapProperties(&heapProp, nullptr); |
| |
| const D3D12_RESOURCE_DESC resourceDescriptor = allocation.GetD3D12Resource()->GetDesc(); |
| |
| const size_t resourceHeapKindIndex = GetResourceHeapKind( |
| resourceDescriptor.Dimension, heapProp.Type, resourceDescriptor.Flags, mResourceHeapTier); |
| |
| mSubAllocatedResourceAllocators[resourceHeapKindIndex]->Deallocate(allocation); |
| } |
| |
| ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreatePlacedResource( |
| D3D12_HEAP_TYPE heapType, |
| const D3D12_RESOURCE_DESC& requestedResourceDescriptor, |
| const D3D12_CLEAR_VALUE* optimizedClearValue, |
| D3D12_RESOURCE_STATES initialUsage) { |
| const ResourceHeapKind resourceHeapKind = |
| GetResourceHeapKind(requestedResourceDescriptor.Dimension, heapType, |
| requestedResourceDescriptor.Flags, mResourceHeapTier); |
| |
| D3D12_RESOURCE_DESC resourceDescriptor = requestedResourceDescriptor; |
| resourceDescriptor.Alignment = GetResourcePlacementAlignment( |
| resourceHeapKind, requestedResourceDescriptor.SampleDesc.Count, |
| requestedResourceDescriptor.Alignment); |
| |
| // TODO(bryan.bernhart): Figure out how to compute the alignment without calling this |
| // twice. |
| D3D12_RESOURCE_ALLOCATION_INFO resourceInfo = |
| mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor); |
| |
| // If the requested resource alignment was rejected, let D3D tell us what the |
| // required alignment is for this resource. |
| if (resourceDescriptor.Alignment != 0 && |
| resourceDescriptor.Alignment != resourceInfo.Alignment) { |
| resourceDescriptor.Alignment = 0; |
| resourceInfo = |
| mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor); |
| } |
| |
| // If d3d tells us the resource size is invalid, treat the error as OOM. |
| // Otherwise, creating the resource could cause a device loss (too large). |
| // This is because NextPowerOfTwo(UINT64_MAX) overflows and proceeds to |
| // incorrectly allocate a mismatched size. |
| if (resourceInfo.SizeInBytes == 0 || |
| resourceInfo.SizeInBytes == std::numeric_limits<uint64_t>::max()) { |
| return DAWN_OUT_OF_MEMORY_ERROR(absl::StrFormat( |
| "Resource allocation size (%u) was invalid.", resourceInfo.SizeInBytes)); |
| } |
| |
| BuddyMemoryAllocator* allocator = |
| mSubAllocatedResourceAllocators[static_cast<size_t>(resourceHeapKind)].get(); |
| |
| ResourceMemoryAllocation allocation; |
| DAWN_TRY_ASSIGN(allocation, |
| allocator->Allocate(resourceInfo.SizeInBytes, resourceInfo.Alignment)); |
| if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) { |
| return ResourceHeapAllocation{}; // invalid |
| } |
| |
| Heap* heap = ToBackend(allocation.GetResourceHeap()); |
| |
| // Before calling CreatePlacedResource, we must ensure the target heap is resident. |
| // CreatePlacedResource will fail if it is not. |
| DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(heap)); |
| |
| // With placed resources, a single heap can be reused. |
| // The resource placed at an offset is only reclaimed |
| // upon Tick or after the last command list using the resource has completed |
| // on the GPU. This means the same physical memory is not reused |
| // within the same command-list and does not require additional synchronization (aliasing |
| // barrier). |
| // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createplacedresource |
| ComPtr<ID3D12Resource> placedResource; |
| DAWN_TRY(CheckOutOfMemoryHRESULT( |
| mDevice->GetD3D12Device()->CreatePlacedResource( |
| heap->GetD3D12Heap(), allocation.GetOffset(), &resourceDescriptor, initialUsage, |
| optimizedClearValue, IID_PPV_ARGS(&placedResource)), |
| "ID3D12Device::CreatePlacedResource")); |
| |
| // After CreatePlacedResource has finished, the heap can be unlocked from residency. This |
| // will insert it into the residency LRU. |
| mDevice->GetResidencyManager()->UnlockAllocation(heap); |
| |
| return ResourceHeapAllocation{allocation.GetInfo(), allocation.GetOffset(), |
| std::move(placedResource), heap}; |
| } |
| |
| ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreateCommittedResource( |
| D3D12_HEAP_TYPE heapType, |
| const D3D12_RESOURCE_DESC& resourceDescriptor, |
| const D3D12_CLEAR_VALUE* optimizedClearValue, |
| D3D12_RESOURCE_STATES initialUsage) { |
| D3D12_HEAP_PROPERTIES heapProperties; |
| heapProperties.Type = heapType; |
| heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; |
| heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; |
| heapProperties.CreationNodeMask = 0; |
| heapProperties.VisibleNodeMask = 0; |
| |
| // If d3d tells us the resource size is invalid, treat the error as OOM. |
| // Otherwise, creating the resource could cause a device loss (too large). |
| // This is because NextPowerOfTwo(UINT64_MAX) overflows and proceeds to |
| // incorrectly allocate a mismatched size. |
| D3D12_RESOURCE_ALLOCATION_INFO resourceInfo = |
| mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor); |
| |
| if (resourceInfo.SizeInBytes == 0 || |
| resourceInfo.SizeInBytes == std::numeric_limits<uint64_t>::max()) { |
| return DAWN_OUT_OF_MEMORY_ERROR("Resource allocation size was invalid."); |
| } |
| |
| if (resourceInfo.SizeInBytes > kMaxHeapSize) { |
| return ResourceHeapAllocation{}; // Invalid |
| } |
| |
| // CreateCommittedResource will implicitly make the created resource resident. We must |
| // ensure enough free memory exists before allocating to avoid an out-of-memory error when |
| // overcommitted. |
| DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanAllocate( |
| resourceInfo.SizeInBytes, GetMemorySegment(mDevice, heapType))); |
| |
| // Note: Heap flags are inferred by the resource descriptor and do not need to be explicitly |
| // provided to CreateCommittedResource. |
| ComPtr<ID3D12Resource> committedResource; |
| DAWN_TRY(CheckOutOfMemoryHRESULT( |
| mDevice->GetD3D12Device()->CreateCommittedResource( |
| &heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDescriptor, initialUsage, |
| optimizedClearValue, IID_PPV_ARGS(&committedResource)), |
| "ID3D12Device::CreateCommittedResource")); |
| |
| // When using CreateCommittedResource, D3D12 creates an implicit heap that contains the |
| // resource allocation. Because Dawn's memory residency management occurs at the resource |
| // heap granularity, every directly allocated ResourceHeapAllocation also stores a Heap |
| // object. This object is created manually, and must be deleted manually upon deallocation |
| // of the committed resource. |
| Heap* heap = |
| new Heap(committedResource, GetMemorySegment(mDevice, heapType), resourceInfo.SizeInBytes); |
| |
| // Calling CreateCommittedResource implicitly calls MakeResident on the resource. We must |
| // track this to avoid calling MakeResident a second time. |
| mDevice->GetResidencyManager()->TrackResidentAllocation(heap); |
| |
| AllocationInfo info; |
| info.mMethod = AllocationMethod::kDirect; |
| |
| return ResourceHeapAllocation{info, |
| /*offset*/ 0, std::move(committedResource), heap}; |
| } |
| |
| void ResourceAllocatorManager::DestroyPool() { |
| for (auto& alloc : mPooledHeapAllocators) { |
| alloc->DestroyPool(); |
| } |
| } |
| |
| } // namespace dawn::native::d3d12 |