Add virtual allocator - struct VIRTUAL_BLOCK_DESC, VIRTUAL_ALLOCATION_DESC, VIRTUAL_ALLOCATION_INFO, class VirtualBlock, function CreateVirtualBlock.
Also updated Doxyfile to Doxygen 1.8.18.
diff --git a/src/D3D12MemAlloc.cpp b/src/D3D12MemAlloc.cpp
index c4d37b1..7da729a 100644
--- a/src/D3D12MemAlloc.cpp
+++ b/src/D3D12MemAlloc.cpp
@@ -157,11 +157,11 @@
}
}
-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
@@ -1996,7 +1996,7 @@
{
UINT64 offset;
UINT64 size;
- Allocation* allocation;
+ void* userData;
SuballocationType type;
};
@@ -2106,19 +2106,22 @@
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.
@@ -2131,11 +2134,14 @@
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;
@@ -2145,6 +2151,7 @@
private:
UINT64 m_Size;
+ bool m_IsVirtual;
const ALLOCATION_CALLBACKS* m_pAllocationCallbacks;
D3D12MA_CLASS_NO_COPY(BlockMetadata);
@@ -2153,7 +2160,7 @@
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);
@@ -2163,6 +2170,8 @@
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,
@@ -2171,10 +2180,12 @@
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;
@@ -2617,8 +2628,9 @@
////////////////////////////////////////////////////////////////////////////////
// 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);
@@ -2627,8 +2639,8 @@
////////////////////////////////////////////////////////////////////////////////
// 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),
@@ -2653,7 +2665,7 @@
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);
@@ -2691,7 +2703,11 @@
// 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)
{
@@ -2707,8 +2723,11 @@
}
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);
@@ -2761,6 +2780,23 @@
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,
@@ -2810,7 +2846,7 @@
void BlockMetadata_Generic::Alloc(
const AllocationRequest& request,
UINT64 allocSize,
- Allocation* allocation)
+ void* userData)
{
D3D12MA_ASSERT(request.item != m_Suballocations.end());
Suballocation& suballoc = *request.item;
@@ -2829,7 +2865,7 @@
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)
@@ -2872,23 +2908,6 @@
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();
@@ -2905,6 +2924,22 @@
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;
@@ -2997,7 +3032,7 @@
// 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;
@@ -3102,6 +3137,22 @@
//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;
@@ -3164,9 +3215,21 @@
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);
}
@@ -3212,7 +3275,7 @@
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;
@@ -3501,7 +3564,7 @@
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();
@@ -5481,14 +5544,14 @@
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)
{
@@ -5498,6 +5561,169 @@
}
////////////////////////////////////////////////////////////////////////////////
+// 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
HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator)
@@ -5512,7 +5738,7 @@
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);
@@ -5524,4 +5750,21 @@
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
diff --git a/src/D3D12MemAlloc.h b/src/D3D12MemAlloc.h
index b472a06..151eb54 100644
--- a/src/D3D12MemAlloc.h
+++ b/src/D3D12MemAlloc.h
@@ -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 NormalBlock;
class BlockVector;
class JsonWriter;
+class VirtualBlockPimpl;
/// \endcond
class Pool;
@@ -1074,10 +1075,10 @@
/** @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 @@
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
diff --git a/src/D3D12Sample.cpp b/src/D3D12Sample.cpp
index a9c33fc..4806467 100644
--- a/src/D3D12Sample.cpp
+++ b/src/D3D12Sample.cpp
@@ -49,6 +49,7 @@
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 @@
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 @@
try
{
TestContext ctx = {};
+ ctx.allocationCallbacks = &g_AllocationCallbacks;
ctx.device = g_Device;
ctx.allocator = g_Allocator;
ctx.allocatorFlags = g_AllocatorFlags;
diff --git a/src/Doxyfile b/src/Doxyfile
index 1bd8fae..1bd8947 100644
--- a/src/Doxyfile
+++ b/src/Doxyfile
@@ -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 @@
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 @@
# 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_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 @@
# 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 @@
# 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 @@
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_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 @@
# 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 @@
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
diff --git a/src/Tests.cpp b/src/Tests.cpp
index 25b2517..3606fea 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -43,6 +43,7 @@
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 @@
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 TestGroupVirtual(const TestContext& ctx)
+{
+ TestVirtualBlocks(ctx);
+}
+
static void TestGroupBasics(const TestContext& ctx)
{
TestFrameIndexAndJson(ctx);
@@ -1202,6 +1329,7 @@
return;
}
+ TestGroupVirtual(ctx);
TestGroupBasics(ctx);
wprintf(L"TESTS END\n");
diff --git a/src/Tests.h b/src/Tests.h
index 8107035..88938b4 100644
--- a/src/Tests.h
+++ b/src/Tests.h
@@ -26,6 +26,7 @@
struct TestContext
{
+ D3D12MA::ALLOCATION_CALLBACKS* allocationCallbacks;
ID3D12Device* device;
D3D12MA::Allocator* allocator;
D3D12MA::ALLOCATOR_FLAGS allocatorFlags;