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 + #if D3D12MA_DXGI_1_4 + #include + #endif +#endif + #include #include #include @@ -165,6 +172,8 @@ static void SetupAllocationCallbacks(ALLOCATION_CALLBACKS& outAllocs, const ALLO //////////////////////////////////////////////////////////////////////////////// // 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 @@ If providing your own implementation, you need to implement a subset of std::ato #define D3D12MA_ATOMIC_UINT32 std::atomic #endif +#ifndef D3D12MA_ATOMIC_UINT64 + #define D3D12MA_ATOMIC_UINT64 std::atomic +#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 @@ -2073,11 +2086,53 @@ private: 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 @@ public: 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 @@ private: 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 AllocationVectorType; @@ -2193,6 +2255,11 @@ private: 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 @@ NormalBlock::NormalBlock( 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 @@ MemoryBlock::~MemoryBlock() { if(m_Heap) { + m_Allocator->m_Budget.m_BlockBytes[HeapTypeToIndex(m_HeapType)] -= m_Size; m_Heap->Release(); } } @@ -2833,7 +2904,12 @@ HRESULT MemoryBlock::Init() 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 @@ HRESULT BlockVector::AllocatePage( 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 @@ HRESULT BlockVector::AllocatePage( } 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 @@ HRESULT BlockVector::AllocatePage( { newBlockSize = smallerNewBlockSize; ++newBlockSizeShift; - hr = CreateBlock(newBlockSize, &newBlockIndex); + hr = newBlockSize <= freeMemory ? + CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY; } else { @@ -3041,6 +3127,13 @@ void BlockVector::Free(Allocation* hAllocation) { 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 @@ void BlockVector::Free(Allocation* hAllocation) 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 @@ void BlockVector::Free(Allocation* hAllocation) } // 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 @@ HRESULT BlockVector::AllocateFromBlock( 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 @@ AllocatorPimpl::AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, 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 @@ AllocatorPimpl::AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, { 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(&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 @@ HRESULT AllocatorPimpl::Init() // 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 @@ HRESULT AllocatorPimpl::CreateResource( } else { - (*ppAllocation)->Release(); - *ppAllocation = NULL; + SAFE_RELEASE(*ppAllocation); return hr; } } @@ -3479,6 +3603,16 @@ HRESULT AllocatorPimpl::AllocateCommittedResource( 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 @@ HRESULT AllocatorPimpl::AllocateCommittedResource( } 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 @@ HRESULT AllocatorPimpl::AllocateHeap( 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 @@ HRESULT AllocatorPimpl::AllocateHeap( (*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 @@ void AllocatorPimpl::FreeCommittedMemory(Allocation* allocation) { 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 @@ void AllocatorPimpl::FreePlacedMemory(Allocation* allocation) 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 @@ void AllocatorPimpl::FreeHeapMemory(Allocation* allocation) { 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 @@ void AllocatorPimpl::CalculateStats(Stats& outStats) 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 @@ void AllocatorPimpl::CalculateStats(Stats& outStats) 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 @@ void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) { 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 @@ void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) 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 @@ void AllocatorPimpl::FreeStatsString(WCHAR* 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 @@ -3917,10 +4243,7 @@ void Allocation::Release() D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK - if(m_Resource) - { - m_Resource->Release(); - } + SAFE_RELEASE(m_Resource); switch(m_Type) { @@ -4147,6 +4470,16 @@ void Allocator::CalculateStats(Stats* 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) { D3D12MA_ASSERT(ppStatsString); @@ -4168,7 +4501,7 @@ void Allocator::FreeStatsString(WCHAR* pStatsString) 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 @@ HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator) 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 -Version 1.0.0-development (2019-10-09) +Version 2.0.0-development (2019-11-20) Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. \n License: MIT @@ -307,8 +307,6 @@ Near future: feature parity with [Vulkan Memory Allocator](https://github.com/GP 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 @@ 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 -// 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 + #include #endif /// \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 = 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 @@ struct ALLOCATOR_DESC /// 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 @@ struct ALLOCATOR_DESC 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 @@ struct Stats 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 @@ public: */ 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 @@ void InitD3D() // initializes direct3d 12 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 @@ static void TestStats(const TestContext& ctx) 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 @@ static void TestStats(const TestContext& ctx) 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)