Add D3D12MemoryAllocator for resource allocations on DX12

v3.1.0
This commit is contained in:
2026-04-23 17:12:26 +02:00
parent 84b53bb9c8
commit 33617a702a
14 changed files with 14028 additions and 73 deletions
@@ -8,6 +8,7 @@
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h"
#include <ThirdParty/D3D12MemoryAllocator/D3D12MemAlloc.h>
void GPUBufferViewDX12::SetSRV(D3D12_SHADER_RESOURCE_VIEW_DESC& srvDesc)
{
@@ -115,34 +116,24 @@ bool GPUBufferDX12::OnInit()
resourceDesc.Flags |= D3D12XBOX_RESOURCE_FLAG_ALLOW_INDIRECT_BUFFER;
#endif
// Create allocation description
D3D12_HEAP_PROPERTIES heapProperties;
// Create resource
D3D12MA::ALLOCATION_DESC allocationDesc = {};
switch (_desc.Usage)
{
case GPUResourceUsage::StagingUpload:
case GPUResourceUsage::Staging:
heapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
allocationDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;
break;
case GPUResourceUsage::StagingReadback:
heapProperties.Type = D3D12_HEAP_TYPE_READBACK;
allocationDesc.HeapType = D3D12_HEAP_TYPE_READBACK;
break;
default:
heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
allocationDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
}
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProperties.CreationNodeMask = 1;
heapProperties.VisibleNodeMask = 1;
// Create resource
ID3D12Resource* resource;
#if PLATFORM_WINDOWS
D3D12_HEAP_FLAGS heapFlags = EnumHasAnyFlags(_desc.Flags, GPUBufferFlags::VertexBuffer | GPUBufferFlags::IndexBuffer) || _desc.InitData ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
#else
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
#endif
D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON;
VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, heapFlags, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource)));
HRESULT result = _device->Allocator->CreateResource(&allocationDesc, &resourceDesc, initialState, nullptr, &_allocation, IID_PPV_ARGS(&resource));
LOG_DIRECTX_RESULT_WITH_RETURN(result, true);
// Set state
initResource(resource, initialState, 1);
@@ -310,6 +310,11 @@ void GPUContextDX12::Reset()
_commandList->IASetVertexBuffers(GPU_MAX_VB_BINDED, 1, &dummyVBView);
ForceRebindDescriptors();
// Discard resources not created during rendering
for (auto* resource : _device->PendingResourceDiscards)
_commandList->DiscardResource(resource, nullptr);
_device->PendingResourceDiscards.Clear();
}
uint64 GPUContextDX12::Execute(bool waitForCompletion)
@@ -29,6 +29,10 @@
#include "Engine/Threading/Threading.h"
#include "CommandSignatureDX12.h"
// Inline into this module to leave it as a header-only
#include <ThirdParty/D3D12MemoryAllocator/D3D12MemAlloc.h>
#include <ThirdParty/D3D12MemoryAllocator/D3D12MemAlloc.cpp>
static bool CheckDX12Support(IDXGIAdapter* adapter)
{
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
@@ -43,6 +47,17 @@ static bool CheckDX12Support(IDXGIAdapter* adapter)
#endif
}
void* D3D12MA_Allocate(size_t Size, size_t Alignment, void* pPrivateData)
{
PROFILE_MEM(GraphicsCommands);
return Platform::Allocate(Size, Alignment);
}
void D3D12MA_Free(void* pMemory, void* pPrivateData)
{
Platform::Free(pMemory);
}
GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets)
: GPUResourceDX12<GPUVertexLayout>(device, StringView::Empty)
, InputElementsCount(elements.Count())
@@ -664,12 +679,13 @@ bool GPUDeviceDX12::Init()
// Create DirectX device
VALIDATE_DIRECTX_CALL(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device)));
HRESULT hr;
#if PLATFORM_WINDOWS
// Detect RenderDoc usage (UUID {A7AA6116-9C8D-4BBA-9083-B4D816B71B78})
IUnknown* unknown = nullptr;
const GUID uuidRenderDoc = { 0xa7aa6116, 0x9c8d, 0x4bba, { 0x90, 0x83, 0xb4, 0xd8, 0x16, 0xb7, 0x1b, 0x78 } };
HRESULT hr = _device->QueryInterface(uuidRenderDoc, (void**)&unknown);
hr = _device->QueryInterface(uuidRenderDoc, (void**)&unknown);
if (SUCCEEDED(hr) && unknown)
{
IsDebugToolAttached = true;
@@ -699,8 +715,8 @@ bool GPUDeviceDX12::Init()
// Debug Layer
#if GPU_ENABLE_DIAGNOSTICS
ComPtr<ID3D12InfoQueue> infoQueue;
HRESULT result = _device->QueryInterface(IID_PPV_ARGS(&infoQueue));
LOG_DIRECTX_RESULT(result);
hr = _device->QueryInterface(IID_PPV_ARGS(&infoQueue));
LOG_DIRECTX_RESULT(hr);
if (infoQueue)
{
D3D12_INFO_QUEUE_FILTER filter;
@@ -809,6 +825,16 @@ bool GPUDeviceDX12::Init()
#endif
// Setup resources
D3D12MA::ALLOCATION_CALLBACKS allocationCallbacks = {};
allocationCallbacks.pAllocate = &D3D12MA_Allocate;
allocationCallbacks.pFree = &D3D12MA_Free;
D3D12MA::ALLOCATOR_DESC allocatorDesc = {};
allocatorDesc.pDevice = _device;
allocatorDesc.pAdapter = adapter;
allocatorDesc.Flags = D3D12MA_RECOMMENDED_ALLOCATOR_FLAGS;
allocatorDesc.pAllocationCallbacks = &allocationCallbacks;
hr = D3D12MA::CreateAllocator(&allocatorDesc, &Allocator);
LOG_DIRECTX_RESULT_WITH_RETURN(hr, true);
_commandQueue = New<CommandQueueDX12>(this, D3D12_COMMAND_LIST_TYPE_DIRECT);
if (_commandQueue->Init())
return true;
@@ -1050,6 +1076,7 @@ void GPUDeviceDX12::Dispose()
SAFE_DELETE(DrawIndirectCommandSignature);
SAFE_DELETE(_mainContext);
SAFE_DELETE(_commandQueue);
SAFE_RELEASE(Allocator);
// Clear DirectX stuff
SAFE_DELETE(_adapter);
@@ -1140,16 +1167,17 @@ GPUConstantBuffer* GPUDeviceDX12::CreateConstantBuffer(uint32 size, const String
return New<GPUConstantBufferDX12>(this, size, name);
}
void GPUDeviceDX12::AddResourceToLateRelease(IGraphicsUnknown* resource, uint32 safeFrameCount)
void GPUDeviceDX12::AddResourceToLateRelease(IGraphicsUnknown* resource, D3D12MA::Allocation* allocation, uint32 safeFrameCount)
{
if (resource == nullptr)
return;
ScopeLock lock(_res2DisposeLock);
// Add to the list
DisposeResourceEntry entry;
entry.Resource = resource;
entry.Allocation = allocation;
entry.TargetFrame = Engine::FrameCount + safeFrameCount;
_res2Dispose.Add(entry);
}
@@ -1169,6 +1197,8 @@ void GPUDeviceDX12::updateRes2Dispose()
const DisposeResourceEntry& entry = _res2Dispose[i];
if (entry.TargetFrame <= currentFrame)
{
if (entry.Allocation)
entry.Allocation->Release();
auto refs = entry.Resource->Release();
if (refs != 0)
{
@@ -25,6 +25,16 @@ class GPUSwapChainDX12;
class CommandQueueDX12;
class CommandSignatureDX12;
// D3D12MemoryAllocator config
namespace D3D12MA
{
class Allocator;
class Allocation;
};
#if !BUILD_DEBUG
#define D3D12MA_ASSERT(cond)
#endif
/// <summary>
/// Implementation of Graphics Device for DirectX 12 rendering system
/// </summary>
@@ -38,6 +48,7 @@ private:
struct DisposeResourceEntry
{
IGraphicsUnknown* Resource;
D3D12MA::Allocation* Allocation;
uint64 TargetFrame;
};
@@ -71,6 +82,7 @@ public:
~GPUDeviceDX12();
public:
D3D12MA::Allocator* Allocator = nullptr;
UploadBufferDX12 UploadBuffer;
bool AllowTearing = false;
CommandSignatureDX12* DispatchIndirectCommandSignature = nullptr;
@@ -78,6 +90,7 @@ public:
CommandSignatureDX12* DrawIndirectCommandSignature = nullptr;
GPUBuffer* DummyVB = nullptr;
Array<QueryHeapDX12*, InlinedAllocation<8>> QueryHeaps;
Array<ID3D12Resource*> PendingResourceDiscards;
D3D12_CPU_DESCRIPTOR_HANDLE NullSRV(D3D12_SRV_DIMENSION dimension) const;
D3D12_CPU_DESCRIPTOR_HANDLE NullUAV() const;
@@ -154,7 +167,7 @@ public:
public:
// Add resource to late release service (will be released after 'safeFrameCount' frames)
void AddResourceToLateRelease(IGraphicsUnknown* resource, uint32 safeFrameCount = DX12_RESOURCE_DELETE_SAFE_FRAMES_COUNT);
void AddResourceToLateRelease(IGraphicsUnknown* resource, D3D12MA::Allocation* allocation = nullptr, uint32 safeFrameCount = DX12_RESOURCE_DELETE_SAFE_FRAMES_COUNT);
static FORCE_INLINE uint32 GetMaxMSAAQuality(uint32 sampleCount)
{
@@ -3,9 +3,11 @@
#if GRAPHICS_API_DIRECTX12
#include "GPUTextureDX12.h"
#include "GPUContextDX12.h"
#include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include <ThirdParty/D3D12MemoryAllocator/D3D12MemAlloc.h>
bool GPUTextureDX12::GetData(int32 arrayIndex, int32 mipMapIndex, TextureMipData& data, uint32 mipRowPitch)
{
@@ -48,8 +50,6 @@ bool GPUTextureDX12::GetData(int32 arrayIndex, int32 mipMapIndex, TextureMipData
bool GPUTextureDX12::OnInit()
{
ID3D12Resource* resource;
// Cache formats
const PixelFormat format = Format();
const PixelFormat typelessFormat = PixelFormatExtensions::MakeTypeless(format);
const DXGI_FORMAT dxgiFormat = RenderToolsDX::ToDxgiFormat(typelessFormat);
@@ -57,25 +57,17 @@ bool GPUTextureDX12::OnInit()
_dxgiFormatSRV = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindShaderResourceFormat(format, _sRGB));
_dxgiFormatRTV = _dxgiFormatSRV;
_dxgiFormatUAV = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindUnorderedAccessFormat(format));
// Cache properties
auto device = _device->GetDevice();
bool useSRV = IsShaderResource();
bool useDSV = IsDepthStencil();
bool useRTV = IsRenderTarget();
bool useUAV = IsUnorderedAccess();
int32 depthOrArraySize = IsVolume() ? Depth() : ArraySize();
D3D12MA::ALLOCATION_DESC allocationDesc = {};
if (IsStaging())
{
// Initialize as a buffer
const int32 totalSize = ComputeBufferTotalSize(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
D3D12_HEAP_PROPERTIES heapProperties;
heapProperties.Type = D3D12_HEAP_TYPE_READBACK;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProperties.CreationNodeMask = 1;
heapProperties.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC resourceDesc;
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
resourceDesc.Alignment = 0;
@@ -88,7 +80,8 @@ bool GPUTextureDX12::OnInit()
resourceDesc.SampleDesc.Quality = 0;
resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
auto result = device->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&resource));
allocationDesc.HeapType = D3D12_HEAP_TYPE_READBACK;
HRESULT result = _device->Allocator->CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, &_allocation, IID_PPV_ARGS(&resource));
LOG_DIRECTX_RESULT_WITH_RETURN(result, true);
initResource(resource, D3D12_RESOURCE_STATE_COPY_DEST, 1);
DX_SET_DEBUG_NAME(_resource, GetName());
@@ -96,9 +89,8 @@ bool GPUTextureDX12::OnInit()
return false;
}
D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON;
// Create texture description
D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON;
D3D12_RESOURCE_DESC resourceDesc;
resourceDesc.MipLevels = _desc.MipLevels;
resourceDesc.Format = dxgiFormat;
@@ -128,16 +120,10 @@ bool GPUTextureDX12::OnInit()
if (useUAV)
{
resourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
if (!useRTV && !useDSV)
initialState = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
}
// Create heap properties
D3D12_HEAP_PROPERTIES heapProperties;
heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProperties.CreationNodeMask = 1;
heapProperties.VisibleNodeMask = 1;
// Create clear value (used by render targets and depth stencil buffers)
D3D12_CLEAR_VALUE* clearValuePtr = nullptr;
D3D12_CLEAR_VALUE clearValue;
@@ -154,17 +140,12 @@ bool GPUTextureDX12::OnInit()
clearValue.DepthStencil.Stencil = 0;
clearValuePtr = &clearValue;
}
if (IsRegularTexture())
initialState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
// Create texture
#if PLATFORM_WINDOWS && 0
D3D12_HEAP_FLAGS heapFlags = useRTV || useDSV ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
#else
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
#endif
auto result = device->CreateCommittedResource(&heapProperties, heapFlags, &resourceDesc, initialState, clearValuePtr, IID_PPV_ARGS(&resource));
allocationDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
HRESULT result = _device->Allocator->CreateResource(&allocationDesc, &resourceDesc, initialState, clearValuePtr, &_allocation, IID_PPV_ARGS(&resource));
LOG_DIRECTX_RESULT_WITH_RETURN(result, true);
// Set state
@@ -186,6 +167,17 @@ bool GPUTextureDX12::OnInit()
initHandles();
}
// Discard resource contents (allows using non-zero heap)
if (isWrite)
{
_device->Locker.Lock();
if (IsInMainThread() && _device->IsRendering())
_device->GetMainContextDX12()->GetCommandList()->DiscardResource(_resource, nullptr);
else
_device->PendingResourceDiscards.Add(_resource);
_device->Locker.Unlock();
}
return false;
}
@@ -42,5 +42,7 @@ public class GraphicsDeviceDX12 : GraphicsDeviceBaseModule
options.DependencyFiles.Add(Path.Combine(options.DepsFolder, "WinPixEventRuntime.dll"));
options.DelayLoadLibraries.Add("WinPixEventRuntime.dll");
}
options.PrivateDependencies.Add("D3D12MemoryAllocator");
}
}
@@ -22,16 +22,13 @@ void ResourceOwnerDX12::initResource(const D3D12_RESOURCE_STATES initialState, c
void ResourceOwnerDX12::releaseResource(uint32 safeFrameCount)
{
if (_resource)
auto resource = _resource;
if (resource)
{
OnRelease(this);
auto resource = _resource;
_resource = nullptr;
_subresourcesCount = 0;
State.Release();
((GPUDeviceDX12*)GPUDevice::Instance)->AddResourceToLateRelease(resource, safeFrameCount);
((GPUDeviceDX12*)GPUDevice::Instance)->AddResourceToLateRelease(resource, _allocation, safeFrameCount);
_allocation = nullptr;
}
}
@@ -8,6 +8,10 @@
#if GRAPHICS_API_DIRECTX12
namespace D3D12MA
{
class Allocation;
}
class GPUResource;
class GPUContextDX12;
class GPUAsyncContextDX12;
@@ -59,19 +63,9 @@ class ResourceOwnerDX12
friend GPUAsyncContextDX12;
protected:
ID3D12Resource* _resource;
uint32 _subresourcesCount;
ResourceOwnerDX12()
: _resource(nullptr)
, _subresourcesCount(0)
{
}
~ResourceOwnerDX12()
{
}
D3D12MA::Allocation* _allocation = nullptr;
ID3D12Resource* _resource = nullptr;
uint32 _subresourcesCount = 0;
public:
@@ -18,7 +18,7 @@
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
#if PLATFORM_XBOX_SCARLETT
#if PLATFORM_XBOX_SCARLETT
#include <Scarlett/d3d12_xs.h>
#else
#include <XboxOne/d3d12_x.h>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="D3D12MA::Vector&lt;*&gt;">
<DisplayString>{{ Count={m_Count} }}</DisplayString>
<Expand>
<Item Name="[Count]">m_Count</Item>
<Item Name="[Capacity]">m_Capacity</Item>
<ArrayItems>
<Size>m_Count</Size>
<ValuePointer>m_pArray</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="D3D12MA::List&lt;*&gt;">
<DisplayString>{{ Count={m_Count} }}</DisplayString>
<Expand>
<Item Name="[Count]">m_Count</Item>
<LinkedListItems>
<Size>m_Count</Size>
<HeadPointer>m_pFront</HeadPointer>
<NextPointer>pNext</NextPointer>
<ValueNode>Value</ValueNode>
</LinkedListItems>
</Expand>
</Type>
<!--
Due to custom way of accesing next items in
D3D12MA::IntrusiveLinkedList via methods in provided type traits,
every specialization must be manually added with
custom <NextPointer> field describing proper way of iterating the list.
-->
<Type Name="D3D12MA::IntrusiveLinkedList&lt;D3D12MA::CommittedAllocationListItemTraits&gt;">
<DisplayString>{{ Count={m_Count} }}</DisplayString>
<Expand>
<Item Name="[Count]">m_Count</Item>
<LinkedListItems>
<Size>m_Count</Size>
<HeadPointer>m_Front</HeadPointer>
<NextPointer>m_Committed.next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Type>
<Type Name="D3D12MA::IntrusiveLinkedList&lt;D3D12MA::PoolListItemTraits&gt;">
<DisplayString>{{ Count={m_Count} }}</DisplayString>
<Expand>
<Item Name="[Count]">m_Count</Item>
<LinkedListItems>
<Size>m_Count</Size>
<HeadPointer>m_Front</HeadPointer>
<NextPointer>m_NextPool</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Type>
</AutoVisualizer>
@@ -0,0 +1,21 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using Flax.Build;
/// <summary>
/// https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator
/// </summary>
public class D3D12MemoryAllocator : HeaderOnlyModule
{
/// <inheritdoc />
public override void Init()
{
base.Init();
LicenseType = LicenseTypes.MIT;
LicenseFilePath = "LICENSE.txt";
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";
}
}
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2019-2026 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.