Merge branch 'feature-virtual-allocator'

This commit is contained in:
Adam Sawicki 2020-07-15 13:54:39 +02:00
commit 0af956a556
6 changed files with 583 additions and 74 deletions

View File

@ -157,11 +157,11 @@ void D3D12MA_DELETE_ARRAY(const ALLOCATION_CALLBACKS& allocs, T* memory, size_t
}
}
static void SetupAllocationCallbacks(ALLOCATION_CALLBACKS& outAllocs, const ALLOCATOR_DESC& allocatorDesc)
static void SetupAllocationCallbacks(ALLOCATION_CALLBACKS& outAllocs, const ALLOCATION_CALLBACKS* allocationCallbacks)
{
if(allocatorDesc.pAllocationCallbacks)
if(allocationCallbacks)
{
outAllocs = *allocatorDesc.pAllocationCallbacks;
outAllocs = *allocationCallbacks;
D3D12MA_ASSERT(outAllocs.pAllocate != NULL && outAllocs.pFree != NULL);
}
else
@ -1998,7 +1998,7 @@ struct Suballocation
{
UINT64 offset;
UINT64 size;
Allocation* allocation;
void* userData;
SuballocationType type;
};
@ -2108,19 +2108,22 @@ in a single ID3D12Heap memory block.
class BlockMetadata
{
public:
BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks);
BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual);
virtual ~BlockMetadata() { }
virtual void Init(UINT64 size) { m_Size = size; }
// Validates all data structures inside this object. If not valid, returns false.
virtual bool Validate() const = 0;
UINT64 GetSize() const { return m_Size; }
bool IsVirtual() const { return m_IsVirtual; }
virtual size_t GetAllocationCount() const = 0;
virtual UINT64 GetSumFreeSize() const = 0;
virtual UINT64 GetUnusedRangeSizeMax() const = 0;
// Returns true if this block is empty - contains only single free suballocation.
virtual bool IsEmpty() const = 0;
virtual void GetAllocationInfo(UINT64 offset, VIRTUAL_ALLOCATION_INFO& outInfo) const = 0;
// Tries to find a place for suballocation with given parameters inside this block.
// If succeeded, fills pAllocationRequest and returns true.
// If failed, returns false.
@ -2133,11 +2136,14 @@ public:
virtual void Alloc(
const AllocationRequest& request,
UINT64 allocSize,
Allocation* Allocation) = 0;
void* userData) = 0;
// Frees suballocation assigned to given memory region.
virtual void Free(const Allocation* allocation) = 0;
virtual void FreeAtOffset(UINT64 offset) = 0;
// Frees all allocations.
// Careful! Don't call it if there are Allocation objects owned by pUserData of of cleared allocations!
virtual void Clear() = 0;
virtual void SetAllocationUserData(UINT64 offset, void* userData) = 0;
virtual void CalcAllocationStatInfo(StatInfo& outInfo) const = 0;
virtual void WriteAllocationInfoToJson(JsonWriter& json) const = 0;
@ -2147,6 +2153,7 @@ protected:
private:
UINT64 m_Size;
bool m_IsVirtual;
const ALLOCATION_CALLBACKS* m_pAllocationCallbacks;
D3D12MA_CLASS_NO_COPY(BlockMetadata);
@ -2155,7 +2162,7 @@ private:
class BlockMetadata_Generic : public BlockMetadata
{
public:
BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks);
BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual);
virtual ~BlockMetadata_Generic();
virtual void Init(UINT64 size);
@ -2165,6 +2172,8 @@ public:
virtual UINT64 GetUnusedRangeSizeMax() const;
virtual bool IsEmpty() const;
virtual void GetAllocationInfo(UINT64 offset, VIRTUAL_ALLOCATION_INFO& outInfo) const;
virtual bool CreateAllocationRequest(
UINT64 allocSize,
UINT64 allocAlignment,
@ -2173,10 +2182,12 @@ public:
virtual void Alloc(
const AllocationRequest& request,
UINT64 allocSize,
Allocation* hAllocation);
void* userData);
virtual void Free(const Allocation* allocation);
virtual void FreeAtOffset(UINT64 offset);
virtual void Clear();
virtual void SetAllocationUserData(UINT64 offset, void* userData);
virtual void CalcAllocationStatInfo(StatInfo& outInfo) const;
virtual void WriteAllocationInfoToJson(JsonWriter& json) const;
@ -2619,8 +2630,9 @@ private:
////////////////////////////////////////////////////////////////////////////////
// Private class BlockMetadata implementation
BlockMetadata::BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks) :
BlockMetadata::BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) :
m_Size(0),
m_IsVirtual(isVirtual),
m_pAllocationCallbacks(allocationCallbacks)
{
D3D12MA_ASSERT(allocationCallbacks);
@ -2629,8 +2641,8 @@ BlockMetadata::BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks) :
////////////////////////////////////////////////////////////////////////////////
// Private class BlockMetadata_Generic implementation
BlockMetadata_Generic::BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks) :
BlockMetadata(allocationCallbacks),
BlockMetadata_Generic::BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) :
BlockMetadata(allocationCallbacks, isVirtual),
m_FreeCount(0),
m_SumFreeSize(0),
m_Suballocations(*allocationCallbacks),
@ -2655,7 +2667,7 @@ void BlockMetadata_Generic::Init(UINT64 size)
suballoc.offset = 0;
suballoc.size = size;
suballoc.type = SUBALLOCATION_TYPE_FREE;
suballoc.allocation = NULL;
suballoc.userData = NULL;
D3D12MA_ASSERT(size > MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER);
m_Suballocations.push_back(suballoc);
@ -2693,7 +2705,11 @@ bool BlockMetadata_Generic::Validate() const
// Two adjacent free suballocations are invalid. They should be merged.
D3D12MA_VALIDATE(!prevFree || !currFree);
D3D12MA_VALIDATE(currFree == (subAlloc.allocation == NULL));
const Allocation* const alloc = (Allocation*)subAlloc.userData;
if(!IsVirtual())
{
D3D12MA_VALIDATE(currFree == (alloc == NULL));
}
if(currFree)
{
@ -2709,8 +2725,11 @@ bool BlockMetadata_Generic::Validate() const
}
else
{
D3D12MA_VALIDATE(subAlloc.allocation->GetOffset() == subAlloc.offset);
D3D12MA_VALIDATE(subAlloc.allocation->GetSize() == subAlloc.size);
if(!IsVirtual())
{
D3D12MA_VALIDATE(alloc->GetOffset() == subAlloc.offset);
D3D12MA_VALIDATE(alloc->GetSize() == subAlloc.size);
}
// Margin required between allocations - previous allocation must be free.
D3D12MA_VALIDATE(D3D12MA_DEBUG_MARGIN == 0 || prevFree);
@ -2763,6 +2782,23 @@ bool BlockMetadata_Generic::IsEmpty() const
return (m_Suballocations.size() == 1) && (m_FreeCount == 1);
}
void BlockMetadata_Generic::GetAllocationInfo(UINT64 offset, VIRTUAL_ALLOCATION_INFO& outInfo) const
{
for(SuballocationList::const_iterator suballocItem = m_Suballocations.cbegin();
suballocItem != m_Suballocations.cend();
++suballocItem)
{
const Suballocation& suballoc = *suballocItem;
if(suballoc.offset == offset)
{
outInfo.size = suballoc.size;
outInfo.pUserData = suballoc.userData;
return;
}
}
D3D12MA_ASSERT(0 && "Not found!");
}
bool BlockMetadata_Generic::CreateAllocationRequest(
UINT64 allocSize,
UINT64 allocAlignment,
@ -2812,7 +2848,7 @@ bool BlockMetadata_Generic::CreateAllocationRequest(
void BlockMetadata_Generic::Alloc(
const AllocationRequest& request,
UINT64 allocSize,
Allocation* allocation)
void* userData)
{
D3D12MA_ASSERT(request.item != m_Suballocations.end());
Suballocation& suballoc = *request.item;
@ -2831,7 +2867,7 @@ void BlockMetadata_Generic::Alloc(
suballoc.offset = request.offset;
suballoc.size = allocSize;
suballoc.type = SUBALLOCATION_TYPE_ALLOCATION;
suballoc.allocation = allocation;
suballoc.userData = userData;
// If there are any free bytes remaining at the end, insert new free suballocation after current one.
if(paddingEnd)
@ -2874,23 +2910,6 @@ void BlockMetadata_Generic::Alloc(
m_ZeroInitializedRange.MarkRangeAsUsed(request.offset, request.offset + allocSize);
}
void BlockMetadata_Generic::Free(const Allocation* allocation)
{
for(SuballocationList::iterator suballocItem = m_Suballocations.begin();
suballocItem != m_Suballocations.end();
++suballocItem)
{
Suballocation& suballoc = *suballocItem;
if(suballoc.allocation == allocation)
{
FreeSuballocation(suballocItem);
D3D12MA_HEAVY_ASSERT(Validate());
return;
}
}
D3D12MA_ASSERT(0 && "Not found!");
}
void BlockMetadata_Generic::FreeAtOffset(UINT64 offset)
{
for(SuballocationList::iterator suballocItem = m_Suballocations.begin();
@ -2907,6 +2926,22 @@ void BlockMetadata_Generic::FreeAtOffset(UINT64 offset)
D3D12MA_ASSERT(0 && "Not found!");
}
void BlockMetadata_Generic::Clear()
{
m_FreeCount = 1;
m_SumFreeSize = GetSize();
m_Suballocations.clear();
Suballocation suballoc = {};
suballoc.offset = 0;
suballoc.size = GetSize();
suballoc.type = SUBALLOCATION_TYPE_FREE;
m_Suballocations.push_back(suballoc);
m_FreeSuballocationsBySize.clear();
m_FreeSuballocationsBySize.push_back(m_Suballocations.begin());
}
bool BlockMetadata_Generic::ValidateFreeSuballocationList() const
{
UINT64 lastSize = 0;
@ -2999,7 +3034,7 @@ SuballocationList::iterator BlockMetadata_Generic::FreeSuballocation(Suballocati
// Change this suballocation to be marked as free.
Suballocation& suballoc = *suballocItem;
suballoc.type = SUBALLOCATION_TYPE_FREE;
suballoc.allocation = NULL;
suballoc.userData = NULL;
// Update totals.
++m_FreeCount;
@ -3104,6 +3139,22 @@ void BlockMetadata_Generic::UnregisterFreeSuballocation(SuballocationList::itera
//D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList());
}
void BlockMetadata_Generic::SetAllocationUserData(UINT64 offset, void* userData)
{
for(SuballocationList::iterator suballocItem = m_Suballocations.begin();
suballocItem != m_Suballocations.end();
++suballocItem)
{
Suballocation& suballoc = *suballocItem;
if(suballoc.offset == offset)
{
suballoc.userData = userData;
return;
}
}
D3D12MA_ASSERT(0 && "Not found!");
}
void BlockMetadata_Generic::CalcAllocationStatInfo(StatInfo& outInfo) const
{
outInfo.BlockCount = 1;
@ -3166,9 +3217,21 @@ void BlockMetadata_Generic::WriteAllocationInfoToJson(JsonWriter& json) const
json.WriteString(L"Size");
json.WriteNumber(suballoc.size);
}
else if(IsVirtual())
{
json.WriteString(L"Type");
json.WriteString(L"ALLOCATION");
json.WriteString(L"Size");
json.WriteNumber(suballoc.size);
if(suballoc.userData)
{
json.WriteString(L"UserData");
json.WriteNumber((uintptr_t)suballoc.userData);
}
}
else
{
const Allocation* const alloc = suballoc.allocation;
const Allocation* const alloc = (const Allocation*)suballoc.userData;
D3D12MA_ASSERT(alloc);
json.AddAllocationToObject(*alloc);
}
@ -3214,7 +3277,7 @@ HRESULT NormalBlock::Init()
return hr;
}
m_pMetadata = D3D12MA_NEW(m_Allocator->GetAllocs(), BlockMetadata_Generic)(&m_Allocator->GetAllocs());
m_pMetadata = D3D12MA_NEW(m_Allocator->GetAllocs(), BlockMetadata_Generic)(&m_Allocator->GetAllocs(), false);
m_pMetadata->Init(m_Size);
return hr;
@ -3503,7 +3566,7 @@ void BlockVector::Free(Allocation* hAllocation)
NormalBlock* pBlock = hAllocation->m_Placed.block;
pBlock->m_pMetadata->Free(hAllocation);
pBlock->m_pMetadata->FreeAtOffset(hAllocation->GetOffset());
D3D12MA_HEAVY_ASSERT(pBlock->Validate());
const size_t blockCount = m_Blocks.size();
@ -5483,14 +5546,14 @@ void Allocator::GetBudget(Budget* pGpuBudget, Budget* pCpuBudget)
m_Pimpl->GetBudget(pGpuBudget, pCpuBudget);
}
void Allocator::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap)
void Allocator::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) const
{
D3D12MA_ASSERT(ppStatsString);
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
m_Pimpl->BuildStatsString(ppStatsString, DetailedMap);
}
void Allocator::FreeStatsString(WCHAR* pStatsString)
void Allocator::FreeStatsString(WCHAR* pStatsString) const
{
if (pStatsString != NULL)
{
@ -5499,6 +5562,169 @@ void Allocator::FreeStatsString(WCHAR* pStatsString)
}
}
////////////////////////////////////////////////////////////////////////////////
// Private class VirtualBlockPimpl definition
class VirtualBlockPimpl
{
public:
const ALLOCATION_CALLBACKS m_AllocationCallbacks;
const UINT64 m_Size;
BlockMetadata_Generic m_Metadata;
VirtualBlockPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, UINT64 size);
~VirtualBlockPimpl();
};
VirtualBlockPimpl::VirtualBlockPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, UINT64 size) :
m_AllocationCallbacks(allocationCallbacks),
m_Size(size),
m_Metadata(&m_AllocationCallbacks,
true) // isVirtual
{
m_Metadata.Init(m_Size);
}
VirtualBlockPimpl::~VirtualBlockPimpl()
{
}
////////////////////////////////////////////////////////////////////////////////
// Public class VirtualBlock implementation
VirtualBlock::VirtualBlock(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc) :
m_Pimpl(D3D12MA_NEW(allocationCallbacks, VirtualBlockPimpl)(allocationCallbacks, desc.Size))
{
}
VirtualBlock::~VirtualBlock()
{
// THIS IS AN IMPORTANT ASSERT!
// Hitting it means you have some memory leak - unreleased allocations in this virtual block.
D3D12MA_ASSERT(m_Pimpl->m_Metadata.IsEmpty() && "Some allocations were not freed before destruction of this virtual block!");
D3D12MA_DELETE(m_Pimpl->m_AllocationCallbacks, m_Pimpl);
}
void VirtualBlock::Release()
{
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
// Copy is needed because otherwise we would call destructor and invalidate the structure with callbacks before using it to free memory.
const ALLOCATION_CALLBACKS allocationCallbacksCopy = m_Pimpl->m_AllocationCallbacks;
D3D12MA_DELETE(allocationCallbacksCopy, this);
}
BOOL VirtualBlock::IsEmpty() const
{
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
return m_Pimpl->m_Metadata.IsEmpty() ? TRUE : FALSE;
}
void VirtualBlock::GetAllocationInfo(UINT64 offset, VIRTUAL_ALLOCATION_INFO* pInfo) const
{
D3D12MA_ASSERT(offset != UINT64_MAX && pInfo);
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
m_Pimpl->m_Metadata.GetAllocationInfo(offset, *pInfo);
}
HRESULT VirtualBlock::Allocate(const VIRTUAL_ALLOCATION_DESC* pDesc, UINT64* pOffset)
{
if(!pDesc || !pOffset || pDesc->Size == 0 || !IsPow2(pDesc->Alignment))
{
D3D12MA_ASSERT(0 && "Invalid arguments passed to VirtualBlock::Allocate.");
return E_INVALIDARG;
}
*pOffset = UINT64_MAX;
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
const UINT64 alignment = pDesc->Alignment != 0 ? pDesc->Alignment : 1;
AllocationRequest allocRequest = {};
if(m_Pimpl->m_Metadata.CreateAllocationRequest(pDesc->Size, alignment, &allocRequest))
{
m_Pimpl->m_Metadata.Alloc(allocRequest, pDesc->Size, pDesc->pUserData);
D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata.Validate());
*pOffset = allocRequest.offset;
return S_OK;
}
else
{
return E_OUTOFMEMORY;
}
}
void VirtualBlock::FreeAllocation(UINT64 offset)
{
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
D3D12MA_ASSERT(offset != UINT64_MAX);
m_Pimpl->m_Metadata.FreeAtOffset(offset);
D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata.Validate());
}
void VirtualBlock::Clear()
{
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
m_Pimpl->m_Metadata.Clear();
D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata.Validate());
}
void VirtualBlock::SetAllocationUserData(UINT64 offset, void* pUserData)
{
D3D12MA_ASSERT(offset != UINT64_MAX);
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
m_Pimpl->m_Metadata.SetAllocationUserData(offset, pUserData);
}
void VirtualBlock::CalculateStats(StatInfo* pInfo) const
{
D3D12MA_ASSERT(pInfo);
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata.Validate());
m_Pimpl->m_Metadata.CalcAllocationStatInfo(*pInfo);
}
void VirtualBlock::BuildStatsString(WCHAR** ppStatsString) const
{
D3D12MA_ASSERT(ppStatsString);
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
StringBuilder sb(m_Pimpl->m_AllocationCallbacks);
{
JsonWriter json(m_Pimpl->m_AllocationCallbacks, sb);
D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata.Validate());
m_Pimpl->m_Metadata.WriteAllocationInfoToJson(json);
} // Scope for JsonWriter
const size_t length = sb.GetLength();
WCHAR* result = AllocateArray<WCHAR>(m_Pimpl->m_AllocationCallbacks, length + 1);
memcpy(result, sb.GetData(), length * sizeof(WCHAR));
result[length] = L'\0';
*ppStatsString = result;
}
void VirtualBlock::FreeStatsString(WCHAR* pStatsString) const
{
if (pStatsString != NULL)
{
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
D3D12MA::Free(m_Pimpl->m_AllocationCallbacks, pStatsString);
}
}
////////////////////////////////////////////////////////////////////////////////
// Public global functions
@ -5514,7 +5740,7 @@ HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator)
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
ALLOCATION_CALLBACKS allocationCallbacks;
SetupAllocationCallbacks(allocationCallbacks, *pDesc);
SetupAllocationCallbacks(allocationCallbacks, pDesc->pAllocationCallbacks);
*ppAllocator = D3D12MA_NEW(allocationCallbacks, Allocator)(allocationCallbacks, *pDesc);
HRESULT hr = (*ppAllocator)->m_Pimpl->Init(*pDesc);
@ -5526,4 +5752,21 @@ HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator)
return hr;
}
HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC* pDesc, VirtualBlock** ppVirtualBlock)
{
if(!pDesc || !ppVirtualBlock)
{
D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateVirtualBlock.");
return E_INVALIDARG;
}
D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK
ALLOCATION_CALLBACKS allocationCallbacks;
SetupAllocationCallbacks(allocationCallbacks, pDesc->pAllocationCallbacks);
*ppVirtualBlock = D3D12MA_NEW(allocationCallbacks, VirtualBlock)(allocationCallbacks, *pDesc);
return S_OK;
}
} // namespace D3D12MA

View File

@ -24,7 +24,7 @@
/** \mainpage D3D12 Memory Allocator
<b>Version 2.0.0-development</b> (2020-05-25)
<b>Version 2.0.0-development</b> (2020-06-15)
Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. \n
License: MIT
@ -425,6 +425,7 @@ class PoolPimpl;
class NormalBlock;
class BlockVector;
class JsonWriter;
class VirtualBlockPimpl;
/// \endcond
class Pool;
@ -1074,10 +1075,10 @@ public:
/** @param[out] ppStatsString Must be freed using Allocator::FreeStatsString.
@param DetailedMap `TRUE` to include full list of allocations (can make the string quite long), `FALSE` to only return statistics.
*/
void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap);
void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) const;
/// Frees memory of a string returned from Allocator::BuildStatsString.
void FreeStatsString(WCHAR* pStatsString);
void FreeStatsString(WCHAR* pStatsString) const;
private:
friend HRESULT CreateAllocator(const ALLOCATOR_DESC*, Allocator**);
@ -1092,12 +1093,134 @@ private:
D3D12MA_CLASS_NO_COPY(Allocator)
};
/** \brief Creates new main Allocator object and returns it through `ppAllocator`.
/// Parameters of created D3D12MA::VirtualBlock object to be passed to CreateVirtualBlock().
struct VIRTUAL_BLOCK_DESC
{
/** \brief Total size of the block.
Sizes can be expressed in bytes or any units you want as long as you are consistent in using them.
For example, if you allocate from some array of structures, 1 can mean single instance of entire structure.
*/
UINT64 Size;
/** \brief Custom CPU memory allocation callbacks. Optional.
Optional, can be null. When specified, will be used for all CPU-side memory allocations.
*/
const ALLOCATION_CALLBACKS* pAllocationCallbacks;
};
/// Parameters of created virtual allocation to be passed to VirtualBlock::Allocate().
struct VIRTUAL_ALLOCATION_DESC
{
/** \brief Size of the allocation.
Cannot be zero.
*/
UINT64 Size;
/** \brief Required alignment of the allocation.
Must be power of two. Special value 0 has the same meaning as 1 - means no special alignment is required, so allocation can start at any offset.
*/
UINT64 Alignment;
/** \brief Custom pointer to be associated with the allocation.
It can be fetched or changed later.
*/
void* pUserData;
};
/// Parameters of an existing virtual allocation, returned by VirtualBlock::GetAllocationInfo().
struct VIRTUAL_ALLOCATION_INFO
{
/** \brief Size of the allocation.
Same value as passed in VIRTUAL_ALLOCATION_DESC::size.
*/
UINT64 size;
/** \brief Custom pointer associated with the allocation.
Same value as passed in VIRTUAL_ALLOCATION_DESC::pUserData or VirtualBlock::SetAllocationUserData().
*/
void* pUserData;
};
/** \brief Represents pure allocation algorithm and a data structure with allocations in some memory block, without actually allocating any GPU memory.
This class allows to use the core algorithm of the library custom allocations e.g. CPU memory or
sub-allocation regions inside a single GPU buffer.
To create this object, fill in D3D12MA::VIRTUAL_BLOCK_DESC and call CreateVirtualBlock().
To destroy it, call its method VirtualBlock::Release().
*/
class VirtualBlock
{
public:
/** \brief Destroys this object and frees it from memory.
You need to free all the allocations within this block or call Clear() before destroying it.
*/
void Release();
/** \brief Returns true if the block is empty - contains 0 allocations.
*/
BOOL IsEmpty() const;
/** \brief Returns information about an allocation at given offset - its size and custom pointer.
*/
void GetAllocationInfo(UINT64 offset, VIRTUAL_ALLOCATION_INFO* pInfo) const;
/** \brief Creates new allocation.
\param pDesc
\param[out] pOffset Offset of the new allocation, which can also be treated as an unique identifier of the allocation within this block. `UINT64_MAX` if allocation failed.
\return `S_OK` if allocation succeeded, `E_OUTOFMEMORY` if it failed.
*/
HRESULT Allocate(const VIRTUAL_ALLOCATION_DESC* pDesc, UINT64* pOffset);
/** \brief Frees the allocation at given offset.
*/
void FreeAllocation(UINT64 offset);
/** \brief Frees all the allocations.
*/
void Clear();
/** \brief Changes custom pointer for an allocation at given offset to a new value.
*/
void SetAllocationUserData(UINT64 offset, void* pUserData);
/** \brief Retrieves statistics from the current state of the block.
*/
void CalculateStats(StatInfo* pInfo) const;
/** \brief Builds and returns statistics as a string in JSON format, including the list of allocations with their parameters.
@param[out] ppStatsString Must be freed using VirtualBlock::FreeStatsString.
*/
void BuildStatsString(WCHAR** ppStatsString) const;
/** \brief Frees memory of a string returned from VirtualBlock::BuildStatsString.
*/
void FreeStatsString(WCHAR* pStatsString) const;
private:
friend HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC*, VirtualBlock**);
template<typename T> friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*);
VirtualBlockPimpl* m_Pimpl;
VirtualBlock(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc);
~VirtualBlock();
D3D12MA_CLASS_NO_COPY(VirtualBlock)
};
/** \brief Creates new main D3D12MA::Allocator object and returns it through `ppAllocator`.
You normally only need to call it once and keep a single Allocator object for your `ID3D12Device`.
*/
HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator);
/** \brief Creates new D3D12MA::VirtualBlock object and returns it through `ppVirtualBlock`.
Note you don't need to create D3D12MA::Allocator to use virtual blocks.
*/
HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC* pDesc, VirtualBlock** ppVirtualBlock);
} // namespace D3D12MA
/// \cond INTERNAL

View File

@ -49,6 +49,7 @@ static const bool ENABLE_DEBUG_LAYER = true;
static const bool ENABLE_CPU_ALLOCATION_CALLBACKS = true;
static const bool ENABLE_CPU_ALLOCATION_CALLBACKS_PRINT = false;
static constexpr D3D12MA::ALLOCATOR_FLAGS g_AllocatorFlags = D3D12MA::ALLOCATOR_FLAG_NONE;
static D3D12MA::ALLOCATION_CALLBACKS g_AllocationCallbacks = {}; // Used only when ENABLE_CPU_ALLOCATION_CALLBACKS
static HINSTANCE g_Instance;
static HWND g_Wnd;
@ -422,13 +423,12 @@ void InitD3D() // initializes direct3d 12
desc.pDevice = device;
desc.pAdapter = adapter;
D3D12MA::ALLOCATION_CALLBACKS allocationCallbacks = {};
if(ENABLE_CPU_ALLOCATION_CALLBACKS)
{
allocationCallbacks.pAllocate = &CustomAllocate;
allocationCallbacks.pFree = &CustomFree;
allocationCallbacks.pUserData = CUSTOM_ALLOCATION_USER_DATA;
desc.pAllocationCallbacks = &allocationCallbacks;
g_AllocationCallbacks.pAllocate = &CustomAllocate;
g_AllocationCallbacks.pFree = &CustomFree;
g_AllocationCallbacks.pUserData = CUSTOM_ALLOCATION_USER_DATA;
desc.pAllocationCallbacks = &g_AllocationCallbacks;
}
CHECK_HR( D3D12MA::CreateAllocator(&desc, &g_Allocator) );
@ -1373,6 +1373,7 @@ static void ExecuteTests()
try
{
TestContext ctx = {};
ctx.allocationCallbacks = &g_AllocationCallbacks;
ctx.device = g_Device;
ctx.allocator = g_Allocator;
ctx.allocatorFlags = g_AllocatorFlags;

View File

@ -1,4 +1,4 @@
# Doxyfile 1.8.16
# Doxyfile 1.8.18
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -263,12 +263,6 @@ TAB_SIZE = 4
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@ -309,14 +303,14 @@ OPTIMIZE_OUTPUT_SLICE = NO
# parses. With this tag you can assign which parser to use for a given
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
# .inc files as Fortran files (default is PHP), and .f files as C (default is
# Fortran), use: inc=Fortran f=C.
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
@ -535,8 +529,8 @@ HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# (class|struct|union) declarations. If set to NO, these declarations will be
# included in the documentation.
# declarations. If set to NO, these declarations will be included in the
# documentation.
# The default value is: NO.
HIDE_FRIEND_COMPOUNDS = YES
@ -851,8 +845,10 @@ INPUT_ENCODING = UTF-8
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
*.cc \
@ -1294,9 +1290,9 @@ HTML_TIMESTAMP = NO
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via Javascript. If disabled, the navigation index will
# are dynamically created via JavaScript. If disabled, the navigation index will
# consists of multiple levels of tabs that are statically embedded in every HTML
# page. Disable this option to support browsers that do not have Javascript,
# page. Disable this option to support browsers that do not have JavaScript,
# like the Qt help browser.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
@ -1564,6 +1560,17 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png The default and svg Looks nicer but requires the
# pdf2svg tool.
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FORMULA_FORMAT = png
# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
@ -1584,8 +1591,14 @@ FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
FORMULA_MACROFILE =
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# https://www.mathjax.org) which uses client side Javascript for the rendering
# https://www.mathjax.org) which uses client side JavaScript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
# installed or if you want to formulas look prettier in the HTML output. When
# enabled you may also need to install MathJax separately and configure the path
@ -1613,7 +1626,7 @@ MATHJAX_FORMAT = HTML-CSS
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
@ -1655,7 +1668,7 @@ MATHJAX_CODEFILE =
SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
# implemented using a web server instead of a web client using Javascript. There
# implemented using a web server instead of a web client using JavaScript. There
# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
# setting. When disabled, doxygen will generate a PHP script for searching and
# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing

View File

@ -43,6 +43,7 @@ struct D3d12maObjDeleter
typedef std::unique_ptr<D3D12MA::Allocation, D3d12maObjDeleter<D3D12MA::Allocation>> AllocationUniquePtr;
typedef std::unique_ptr<D3D12MA::Pool, D3d12maObjDeleter<D3D12MA::Pool>> PoolUniquePtr;
typedef std::unique_ptr<D3D12MA::VirtualBlock, D3d12maObjDeleter<D3D12MA::VirtualBlock>> VirtualBlockUniquePtr;
struct ResourceWithAllocation
{
@ -118,6 +119,127 @@ static bool ValidateDataZero(const void* ptr, const UINT64 sizeInBytes)
return true;
}
static void TestVirtualBlocks(const TestContext& ctx)
{
wprintf(L"Test virtual blocks\n");
using namespace D3D12MA;
const UINT64 blockSize = 16 * MEGABYTE;
const UINT64 alignment = 256;
// # Create block 16 MB
VirtualBlockUniquePtr block;
VirtualBlock* blockPtr = nullptr;
VIRTUAL_BLOCK_DESC blockDesc = {};
blockDesc.pAllocationCallbacks = ctx.allocationCallbacks;
blockDesc.Size = blockSize;
CHECK_HR( CreateVirtualBlock(&blockDesc, &blockPtr) );
CHECK_BOOL( blockPtr );
block.reset(blockPtr);
// # Allocate 8 MB
VIRTUAL_ALLOCATION_DESC allocDesc = {};
allocDesc.Alignment = alignment;
allocDesc.pUserData = (void*)(uintptr_t)1;
allocDesc.Size = 8 * MEGABYTE;
UINT64 alloc0Offset;
CHECK_HR( block->Allocate(&allocDesc, &alloc0Offset) );
CHECK_BOOL( alloc0Offset < blockSize );
// # Validate the allocation
VIRTUAL_ALLOCATION_INFO allocInfo = {};
block->GetAllocationInfo(alloc0Offset, &allocInfo);
CHECK_BOOL( allocInfo.size == allocDesc.Size );
CHECK_BOOL( allocInfo.pUserData == allocDesc.pUserData );
// # Check SetUserData
block->SetAllocationUserData(alloc0Offset, (void*)(uintptr_t)2);
block->GetAllocationInfo(alloc0Offset, &allocInfo);
CHECK_BOOL( allocInfo.pUserData == (void*)(uintptr_t)2 );
// # Allocate 4 MB
allocDesc.Size = 4 * MEGABYTE;
allocDesc.Alignment = alignment;
UINT64 alloc1Offset;
CHECK_HR( block->Allocate(&allocDesc, &alloc1Offset) );
CHECK_BOOL( alloc1Offset < blockSize );
CHECK_BOOL( alloc1Offset + 4 * MEGABYTE <= alloc0Offset || alloc0Offset + 8 * MEGABYTE <= alloc1Offset ); // Check if they don't overlap.
// # Allocate another 8 MB - it should fail
allocDesc.Size = 8 * MEGABYTE;
allocDesc.Alignment = alignment;
UINT64 alloc2Offset;
CHECK_BOOL( FAILED(block->Allocate(&allocDesc, &alloc2Offset)) );
CHECK_BOOL( alloc2Offset == UINT64_MAX );
// # Free the 4 MB block. Now allocation of 8 MB should succeed.
block->FreeAllocation(alloc1Offset);
CHECK_HR( block->Allocate(&allocDesc, &alloc2Offset) );
CHECK_BOOL( alloc2Offset < blockSize );
CHECK_BOOL( alloc2Offset + 4 * MEGABYTE <= alloc0Offset || alloc0Offset + 8 * MEGABYTE <= alloc2Offset ); // Check if they don't overlap.
// # Calculate statistics
StatInfo statInfo = {};
block->CalculateStats(&statInfo);
CHECK_BOOL(statInfo.AllocationCount == 2);
CHECK_BOOL(statInfo.BlockCount == 1);
CHECK_BOOL(statInfo.UsedBytes == blockSize);
CHECK_BOOL(statInfo.UnusedBytes + statInfo.UsedBytes == blockSize);
// # Generate JSON dump
WCHAR* json = nullptr;
block->BuildStatsString(&json);
{
std::wstring str(json);
CHECK_BOOL( str.find(L"\"UserData\": 1") != std::wstring::npos );
CHECK_BOOL( str.find(L"\"UserData\": 2") != std::wstring::npos );
}
block->FreeStatsString(json);
// # Free alloc0, leave alloc2 unfreed.
block->FreeAllocation(alloc0Offset);
// # Test alignment
{
constexpr size_t allocCount = 10;
UINT64 allocOffset[allocCount] = {};
for(size_t i = 0; i < allocCount; ++i)
{
const bool alignment0 = i == allocCount - 1;
allocDesc.Size = i * 3 + 15;
allocDesc.Alignment = alignment0 ? 0 : 8;
CHECK_HR(block->Allocate(&allocDesc, &allocOffset[i]));
if(!alignment0)
{
CHECK_BOOL(allocOffset[i] % allocDesc.Alignment == 0);
}
}
for(size_t i = allocCount; i--; )
{
block->FreeAllocation(allocOffset[i]);
}
}
// # Final cleanup
block->FreeAllocation(alloc2Offset);
//block->Clear();
}
static void TestFrameIndexAndJson(const TestContext& ctx)
{
const UINT64 bufSize = 32ull * 1024;
@ -1175,6 +1297,11 @@ static void TestMultithreading(const TestContext& ctx)
}
}
static void TestGroupVirtual(const TestContext& ctx)
{
TestVirtualBlocks(ctx);
}
static void TestGroupBasics(const TestContext& ctx)
{
TestFrameIndexAndJson(ctx);
@ -1202,6 +1329,7 @@ void Test(const TestContext& ctx)
return;
}
TestGroupVirtual(ctx);
TestGroupBasics(ctx);
wprintf(L"TESTS END\n");

View File

@ -26,6 +26,7 @@
struct TestContext
{
D3D12MA::ALLOCATION_CALLBACKS* allocationCallbacks;
ID3D12Device* device;
D3D12MA::Allocator* allocator;
D3D12MA::ALLOCATOR_FLAGS allocatorFlags;