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.
diff --git a/src/D3D12MemAlloc.cpp b/src/D3D12MemAlloc.cpp
index fdbbea9..5aee696 100644
--- a/src/D3D12MemAlloc.cpp
+++ b/src/D3D12MemAlloc.cpp
@@ -22,6 +22,13 @@
#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 <atomic>
#include <algorithm>
@@ -165,6 +172,8 @@
////////////////////////////////////////////////////////////////////////////////
// Private globals - basic facilities
+#define SAFE_RELEASE(ptr) do { if(ptr) { (ptr)->Release(); (ptr) = NULL; } } while(false)
+
#define D3D12MA_VALIDATE(cond) do { if(!(cond)) { \
D3D12MA_ASSERT(0 && "Validation failed: " #cond); \
return false; \
@@ -225,6 +234,10 @@
#define D3D12MA_ATOMIC_UINT32 std::atomic<UINT>
#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.
// Use types like UINT, uint64_t as T.
template <typename T>
@@ -2073,11 +2086,53 @@
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
{
public:
+ CurrentBudgetData m_Budget;
+
AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc);
- HRESULT Init();
+ HRESULT Init(const ALLOCATOR_DESC& desc);
~AllocatorPimpl();
ID3D12Device* GetDevice() const { return m_Device; }
@@ -2118,6 +2173,9 @@
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 FreeStatsString(WCHAR* pStatsString);
@@ -2133,11 +2191,15 @@
const bool m_UseMutex;
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;
ALLOCATION_CALLBACKS m_AllocationCallbacks;
D3D12MA_ATOMIC_UINT32 m_CurrentFrameIndex;
-
+ DXGI_ADAPTER_DESC m_AdapterDesc;
D3D12_FEATURE_DATA_D3D12_OPTIONS m_D3D12Options;
typedef Vector<Allocation*> AllocationVectorType;
@@ -2193,6 +2255,11 @@
void RegisterCommittedAllocation(Allocation* alloc, D3D12_HEAP_TYPE heapType);
// Unregisters Allocation object from m_pCommittedAllocations.
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 @@
UINT64 size,
UINT id) :
MemoryBlock(allocator, heapType, heapFlags, size, id),
+ m_pMetadata(NULL),
m_BlockVector(blockVector)
{
}
NormalBlock::~NormalBlock()
{
- // 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!");
+ if(m_pMetadata != NULL)
+ {
+ // 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);
- m_pMetadata = NULL;
+ D3D12MA_DELETE(m_Allocator->GetAllocs(), m_pMetadata);
+ }
}
HRESULT NormalBlock::Init()
@@ -2819,6 +2889,7 @@
{
if(m_Heap)
{
+ m_Allocator->m_Budget.m_BlockBytes[HeapTypeToIndex(m_HeapType)] -= m_Size;
m_Heap->Release();
}
}
@@ -2833,7 +2904,12 @@
heapDesc.Alignment = HeapFlagsToAlignment(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 @@
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 =
((createInfo.Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) == 0) &&
- (m_Blocks.size() < m_MaxBlockCount);
+ (m_Blocks.size() < m_MaxBlockCount) &&
+ freeMemory >= size;
if(canCreateNewBlock)
{
@@ -2990,7 +3074,8 @@
}
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.
if(!m_ExplicitBlockSize)
{
@@ -3001,7 +3086,8 @@
{
newBlockSize = smallerNewBlockSize;
++newBlockSizeShift;
- hr = CreateBlock(newBlockSize, &newBlockIndex);
+ hr = newBlockSize <= freeMemory ?
+ CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY;
}
else
{
@@ -3041,6 +3127,13 @@
{
NormalBlock* pBlockToDelete = NULL;
+ bool budgetExceeded = false;
+ {
+ Budget budget = {};
+ m_hAllocator->GetBudgetForHeapType(budget, m_HeapType);
+ budgetExceeded = budget.Usage >= budget.Budget;
+ }
+
// Scope for lock.
{
MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex());
@@ -3050,11 +3143,12 @@
pBlock->m_pMetadata->Free(hAllocation);
D3D12MA_HEAVY_ASSERT(pBlock->Validate());
+ const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount;
// pBlock became empty after this deallocation.
if(pBlock->m_pMetadata->IsEmpty())
{
// 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;
Remove(pBlock);
@@ -3067,7 +3161,7 @@
}
// pBlock didn't become empty, but we have another empty block - find and free that one.
// (This is optional, heuristics.)
- else if(m_HasEmptyBlock)
+ else if(m_HasEmptyBlock && canDeleteBlock)
{
NormalBlock* pLastBlock = m_Blocks.back();
if(pLastBlock->m_pMetadata->IsEmpty() && m_Blocks.size() > m_MinBlockCount)
@@ -3157,6 +3251,7 @@
alignment,
pBlock);
D3D12MA_HEAVY_ASSERT(pBlock->Validate());
+ m_hAllocator->m_Budget.AddAllocation(HeapTypeToIndex(m_HeapType), size);
return S_OK;
}
return E_OUTOFMEMORY;
@@ -3234,6 +3329,10 @@
m_UseMutex((desc.Flags & ALLOCATOR_FLAG_SINGLETHREADED) == 0),
m_AlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0),
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_AllocationCallbacks(allocationCallbacks),
m_CurrentFrameIndex(0)
@@ -3249,11 +3348,24 @@
{
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))
{
return hr;
@@ -3277,11 +3389,24 @@
// 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;
}
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--; )
{
D3D12MA_DELETE(GetAllocs(), m_BlockVectors[i]);
@@ -3385,8 +3510,7 @@
}
else
{
- (*ppAllocation)->Release();
- *ppAllocation = NULL;
+ SAFE_RELEASE(*ppAllocation);
return hr;
}
}
@@ -3479,6 +3603,16 @@
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 = {};
heapProps.Type = pAllocDesc->HeapType;
ID3D12Resource* res = NULL;
@@ -3499,6 +3633,10 @@
}
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;
}
@@ -3516,6 +3654,16 @@
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 = {};
heapDesc.SizeInBytes = allocInfo.SizeInBytes;
heapDesc.Properties.Type = pAllocDesc->HeapType;
@@ -3529,6 +3677,10 @@
(*ppAllocation) = D3D12MA_NEW(m_AllocationCallbacks, Allocation)();
(*ppAllocation)->InitHeap(this, allocInfo.SizeInBytes, pAllocDesc->HeapType, heap);
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;
}
@@ -3676,6 +3828,11 @@
{
D3D12MA_ASSERT(allocation && allocation->m_Type == Allocation::TYPE_COMMITTED);
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)
@@ -3686,6 +3843,7 @@
D3D12MA_ASSERT(block);
BlockVector* const blockVector = block->GetBlockVector();
D3D12MA_ASSERT(blockVector);
+ m_Budget.RemoveAllocation(HeapTypeToIndex(block->GetHeapType()), allocation->GetSize());
blockVector->Free(allocation);
}
@@ -3693,12 +3851,24 @@
{
D3D12MA_ASSERT(allocation && allocation->m_Type == Allocation::TYPE_HEAP);
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)
{
m_CurrentFrameIndex.store(frameIndex);
+
+#if D3D12MA_DXGI_1_4
+ if(m_Adapter3)
+ {
+ UpdateD3D12Budget();
+ }
+#endif
}
void AllocatorPimpl::CalculateStats(Stats& outStats)
@@ -3739,7 +3909,7 @@
statInfo.UnusedBytes = 0;
statInfo.AllocationSizeMin = size;
statInfo.AllocationSizeMax = size;
- statInfo.UnusedRangeSizeMin = 0;
+ statInfo.UnusedRangeSizeMin = UINT64_MAX;
statInfo.UnusedRangeSizeMax = 0;
AddStatInfo(outStats.Total, statInfo);
AddStatInfo(heapStatInfo, statInfo);
@@ -3752,6 +3922,94 @@
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)
{
json.BeginObject();
@@ -3795,10 +4053,14 @@
{
JsonWriter json(GetAllocs(), sb);
+ Budget gpuBudget = {}, cpuBudget = {};
+ GetBudget(&gpuBudget, &cpuBudget);
+
Stats stats;
CalculateStats(stats);
json.BeginObject();
+
json.WriteString(L"Total");
AddStatInfoToJson(json, stats.Total);
for (size_t heapType = 0; heapType < HEAP_TYPE_COUNT; ++heapType)
@@ -3807,6 +4069,16 @@
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)
{
json.WriteString(L"DetailedMap");
@@ -3905,6 +4177,60 @@
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
@@ -3917,10 +4243,7 @@
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
- if(m_Resource)
- {
- m_Resource->Release();
- }
+ SAFE_RELEASE(m_Resource);
switch(m_Type)
{
@@ -4147,6 +4470,16 @@
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)
{
D3D12MA_ASSERT(ppStatsString);
@@ -4168,7 +4501,7 @@
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)))
{
D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateAllocator.");
@@ -4181,7 +4514,7 @@
SetupAllocationCallbacks(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))
{
D3D12MA_DELETE(allocationCallbacks, *ppAllocator);
diff --git a/src/D3D12MemAlloc.h b/src/D3D12MemAlloc.h
index 64b1b8a..4951f66 100644
--- a/src/D3D12MemAlloc.h
+++ b/src/D3D12MemAlloc.h
@@ -24,7 +24,7 @@
/** \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
License: MIT
@@ -307,8 +307,6 @@
Later:
- Memory defragmentation
-- Query for memory budget using `IDXGIAdapter3::QueryVideoMemoryInfo` and
- sticking to this budget with allocations
- Support for resource aliasing (overlap)
- Support for multi-GPU (multi-adapter)
@@ -325,10 +323,16 @@
*/
+// 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
-// include D3D12-compatible header before this library on your own.
-#ifdef _WIN32
+// include D3D12-compatible header before this library on your own and define this macro.
+#ifndef D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED
#include <d3d12.h>
+ #include <dxgi.h>
#endif
/// \cond INTERNAL
@@ -398,6 +402,10 @@
#ALLOCATION_FLAG_NEVER_ALLOCATE at the same time. It makes no sense.
*/
ALLOCATION_FLAG_NEVER_ALLOCATE = 0x2,
+
+ /** TODO
+ */
+ ALLOCATION_FLAG_WITHIN_BUDGET = 0x4,
} ALLOCATION_FLAGS;
/// \brief Parameters of created Allocation object. To be used with Allocator::CreateResource.
@@ -558,7 +566,10 @@
/// 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;
/** \brief Preferred size of a single `ID3D12Heap` block to be allocated.
@@ -572,6 +583,12 @@
Optional, can be null. When specified, will be used for all CPU-side memory allocations.
*/
const ALLOCATION_CALLBACKS* pAllocationCallbacks;
+
+ /** TODO
+
+ Allocator is doing AddRef/Release on this object.
+ */
+ IDXGIAdapter* pAdapter;
};
/**
@@ -616,6 +633,44 @@
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`.
@@ -702,6 +757,19 @@
*/
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.
/** @param[out] ppStatsString Must be freed using Allocator::FreeStatsString.
*/
diff --git a/src/D3D12Sample.cpp b/src/D3D12Sample.cpp
index aa7bfa0..9fb9e22 100644
--- a/src/D3D12Sample.cpp
+++ b/src/D3D12Sample.cpp
@@ -420,6 +420,7 @@
D3D12MA::ALLOCATOR_DESC desc = {};
desc.Flags = g_AllocatorFlags;
desc.pDevice = device;
+ desc.pAdapter = adapter;
D3D12MA::ALLOCATION_CALLBACKS allocationCallbacks = {};
if(ENABLE_CPU_ALLOCATION_CALLBACKS)
diff --git a/src/Tests.cpp b/src/Tests.cpp
index 19e11c7..ee31e16 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -481,6 +481,8 @@
for(UINT i = 0; i < count; ++i)
{
+ if(i == count / 2)
+ allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
D3D12MA::Allocation* alloc = nullptr;
CHECK_HR( ctx.allocator->CreateResource(
&allocDesc,
@@ -514,6 +516,18 @@
CheckStatInfo(endStats.HeapType[0]);
CheckStatInfo(endStats.HeapType[1]);
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)