Add big feature of budget management - struct Budget, Allocator::GetBudget, ALLOCATION_FLAG_WITHIN_BUDGET, ALLOCATOR_DESC::pAdapter.

Added macro D3D12MA_DXGI_1_4, D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED.
Some bug fixes and improvements.
This commit is contained in:
Adam Sawicki 2019-11-20 14:07:21 +01:00
parent 87528d084a
commit 0db8cd41e4
4 changed files with 448 additions and 32 deletions

View File

@ -22,6 +22,13 @@
#include "D3D12MemAlloc.h" #include "D3D12MemAlloc.h"
#ifndef D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED
#include <dxgi.h>
#if D3D12MA_DXGI_1_4
#include <dxgi1_4.h>
#endif
#endif
#include <mutex> #include <mutex>
#include <atomic> #include <atomic>
#include <algorithm> #include <algorithm>
@ -165,6 +172,8 @@ static void SetupAllocationCallbacks(ALLOCATION_CALLBACKS& outAllocs, const ALLO
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Private globals - basic facilities // Private globals - basic facilities
#define SAFE_RELEASE(ptr) do { if(ptr) { (ptr)->Release(); (ptr) = NULL; } } while(false)
#define D3D12MA_VALIDATE(cond) do { if(!(cond)) { \ #define D3D12MA_VALIDATE(cond) do { if(!(cond)) { \
D3D12MA_ASSERT(0 && "Validation failed: " #cond); \ D3D12MA_ASSERT(0 && "Validation failed: " #cond); \
return false; \ return false; \
@ -225,6 +234,10 @@ If providing your own implementation, you need to implement a subset of std::ato
#define D3D12MA_ATOMIC_UINT32 std::atomic<UINT> #define D3D12MA_ATOMIC_UINT32 std::atomic<UINT>
#endif #endif
#ifndef D3D12MA_ATOMIC_UINT64
#define D3D12MA_ATOMIC_UINT64 std::atomic<UINT64>
#endif
// Aligns given value up to nearest multiply of align value. For example: AlignUp(11, 8) = 16. // Aligns given value up to nearest multiply of align value. For example: AlignUp(11, 8) = 16.
// Use types like UINT, uint64_t as T. // Use types like UINT, uint64_t as T.
template <typename T> template <typename T>
@ -2073,11 +2086,53 @@ private:
static const UINT DEFAULT_POOL_MAX_COUNT = 9; static const UINT DEFAULT_POOL_MAX_COUNT = 9;
struct CurrentBudgetData
{
D3D12MA_ATOMIC_UINT64 m_BlockBytes[HEAP_TYPE_COUNT];
D3D12MA_ATOMIC_UINT64 m_AllocationBytes[HEAP_TYPE_COUNT];
D3D12MA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch;
D3D12MA_RW_MUTEX m_BudgetMutex;
UINT64 m_D3D12UsageLocal, m_D3D12UsageNonLocal;
UINT64 m_D3D12BudgetLocal, m_D3D12BudgetNonLocal;
UINT64 m_BlockBytesAtBudgetFetch[HEAP_TYPE_COUNT];
CurrentBudgetData()
{
for(UINT i = 0; i < HEAP_TYPE_COUNT; ++i)
{
m_BlockBytes[i] = 0;
m_AllocationBytes[i] = 0;
m_BlockBytesAtBudgetFetch[i] = 0;
}
m_D3D12UsageLocal = 0;
m_D3D12UsageNonLocal = 0;
m_D3D12BudgetLocal = 0;
m_D3D12BudgetNonLocal = 0;
m_OperationsSinceBudgetFetch = 0;
}
void AddAllocation(UINT heapTypeIndex, UINT64 allocationSize)
{
m_AllocationBytes[heapTypeIndex] += allocationSize;
++m_OperationsSinceBudgetFetch;
}
void RemoveAllocation(UINT heapTypeIndex, UINT64 allocationSize)
{
m_AllocationBytes[heapTypeIndex] -= allocationSize;
++m_OperationsSinceBudgetFetch;
}
};
class AllocatorPimpl class AllocatorPimpl
{ {
public: public:
CurrentBudgetData m_Budget;
AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc); AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc);
HRESULT Init(); HRESULT Init(const ALLOCATOR_DESC& desc);
~AllocatorPimpl(); ~AllocatorPimpl();
ID3D12Device* GetDevice() const { return m_Device; } ID3D12Device* GetDevice() const { return m_Device; }
@ -2118,6 +2173,9 @@ public:
void CalculateStats(Stats& outStats); void CalculateStats(Stats& outStats);
void GetBudget(Budget* outGpuBudget, Budget* outCpuBudget);
void GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE heapType);
void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap); void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap);
void FreeStatsString(WCHAR* pStatsString); void FreeStatsString(WCHAR* pStatsString);
@ -2133,11 +2191,15 @@ private:
const bool m_UseMutex; const bool m_UseMutex;
const bool m_AlwaysCommitted; const bool m_AlwaysCommitted;
ID3D12Device* m_Device; ID3D12Device* m_Device; // AddRef
IDXGIAdapter* m_Adapter; // AddRef
#if D3D12MA_DXGI_1_4
IDXGIAdapter3* m_Adapter3; // AddRef, optional
#endif
UINT64 m_PreferredBlockSize; UINT64 m_PreferredBlockSize;
ALLOCATION_CALLBACKS m_AllocationCallbacks; ALLOCATION_CALLBACKS m_AllocationCallbacks;
D3D12MA_ATOMIC_UINT32 m_CurrentFrameIndex; D3D12MA_ATOMIC_UINT32 m_CurrentFrameIndex;
DXGI_ADAPTER_DESC m_AdapterDesc;
D3D12_FEATURE_DATA_D3D12_OPTIONS m_D3D12Options; D3D12_FEATURE_DATA_D3D12_OPTIONS m_D3D12Options;
typedef Vector<Allocation*> AllocationVectorType; typedef Vector<Allocation*> AllocationVectorType;
@ -2193,6 +2255,11 @@ private:
void RegisterCommittedAllocation(Allocation* alloc, D3D12_HEAP_TYPE heapType); void RegisterCommittedAllocation(Allocation* alloc, D3D12_HEAP_TYPE heapType);
// Unregisters Allocation object from m_pCommittedAllocations. // Unregisters Allocation object from m_pCommittedAllocations.
void UnregisterCommittedAllocation(Allocation* alloc, D3D12_HEAP_TYPE heapType); void UnregisterCommittedAllocation(Allocation* alloc, D3D12_HEAP_TYPE heapType);
HRESULT UpdateD3D12Budget();
// Writes object { } with data of given budget.
static void WriteBudgetToJson(JsonWriter& json, const Budget& budget);
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -2761,18 +2828,21 @@ NormalBlock::NormalBlock(
UINT64 size, UINT64 size,
UINT id) : UINT id) :
MemoryBlock(allocator, heapType, heapFlags, size, id), MemoryBlock(allocator, heapType, heapFlags, size, id),
m_pMetadata(NULL),
m_BlockVector(blockVector) m_BlockVector(blockVector)
{ {
} }
NormalBlock::~NormalBlock() NormalBlock::~NormalBlock()
{ {
// THIS IS THE MOST IMPORTANT ASSERT IN THE ENTIRE LIBRARY! if(m_pMetadata != NULL)
// Hitting it means you have some memory leak - unreleased Allocation objects. {
D3D12MA_ASSERT(m_pMetadata->IsEmpty() && "Some allocations were not freed before destruction of this memory block!"); // THIS IS THE MOST IMPORTANT ASSERT IN THE ENTIRE LIBRARY!
// Hitting it means you have some memory leak - unreleased Allocation objects.
D3D12MA_ASSERT(m_pMetadata->IsEmpty() && "Some allocations were not freed before destruction of this memory block!");
D3D12MA_DELETE(m_Allocator->GetAllocs(), m_pMetadata); D3D12MA_DELETE(m_Allocator->GetAllocs(), m_pMetadata);
m_pMetadata = NULL; }
} }
HRESULT NormalBlock::Init() HRESULT NormalBlock::Init()
@ -2819,6 +2889,7 @@ MemoryBlock::~MemoryBlock()
{ {
if(m_Heap) if(m_Heap)
{ {
m_Allocator->m_Budget.m_BlockBytes[HeapTypeToIndex(m_HeapType)] -= m_Size;
m_Heap->Release(); m_Heap->Release();
} }
} }
@ -2833,7 +2904,12 @@ HRESULT MemoryBlock::Init()
heapDesc.Alignment = HeapFlagsToAlignment(m_HeapFlags); heapDesc.Alignment = HeapFlagsToAlignment(m_HeapFlags);
heapDesc.Flags = m_HeapFlags; heapDesc.Flags = m_HeapFlags;
return m_Allocator->GetDevice()->CreateHeap(&heapDesc, __uuidof(*m_Heap), (void**)&m_Heap); HRESULT hr = m_Allocator->GetDevice()->CreateHeap(&heapDesc, __uuidof(*m_Heap), (void**)&m_Heap);
if(SUCCEEDED(hr))
{
m_Allocator->m_Budget.m_BlockBytes[HeapTypeToIndex(m_HeapType)] += m_Size;
}
return hr;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -2932,9 +3008,17 @@ HRESULT BlockVector::AllocatePage(
return E_OUTOFMEMORY; return E_OUTOFMEMORY;
} }
UINT64 freeMemory;
{
Budget budget = {};
m_hAllocator->GetBudgetForHeapType(budget, m_HeapType);
freeMemory = (budget.Usage < budget.Budget) ? (budget.Budget - budget.Usage) : 0;
}
const bool canCreateNewBlock = const bool canCreateNewBlock =
((createInfo.Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) == 0) && ((createInfo.Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) == 0) &&
(m_Blocks.size() < m_MaxBlockCount); (m_Blocks.size() < m_MaxBlockCount) &&
freeMemory >= size;
if(canCreateNewBlock) if(canCreateNewBlock)
{ {
@ -2990,7 +3074,8 @@ HRESULT BlockVector::AllocatePage(
} }
size_t newBlockIndex = 0; size_t newBlockIndex = 0;
HRESULT hr = CreateBlock(newBlockSize, &newBlockIndex); HRESULT hr = newBlockSize <= freeMemory ?
CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY;
// Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize. // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize.
if(!m_ExplicitBlockSize) if(!m_ExplicitBlockSize)
{ {
@ -3001,7 +3086,8 @@ HRESULT BlockVector::AllocatePage(
{ {
newBlockSize = smallerNewBlockSize; newBlockSize = smallerNewBlockSize;
++newBlockSizeShift; ++newBlockSizeShift;
hr = CreateBlock(newBlockSize, &newBlockIndex); hr = newBlockSize <= freeMemory ?
CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY;
} }
else else
{ {
@ -3041,6 +3127,13 @@ void BlockVector::Free(Allocation* hAllocation)
{ {
NormalBlock* pBlockToDelete = NULL; NormalBlock* pBlockToDelete = NULL;
bool budgetExceeded = false;
{
Budget budget = {};
m_hAllocator->GetBudgetForHeapType(budget, m_HeapType);
budgetExceeded = budget.Usage >= budget.Budget;
}
// Scope for lock. // Scope for lock.
{ {
MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex()); MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex());
@ -3050,11 +3143,12 @@ void BlockVector::Free(Allocation* hAllocation)
pBlock->m_pMetadata->Free(hAllocation); pBlock->m_pMetadata->Free(hAllocation);
D3D12MA_HEAVY_ASSERT(pBlock->Validate()); D3D12MA_HEAVY_ASSERT(pBlock->Validate());
const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount;
// pBlock became empty after this deallocation. // pBlock became empty after this deallocation.
if(pBlock->m_pMetadata->IsEmpty()) if(pBlock->m_pMetadata->IsEmpty())
{ {
// Already has empty Allocation. We don't want to have two, so delete this one. // Already has empty Allocation. We don't want to have two, so delete this one.
if(m_HasEmptyBlock && m_Blocks.size() > m_MinBlockCount) if((m_HasEmptyBlock || budgetExceeded) && canDeleteBlock)
{ {
pBlockToDelete = pBlock; pBlockToDelete = pBlock;
Remove(pBlock); Remove(pBlock);
@ -3067,7 +3161,7 @@ void BlockVector::Free(Allocation* hAllocation)
} }
// pBlock didn't become empty, but we have another empty block - find and free that one. // pBlock didn't become empty, but we have another empty block - find and free that one.
// (This is optional, heuristics.) // (This is optional, heuristics.)
else if(m_HasEmptyBlock) else if(m_HasEmptyBlock && canDeleteBlock)
{ {
NormalBlock* pLastBlock = m_Blocks.back(); NormalBlock* pLastBlock = m_Blocks.back();
if(pLastBlock->m_pMetadata->IsEmpty() && m_Blocks.size() > m_MinBlockCount) if(pLastBlock->m_pMetadata->IsEmpty() && m_Blocks.size() > m_MinBlockCount)
@ -3157,6 +3251,7 @@ HRESULT BlockVector::AllocateFromBlock(
alignment, alignment,
pBlock); pBlock);
D3D12MA_HEAVY_ASSERT(pBlock->Validate()); D3D12MA_HEAVY_ASSERT(pBlock->Validate());
m_hAllocator->m_Budget.AddAllocation(HeapTypeToIndex(m_HeapType), size);
return S_OK; return S_OK;
} }
return E_OUTOFMEMORY; return E_OUTOFMEMORY;
@ -3234,6 +3329,10 @@ AllocatorPimpl::AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks,
m_UseMutex((desc.Flags & ALLOCATOR_FLAG_SINGLETHREADED) == 0), m_UseMutex((desc.Flags & ALLOCATOR_FLAG_SINGLETHREADED) == 0),
m_AlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0), m_AlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0),
m_Device(desc.pDevice), m_Device(desc.pDevice),
m_Adapter(desc.pAdapter),
#if D3D12MA_DXGI_1_4
m_Adapter3(NULL),
#endif
m_PreferredBlockSize(desc.PreferredBlockSize != 0 ? desc.PreferredBlockSize : D3D12MA_DEFAULT_BLOCK_SIZE), m_PreferredBlockSize(desc.PreferredBlockSize != 0 ? desc.PreferredBlockSize : D3D12MA_DEFAULT_BLOCK_SIZE),
m_AllocationCallbacks(allocationCallbacks), m_AllocationCallbacks(allocationCallbacks),
m_CurrentFrameIndex(0) m_CurrentFrameIndex(0)
@ -3249,11 +3348,24 @@ AllocatorPimpl::AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks,
{ {
m_pCommittedAllocations[heapTypeIndex] = D3D12MA_NEW(GetAllocs(), AllocationVectorType)(GetAllocs()); m_pCommittedAllocations[heapTypeIndex] = D3D12MA_NEW(GetAllocs(), AllocationVectorType)(GetAllocs());
} }
m_Device->AddRef();
m_Adapter->AddRef();
} }
HRESULT AllocatorPimpl::Init() HRESULT AllocatorPimpl::Init(const ALLOCATOR_DESC& desc)
{ {
HRESULT hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &m_D3D12Options, sizeof(m_D3D12Options)); #if D3D12MA_DXGI_1_4
desc.pAdapter->QueryInterface<IDXGIAdapter3>(&m_Adapter3);
#endif
HRESULT hr = m_Adapter->GetDesc(&m_AdapterDesc);
if(FAILED(hr))
{
return hr;
}
hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &m_D3D12Options, sizeof(m_D3D12Options));
if(FAILED(hr)) if(FAILED(hr))
{ {
return hr; return hr;
@ -3277,11 +3389,24 @@ HRESULT AllocatorPimpl::Init()
// No need to call m_pBlockVectors[i]->CreateMinBlocks here, becase minBlockCount is 0. // No need to call m_pBlockVectors[i]->CreateMinBlocks here, becase minBlockCount is 0.
} }
#if D3D12MA_DXGI_1_4
if(m_Adapter3)
{
UpdateD3D12Budget();
}
#endif
return S_OK; return S_OK;
} }
AllocatorPimpl::~AllocatorPimpl() AllocatorPimpl::~AllocatorPimpl()
{ {
#if D3D12MA_DXGI_1_4
SAFE_RELEASE(m_Adapter3);
#endif
SAFE_RELEASE(m_Adapter);
SAFE_RELEASE(m_Device);
for(UINT i = DEFAULT_POOL_MAX_COUNT; i--; ) for(UINT i = DEFAULT_POOL_MAX_COUNT; i--; )
{ {
D3D12MA_DELETE(GetAllocs(), m_BlockVectors[i]); D3D12MA_DELETE(GetAllocs(), m_BlockVectors[i]);
@ -3385,8 +3510,7 @@ HRESULT AllocatorPimpl::CreateResource(
} }
else else
{ {
(*ppAllocation)->Release(); SAFE_RELEASE(*ppAllocation);
*ppAllocation = NULL;
return hr; return hr;
} }
} }
@ -3479,6 +3603,16 @@ HRESULT AllocatorPimpl::AllocateCommittedResource(
return E_OUTOFMEMORY; return E_OUTOFMEMORY;
} }
if((pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0)
{
Budget budget = {};
GetBudgetForHeapType(budget, pAllocDesc->HeapType);
if(budget.Usage + resAllocInfo.SizeInBytes > budget.Budget)
{
return E_OUTOFMEMORY;
}
}
D3D12_HEAP_PROPERTIES heapProps = {}; D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = pAllocDesc->HeapType; heapProps.Type = pAllocDesc->HeapType;
ID3D12Resource* res = NULL; ID3D12Resource* res = NULL;
@ -3499,6 +3633,10 @@ HRESULT AllocatorPimpl::AllocateCommittedResource(
} }
RegisterCommittedAllocation(*ppAllocation, pAllocDesc->HeapType); RegisterCommittedAllocation(*ppAllocation, pAllocDesc->HeapType);
const UINT heapTypeIndex = HeapTypeToIndex(pAllocDesc->HeapType);
m_Budget.AddAllocation(heapTypeIndex, resAllocInfo.SizeInBytes);
m_Budget.m_BlockBytes[heapTypeIndex] += resAllocInfo.SizeInBytes;
} }
return hr; return hr;
} }
@ -3516,6 +3654,16 @@ HRESULT AllocatorPimpl::AllocateHeap(
return E_OUTOFMEMORY; return E_OUTOFMEMORY;
} }
if((pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0)
{
Budget budget = {};
GetBudgetForHeapType(budget, pAllocDesc->HeapType);
if(budget.Usage + allocInfo.SizeInBytes > budget.Budget)
{
return E_OUTOFMEMORY;
}
}
D3D12_HEAP_DESC heapDesc = {}; D3D12_HEAP_DESC heapDesc = {};
heapDesc.SizeInBytes = allocInfo.SizeInBytes; heapDesc.SizeInBytes = allocInfo.SizeInBytes;
heapDesc.Properties.Type = pAllocDesc->HeapType; heapDesc.Properties.Type = pAllocDesc->HeapType;
@ -3529,6 +3677,10 @@ HRESULT AllocatorPimpl::AllocateHeap(
(*ppAllocation) = D3D12MA_NEW(m_AllocationCallbacks, Allocation)(); (*ppAllocation) = D3D12MA_NEW(m_AllocationCallbacks, Allocation)();
(*ppAllocation)->InitHeap(this, allocInfo.SizeInBytes, pAllocDesc->HeapType, heap); (*ppAllocation)->InitHeap(this, allocInfo.SizeInBytes, pAllocDesc->HeapType, heap);
RegisterCommittedAllocation(*ppAllocation, pAllocDesc->HeapType); RegisterCommittedAllocation(*ppAllocation, pAllocDesc->HeapType);
const UINT heapTypeIndex = HeapTypeToIndex(pAllocDesc->HeapType);
m_Budget.AddAllocation(heapTypeIndex, allocInfo.SizeInBytes);
m_Budget.m_BlockBytes[heapTypeIndex] += allocInfo.SizeInBytes;
} }
return hr; return hr;
} }
@ -3676,6 +3828,11 @@ void AllocatorPimpl::FreeCommittedMemory(Allocation* allocation)
{ {
D3D12MA_ASSERT(allocation && allocation->m_Type == Allocation::TYPE_COMMITTED); D3D12MA_ASSERT(allocation && allocation->m_Type == Allocation::TYPE_COMMITTED);
UnregisterCommittedAllocation(allocation, allocation->m_Committed.heapType); UnregisterCommittedAllocation(allocation, allocation->m_Committed.heapType);
const UINT64 allocationSize = allocation->GetSize();
const UINT heapTypeIndex = HeapTypeToIndex(allocation->m_Committed.heapType);
m_Budget.RemoveAllocation(heapTypeIndex, allocationSize);
m_Budget.m_BlockBytes[heapTypeIndex] -= allocationSize;
} }
void AllocatorPimpl::FreePlacedMemory(Allocation* allocation) void AllocatorPimpl::FreePlacedMemory(Allocation* allocation)
@ -3686,6 +3843,7 @@ void AllocatorPimpl::FreePlacedMemory(Allocation* allocation)
D3D12MA_ASSERT(block); D3D12MA_ASSERT(block);
BlockVector* const blockVector = block->GetBlockVector(); BlockVector* const blockVector = block->GetBlockVector();
D3D12MA_ASSERT(blockVector); D3D12MA_ASSERT(blockVector);
m_Budget.RemoveAllocation(HeapTypeToIndex(block->GetHeapType()), allocation->GetSize());
blockVector->Free(allocation); blockVector->Free(allocation);
} }
@ -3693,12 +3851,24 @@ void AllocatorPimpl::FreeHeapMemory(Allocation* allocation)
{ {
D3D12MA_ASSERT(allocation && allocation->m_Type == Allocation::TYPE_HEAP); D3D12MA_ASSERT(allocation && allocation->m_Type == Allocation::TYPE_HEAP);
UnregisterCommittedAllocation(allocation, allocation->m_Heap.heapType); UnregisterCommittedAllocation(allocation, allocation->m_Heap.heapType);
allocation->m_Heap.heap->Release(); SAFE_RELEASE(allocation->m_Heap.heap);
const UINT heapTypeIndex = HeapTypeToIndex(allocation->m_Heap.heapType);
const UINT64 allocationSize = allocation->GetSize();
m_Budget.m_BlockBytes[heapTypeIndex] -= allocationSize;
m_Budget.RemoveAllocation(heapTypeIndex, allocationSize);
} }
void AllocatorPimpl::SetCurrentFrameIndex(UINT frameIndex) void AllocatorPimpl::SetCurrentFrameIndex(UINT frameIndex)
{ {
m_CurrentFrameIndex.store(frameIndex); m_CurrentFrameIndex.store(frameIndex);
#if D3D12MA_DXGI_1_4
if(m_Adapter3)
{
UpdateD3D12Budget();
}
#endif
} }
void AllocatorPimpl::CalculateStats(Stats& outStats) void AllocatorPimpl::CalculateStats(Stats& outStats)
@ -3739,7 +3909,7 @@ void AllocatorPimpl::CalculateStats(Stats& outStats)
statInfo.UnusedBytes = 0; statInfo.UnusedBytes = 0;
statInfo.AllocationSizeMin = size; statInfo.AllocationSizeMin = size;
statInfo.AllocationSizeMax = size; statInfo.AllocationSizeMax = size;
statInfo.UnusedRangeSizeMin = 0; statInfo.UnusedRangeSizeMin = UINT64_MAX;
statInfo.UnusedRangeSizeMax = 0; statInfo.UnusedRangeSizeMax = 0;
AddStatInfo(outStats.Total, statInfo); AddStatInfo(outStats.Total, statInfo);
AddStatInfo(heapStatInfo, statInfo); AddStatInfo(heapStatInfo, statInfo);
@ -3752,6 +3922,94 @@ void AllocatorPimpl::CalculateStats(Stats& outStats)
PostProcessStatInfo(outStats.HeapType[i]); PostProcessStatInfo(outStats.HeapType[i]);
} }
void AllocatorPimpl::GetBudget(Budget* outGpuBudget, Budget* outCpuBudget)
{
if(outGpuBudget)
{
// Taking DEFAULT.
outGpuBudget->BlockBytes = m_Budget.m_BlockBytes[0];
outGpuBudget->AllocationBytes = m_Budget.m_AllocationBytes[0];
}
if(outCpuBudget)
{
// Taking UPLOAD + READBACK.
outCpuBudget->BlockBytes = m_Budget.m_BlockBytes[1] + m_Budget.m_BlockBytes[2];
outCpuBudget->AllocationBytes = m_Budget.m_AllocationBytes[1] + m_Budget.m_AllocationBytes[2];
}
#if D3D12MA_DXGI_1_4
if(m_Adapter3)
{
if(m_Budget.m_OperationsSinceBudgetFetch < 30)
{
MutexLockRead lockRead(m_Budget.m_BudgetMutex, m_UseMutex);
if(outGpuBudget)
{
if(m_Budget.m_D3D12UsageLocal + outGpuBudget->BlockBytes > m_Budget.m_BlockBytesAtBudgetFetch[0])
{
outGpuBudget->Usage = m_Budget.m_D3D12UsageLocal +
outGpuBudget->BlockBytes - m_Budget.m_BlockBytesAtBudgetFetch[0];
}
else
{
outGpuBudget->Usage = 0;
}
outGpuBudget->Budget = m_Budget.m_D3D12BudgetLocal;
}
if(outCpuBudget)
{
if(m_Budget.m_D3D12UsageNonLocal + outCpuBudget->BlockBytes > m_Budget.m_BlockBytesAtBudgetFetch[1] + m_Budget.m_BlockBytesAtBudgetFetch[2])
{
outCpuBudget->Usage = m_Budget.m_D3D12UsageNonLocal +
outCpuBudget->BlockBytes - (m_Budget.m_BlockBytesAtBudgetFetch[1] + m_Budget.m_BlockBytesAtBudgetFetch[2]);
}
else
{
outCpuBudget->Usage = 0;
}
outCpuBudget->Budget = m_Budget.m_D3D12BudgetNonLocal;
}
}
else
{
UpdateD3D12Budget(); // Outside of mutex lock
GetBudget(outGpuBudget, outCpuBudget); // Recursion
}
}
else
#endif
{
if(outGpuBudget)
{
const UINT64 gpuMemorySize = m_AdapterDesc.DedicatedVideoMemory + m_AdapterDesc.DedicatedSystemMemory; // TODO: Is this right?
outGpuBudget->Usage = outGpuBudget->BlockBytes;
outGpuBudget->Budget = gpuMemorySize * 8 / 10; // 80% heuristics.
}
if(outCpuBudget)
{
const UINT64 cpuMemorySize = m_AdapterDesc.SharedSystemMemory; // TODO: Is this right?
outCpuBudget->Usage = outCpuBudget->BlockBytes;
outCpuBudget->Budget = cpuMemorySize * 8 / 10; // 80% heuristics.
}
}
}
void AllocatorPimpl::GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE heapType)
{
switch(heapType)
{
case D3D12_HEAP_TYPE_DEFAULT:
GetBudget(&outBudget, NULL);
break;
case D3D12_HEAP_TYPE_UPLOAD:
case D3D12_HEAP_TYPE_READBACK:
GetBudget(NULL, &outBudget);
break;
default: D3D12MA_ASSERT(0);
}
}
static void AddStatInfoToJson(JsonWriter& json, const StatInfo& statInfo) static void AddStatInfoToJson(JsonWriter& json, const StatInfo& statInfo)
{ {
json.BeginObject(); json.BeginObject();
@ -3795,10 +4053,14 @@ void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap)
{ {
JsonWriter json(GetAllocs(), sb); JsonWriter json(GetAllocs(), sb);
Budget gpuBudget = {}, cpuBudget = {};
GetBudget(&gpuBudget, &cpuBudget);
Stats stats; Stats stats;
CalculateStats(stats); CalculateStats(stats);
json.BeginObject(); json.BeginObject();
json.WriteString(L"Total"); json.WriteString(L"Total");
AddStatInfoToJson(json, stats.Total); AddStatInfoToJson(json, stats.Total);
for (size_t heapType = 0; heapType < HEAP_TYPE_COUNT; ++heapType) for (size_t heapType = 0; heapType < HEAP_TYPE_COUNT; ++heapType)
@ -3807,6 +4069,16 @@ void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap)
AddStatInfoToJson(json, stats.HeapType[heapType]); AddStatInfoToJson(json, stats.HeapType[heapType]);
} }
json.WriteString(L"Budget");
json.BeginObject();
{
json.WriteString(L"GPU");
WriteBudgetToJson(json, gpuBudget);
json.WriteString(L"CPU");
WriteBudgetToJson(json, cpuBudget);
}
json.EndObject();
if (DetailedMap) if (DetailedMap)
{ {
json.WriteString(L"DetailedMap"); json.WriteString(L"DetailedMap");
@ -3905,6 +4177,60 @@ void AllocatorPimpl::FreeStatsString(WCHAR* pStatsString)
Free(GetAllocs(), pStatsString); Free(GetAllocs(), pStatsString);
} }
HRESULT AllocatorPimpl::UpdateD3D12Budget()
{
#if D3D12MA_DXGI_1_4
D3D12MA_ASSERT(m_Adapter3);
DXGI_QUERY_VIDEO_MEMORY_INFO infoLocal = {};
DXGI_QUERY_VIDEO_MEMORY_INFO infoNonLocal = {};
HRESULT hrLocal = m_Adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &infoLocal);
HRESULT hrNonLocal = m_Adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &infoNonLocal);
{
MutexLockWrite lockWrite(m_Budget.m_BudgetMutex, m_UseMutex);
if(SUCCEEDED(hrLocal))
{
m_Budget.m_D3D12UsageLocal = infoLocal.CurrentUsage;
m_Budget.m_D3D12BudgetLocal = infoLocal.Budget;
}
if(SUCCEEDED(hrNonLocal))
{
m_Budget.m_D3D12UsageNonLocal = infoNonLocal.CurrentUsage;
m_Budget.m_D3D12BudgetNonLocal = infoNonLocal.Budget;
}
for(UINT i = 0; i < HEAP_TYPE_COUNT; ++i)
{
m_Budget.m_BlockBytesAtBudgetFetch[i] = m_Budget.m_BlockBytes[i].load();
}
m_Budget.m_OperationsSinceBudgetFetch = 0;
}
return FAILED(hrLocal) ? hrLocal : hrNonLocal;
#else
return S_OK;
#endif
}
void AllocatorPimpl::WriteBudgetToJson(JsonWriter& json, const Budget& budget)
{
json.BeginObject();
{
json.WriteString(L"BlockBytes");
json.WriteNumber(budget.BlockBytes);
json.WriteString(L"AllocationBytes");
json.WriteNumber(budget.AllocationBytes);
json.WriteString(L"Usage");
json.WriteNumber(budget.Usage);
json.WriteString(L"Budget");
json.WriteNumber(budget.Budget);
}
json.EndObject();
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Public class Allocation implementation // Public class Allocation implementation
@ -3917,10 +4243,7 @@ void Allocation::Release()
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
if(m_Resource) SAFE_RELEASE(m_Resource);
{
m_Resource->Release();
}
switch(m_Type) switch(m_Type)
{ {
@ -4147,6 +4470,16 @@ void Allocator::CalculateStats(Stats* pStats)
m_Pimpl->CalculateStats(*pStats); m_Pimpl->CalculateStats(*pStats);
} }
void Allocator::GetBudget(Budget* pGpuBudget, Budget* pCpuBudget)
{
if(pGpuBudget == NULL && pCpuBudget == NULL)
{
return;
}
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
m_Pimpl->GetBudget(pGpuBudget, pCpuBudget);
}
void Allocator::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) void Allocator::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap)
{ {
D3D12MA_ASSERT(ppStatsString); D3D12MA_ASSERT(ppStatsString);
@ -4168,7 +4501,7 @@ void Allocator::FreeStatsString(WCHAR* pStatsString)
HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator) HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator)
{ {
if(!pDesc || !ppAllocator || !pDesc->pDevice || if(!pDesc || !ppAllocator || !pDesc->pDevice || !pDesc->pAdapter ||
!(pDesc->PreferredBlockSize == 0 || (pDesc->PreferredBlockSize >= 16 && pDesc->PreferredBlockSize < 0x10000000000ull))) !(pDesc->PreferredBlockSize == 0 || (pDesc->PreferredBlockSize >= 16 && pDesc->PreferredBlockSize < 0x10000000000ull)))
{ {
D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateAllocator."); D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateAllocator.");
@ -4181,7 +4514,7 @@ HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator)
SetupAllocationCallbacks(allocationCallbacks, *pDesc); SetupAllocationCallbacks(allocationCallbacks, *pDesc);
*ppAllocator = D3D12MA_NEW(allocationCallbacks, Allocator)(allocationCallbacks, *pDesc); *ppAllocator = D3D12MA_NEW(allocationCallbacks, Allocator)(allocationCallbacks, *pDesc);
HRESULT hr = (*ppAllocator)->m_Pimpl->Init(); HRESULT hr = (*ppAllocator)->m_Pimpl->Init(*pDesc);
if(FAILED(hr)) if(FAILED(hr))
{ {
D3D12MA_DELETE(allocationCallbacks, *ppAllocator); D3D12MA_DELETE(allocationCallbacks, *ppAllocator);

View File

@ -24,7 +24,7 @@
/** \mainpage D3D12 Memory Allocator /** \mainpage D3D12 Memory Allocator
<b>Version 1.0.0-development</b> (2019-10-09) <b>Version 2.0.0-development</b> (2019-11-20)
Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. \n Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. \n
License: MIT License: MIT
@ -307,8 +307,6 @@ Near future: feature parity with [Vulkan Memory Allocator](https://github.com/GP
Later: Later:
- Memory defragmentation - Memory defragmentation
- Query for memory budget using `IDXGIAdapter3::QueryVideoMemoryInfo` and
sticking to this budget with allocations
- Support for resource aliasing (overlap) - Support for resource aliasing (overlap)
- Support for multi-GPU (multi-adapter) - Support for multi-GPU (multi-adapter)
@ -325,10 +323,16 @@ Features deliberately excluded from the scope of this library:
*/ */
// Define this macro to 0 to disable usage of DXGI 1.4 (needed for IDXGIAdapter3 and query for memory budget).
#ifndef D3D12MA_DXGI_1_4
#define D3D12MA_DXGI_1_4 1
#endif
// If using this library on a platform different than Windows PC, you should // If using this library on a platform different than Windows PC, you should
// include D3D12-compatible header before this library on your own. // include D3D12-compatible header before this library on your own and define this macro.
#ifdef _WIN32 #ifndef D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED
#include <d3d12.h> #include <d3d12.h>
#include <dxgi.h>
#endif #endif
/// \cond INTERNAL /// \cond INTERNAL
@ -398,6 +402,10 @@ typedef enum ALLOCATION_FLAGS
#ALLOCATION_FLAG_NEVER_ALLOCATE at the same time. It makes no sense. #ALLOCATION_FLAG_NEVER_ALLOCATE at the same time. It makes no sense.
*/ */
ALLOCATION_FLAG_NEVER_ALLOCATE = 0x2, ALLOCATION_FLAG_NEVER_ALLOCATE = 0x2,
/** TODO
*/
ALLOCATION_FLAG_WITHIN_BUDGET = 0x4,
} ALLOCATION_FLAGS; } ALLOCATION_FLAGS;
/// \brief Parameters of created Allocation object. To be used with Allocator::CreateResource. /// \brief Parameters of created Allocation object. To be used with Allocator::CreateResource.
@ -558,7 +566,10 @@ struct ALLOCATOR_DESC
/// Flags. /// Flags.
ALLOCATOR_FLAGS Flags; ALLOCATOR_FLAGS Flags;
/// Direct3D device object that the allocator should be attached to. /** Direct3D device object that the allocator should be attached to.
Allocator is doing AddRef/Release on this object.
*/
ID3D12Device* pDevice; ID3D12Device* pDevice;
/** \brief Preferred size of a single `ID3D12Heap` block to be allocated. /** \brief Preferred size of a single `ID3D12Heap` block to be allocated.
@ -572,6 +583,12 @@ struct ALLOCATOR_DESC
Optional, can be null. When specified, will be used for all CPU-side memory allocations. Optional, can be null. When specified, will be used for all CPU-side memory allocations.
*/ */
const ALLOCATION_CALLBACKS* pAllocationCallbacks; const ALLOCATION_CALLBACKS* pAllocationCallbacks;
/** TODO
Allocator is doing AddRef/Release on this object.
*/
IDXGIAdapter* pAdapter;
}; };
/** /**
@ -616,6 +633,44 @@ struct Stats
StatInfo HeapType[HEAP_TYPE_COUNT]; StatInfo HeapType[HEAP_TYPE_COUNT];
}; };
/** \brief Statistics of current memory usage and available budget, in bytes, for GPU or CPU memory.
*/
struct Budget
{
/** \brief Sum size of all memory blocks allocated from particular heap type, in bytes.
*/
UINT64 BlockBytes;
/** \brief Sum size of all allocations created in particular heap type, in bytes.
Always less or equal than `BlockBytes`.
Difference `BlockBytes - AllocationBytes` is the amount of memory allocated but unused -
available for new allocations or wasted due to fragmentation.
*/
UINT64 AllocationBytes;
/** \brief Estimated current memory usage of the program, in bytes.
Fetched from system using TODO if enabled.
It might be different than `BlockBytes` (usually higher) due to additional implicit objects
also occupying the memory, like swapchain, pipeline state objects, descriptor heaps, command lists, or
memory blocks allocated outside of this library, if any.
*/
UINT64 Usage;
/** \brief Estimated amount of memory available to the program, in bytes.
Fetched from system using TODO if enabled.
It might be different (most probably smaller) than TODO due to factors
external to the program, like other programs also consuming system resources.
Difference `Budget - Usage` is the amount of additional memory that can probably
be allocated without problems. Exceeding the budget may result in various problems.
*/
UINT64 Budget;
};
/** /**
\brief Represents main object of this library initialized for particular `ID3D12Device`. \brief Represents main object of this library initialized for particular `ID3D12Device`.
@ -702,6 +757,19 @@ public:
*/ */
void CalculateStats(Stats* pStats); void CalculateStats(Stats* pStats);
/** \brief Retrieves information about current memory budget.
\param[out] pGpuBudget Optional, can be null.
\param[out] pCpuBudget Optional, can be null.
This function is called "get" not "calculate" because it is very fast, suitable to be called
every frame or every allocation. For more detailed statistics use CalculateStats().
Note that when using allocator from multiple threads, returned information may immediately
become outdated.
*/
void GetBudget(Budget* pGpuBudget, Budget* pCpuBudget);
/// Builds and returns statistics as a string in JSON format. /// Builds and returns statistics as a string in JSON format.
/** @param[out] ppStatsString Must be freed using Allocator::FreeStatsString. /** @param[out] ppStatsString Must be freed using Allocator::FreeStatsString.
*/ */

View File

@ -420,6 +420,7 @@ void InitD3D() // initializes direct3d 12
D3D12MA::ALLOCATOR_DESC desc = {}; D3D12MA::ALLOCATOR_DESC desc = {};
desc.Flags = g_AllocatorFlags; desc.Flags = g_AllocatorFlags;
desc.pDevice = device; desc.pDevice = device;
desc.pAdapter = adapter;
D3D12MA::ALLOCATION_CALLBACKS allocationCallbacks = {}; D3D12MA::ALLOCATION_CALLBACKS allocationCallbacks = {};
if(ENABLE_CPU_ALLOCATION_CALLBACKS) if(ENABLE_CPU_ALLOCATION_CALLBACKS)

View File

@ -481,6 +481,8 @@ static void TestStats(const TestContext& ctx)
for(UINT i = 0; i < count; ++i) for(UINT i = 0; i < count; ++i)
{ {
if(i == count / 2)
allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
D3D12MA::Allocation* alloc = nullptr; D3D12MA::Allocation* alloc = nullptr;
CHECK_HR( ctx.allocator->CreateResource( CHECK_HR( ctx.allocator->CreateResource(
&allocDesc, &allocDesc,
@ -514,6 +516,18 @@ static void TestStats(const TestContext& ctx)
CheckStatInfo(endStats.HeapType[0]); CheckStatInfo(endStats.HeapType[0]);
CheckStatInfo(endStats.HeapType[1]); CheckStatInfo(endStats.HeapType[1]);
CheckStatInfo(endStats.HeapType[2]); CheckStatInfo(endStats.HeapType[2]);
D3D12MA::Budget gpuBudget = {}, cpuBudget = {};
ctx.allocator->GetBudget(&gpuBudget, &cpuBudget);
CHECK_BOOL(gpuBudget.AllocationBytes <= gpuBudget.BlockBytes);
CHECK_BOOL(gpuBudget.AllocationBytes == endStats.HeapType[0].UsedBytes);
CHECK_BOOL(gpuBudget.BlockBytes == endStats.HeapType[0].UsedBytes + endStats.HeapType[0].UnusedBytes);
CHECK_BOOL(cpuBudget.AllocationBytes <= cpuBudget.BlockBytes);
CHECK_BOOL(cpuBudget.AllocationBytes == endStats.HeapType[1].UsedBytes + endStats.HeapType[2].UsedBytes);
CHECK_BOOL(cpuBudget.BlockBytes == endStats.HeapType[1].UsedBytes + endStats.HeapType[1].UnusedBytes +
endStats.HeapType[2].UsedBytes + endStats.HeapType[2].UnusedBytes);
} }
static void TestTransfer(const TestContext& ctx) static void TestTransfer(const TestContext& ctx)