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;