Add memory reservation feature - functions Allocator::SetDefaultHeapMinBytes, Pool::SetMinBytes
diff --git a/src/D3D12MemAlloc.cpp b/src/D3D12MemAlloc.cpp
index e648300..01175b1 100644
--- a/src/D3D12MemAlloc.cpp
+++ b/src/D3D12MemAlloc.cpp
@@ -191,6 +191,8 @@
         return false; \

     } } while(false)

 

+const UINT NEW_BLOCK_SIZE_SHIFT_MAX = 3;

+

 template<typename T>

 static inline T D3D12MA_MIN(const T& a, const T& b)

 {

@@ -2352,6 +2354,8 @@
         REFIID riidResource,

         void** ppvResource);

 

+    HRESULT SetMinBytes(UINT64 minBytes);

+

     void AddStats(StatInfo& outStats);

     void AddStats(Stats& outStats);

 

@@ -2365,6 +2369,7 @@
     const size_t m_MinBlockCount;

     const size_t m_MaxBlockCount;

     const bool m_ExplicitBlockSize;

+    UINT64 m_MinBytes;

     /* There can be at most one allocation that is completely empty - a

     hysteresis to avoid pessimistic case of alternating creation and destruction

     of a VkDeviceMemory. */

@@ -2374,6 +2379,7 @@
     Vector<NormalBlock*> m_Blocks;

     UINT m_NextBlockId;

 

+    UINT64 CalcSumBlockSize() const;

     UINT64 CalcMaxBlockSize() const;

 

     // Finds and removes given block from vector.

@@ -2485,6 +2491,11 @@
         REFIID riidResource,

         void** ppvResource);

 

+    HRESULT SetDefaultHeapMinBytes(

+        D3D12_HEAP_TYPE heapType,

+        D3D12_HEAP_FLAGS heapFlags,

+        UINT64 minBytes);

+

     // Unregisters allocation from the collection of dedicated allocations.

     // Allocation object must be deleted externally afterwards.

     void FreeCommittedMemory(Allocation* allocation);

@@ -2543,6 +2554,11 @@
     // Default pools.

     BlockVector* m_BlockVectors[DEFAULT_POOL_MAX_COUNT];

 

+    // # Used only when ResourceHeapTier = 1

+    UINT64 m_DefaultPoolTier1MinBytes[DEFAULT_POOL_MAX_COUNT]; // Default 0

+    UINT64 m_DefaultPoolHeapTypeMinBytes[HEAP_TYPE_COUNT]; // Default UINT64_MAX, meaning not set

+    D3D12MA_RW_MUTEX m_DefaultPoolMinBytesMutex;

+

     // Allocates and registers new committed resource with implicit heap, as dedicated allocation.

     // Creates and returns Allocation object.

     HRESULT AllocateCommittedResource(

@@ -2581,7 +2597,15 @@
     UINT CalcDefaultPoolCount() const;

     UINT CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc, const D3D12_RESOURCE_DESC& resourceDesc) const;

     // This one returns UINT32_MAX if nonstandard heap flags are used and index cannot be calculcated.

-    UINT CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc) const;

+    static UINT CalcDefaultPoolIndex(D3D12_HEAP_TYPE heapType, D3D12_HEAP_FLAGS heapFlags, bool supportsResourceHeapTier2);

+    UINT CalcDefaultPoolIndex(D3D12_HEAP_TYPE heapType, D3D12_HEAP_FLAGS heapFlags) const

+    {

+        return CalcDefaultPoolIndex(heapType, heapFlags, SupportsResourceHeapTier2());

+    }

+    UINT CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc) const

+    {

+        return CalcDefaultPoolIndex(allocDesc.HeapType, allocDesc.ExtraHeapFlags);

+    }

     void CalcDefaultPoolParams(D3D12_HEAP_TYPE& outHeapType, D3D12_HEAP_FLAGS& outHeapFlags, UINT index) const;

 

     // Registers Allocation object in m_pCommittedAllocations.

@@ -3277,6 +3301,7 @@
     m_MinBlockCount(minBlockCount),

     m_MaxBlockCount(maxBlockCount),

     m_ExplicitBlockSize(explicitBlockSize),

+    m_MinBytes(0),

     m_HasEmptyBlock(false),

     m_Blocks(hAllocator->GetAllocs()),

     m_NextBlockId(0)

@@ -3402,7 +3427,6 @@
         // Calculate optimal size for new block.

         UINT64 newBlockSize = m_PreferredBlockSize;

         UINT newBlockSizeShift = 0;

-        const UINT NEW_BLOCK_SIZE_SHIFT_MAX = 3;

 

         if(!m_ExplicitBlockSize)

         {

@@ -3492,12 +3516,15 @@
         pBlock->m_pMetadata->Free(hAllocation);

         D3D12MA_HEAVY_ASSERT(pBlock->Validate());

 

-        const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount;

+        const size_t blockCount = m_Blocks.size();

+        const UINT64 sumBlockSize = CalcSumBlockSize();

         // pBlock became empty after this deallocation.

         if(pBlock->m_pMetadata->IsEmpty())

         {

             // Already has empty Allocation. We don't want to have two, so delete this one.

-            if((m_HasEmptyBlock || budgetExceeded) && canDeleteBlock)

+            if((m_HasEmptyBlock || budgetExceeded) &&

+                blockCount > m_MinBlockCount &&

+                sumBlockSize - pBlock->m_pMetadata->GetSize() >= m_MinBytes)

             {

                 pBlockToDelete = pBlock;

                 Remove(pBlock);

@@ -3510,10 +3537,11 @@
         }

         // pBlock didn't become empty, but we have another empty block - find and free that one.

         // (This is optional, heuristics.)

-        else if(m_HasEmptyBlock && canDeleteBlock)

+        else if(m_HasEmptyBlock && blockCount > m_MinBlockCount)

         {

             NormalBlock* pLastBlock = m_Blocks.back();

-            if(pLastBlock->m_pMetadata->IsEmpty() && m_Blocks.size() > m_MinBlockCount)

+            if(pLastBlock->m_pMetadata->IsEmpty() &&

+                sumBlockSize - pLastBlock->m_pMetadata->GetSize() >= m_MinBytes)

             {

                 pBlockToDelete = pLastBlock;

                 m_Blocks.pop_back();

@@ -3572,6 +3600,16 @@
     return hr;

 }

 

+UINT64 BlockVector::CalcSumBlockSize() const

+{

+    UINT64 result = 0;

+    for(size_t i = m_Blocks.size(); i--; )

+    {

+        result += m_Blocks[i]->m_pMetadata->GetSize();

+    }

+    return result;

+}

+

 UINT64 BlockVector::CalcMaxBlockSize() const

 {

     UINT64 result = 0;

@@ -3588,7 +3626,7 @@
 

 void BlockVector::Remove(NormalBlock* pBlock)

 {

-    for(UINT blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex)

+    for(size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex)

     {

         if(m_Blocks[blockIndex] == pBlock)

         {

@@ -3666,6 +3704,89 @@
     return hr;

 }

 

+HRESULT BlockVector::SetMinBytes(UINT64 minBytes)

+{

+    MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex());

+

+    if(minBytes == m_MinBytes)

+    {

+        return S_OK;

+    }

+

+    HRESULT hr = S_OK;

+    UINT64 sumBlockSize = CalcSumBlockSize();

+    size_t blockCount = m_Blocks.size();

+

+    // New minBytes is smaller - may be able to free some blocks.

+    if(minBytes < m_MinBytes)

+    {

+        m_HasEmptyBlock = false; // Will recalculate this value from scratch.

+        for(size_t blockIndex = blockCount; blockIndex--; )

+        {

+            NormalBlock* const block = m_Blocks[blockIndex];

+            const UINT64 size = block->m_pMetadata->GetSize();

+            const bool isEmpty = block->m_pMetadata->IsEmpty();

+            if(isEmpty &&

+                sumBlockSize - size >= minBytes &&

+                blockCount - 1 >= m_MinBlockCount)

+            {

+                D3D12MA_DELETE(m_hAllocator->GetAllocs(), block);

+                m_Blocks.remove(blockIndex);

+                sumBlockSize -= size;

+                --blockCount;

+            }

+            else

+            {

+                if(isEmpty)

+                {

+                    m_HasEmptyBlock = true;

+                }

+            }

+        }

+    }

+    // New minBytes is larger - may need to allocate some blocks.

+    else

+    {

+        const UINT64 minBlockSize = m_PreferredBlockSize >> NEW_BLOCK_SIZE_SHIFT_MAX;

+        while(SUCCEEDED(hr) && sumBlockSize < minBytes)

+        {

+            if(blockCount < m_MaxBlockCount)

+            {

+                UINT64 newBlockSize = m_PreferredBlockSize;

+                if(!m_ExplicitBlockSize)

+                {

+                    if(sumBlockSize + newBlockSize > minBytes)

+                    {

+                        newBlockSize = minBytes - sumBlockSize;

+                    }

+                    // Next one would be the last block to create and its size would be smaller than

+                    // the smallest block size we want to use here, so make this one smaller.

+                    else if(blockCount + 1 < m_MaxBlockCount &&

+                        sumBlockSize + newBlockSize + minBlockSize > minBytes)

+                    {

+                        newBlockSize -= minBlockSize + sumBlockSize + m_PreferredBlockSize - minBytes;

+                    }

+                }

+

+                hr = CreateBlock(newBlockSize, NULL);

+                if(SUCCEEDED(hr))

+                {

+                    m_HasEmptyBlock = true;

+                    sumBlockSize += newBlockSize;

+                    ++blockCount;

+                }

+            }

+            else

+            {

+                hr = E_INVALIDARG;

+            }

+        }

+    }

+

+    m_MinBytes = minBytes;

+    return hr;

+}

+

 void BlockVector::AddStats(StatInfo& outStats)

 {

     MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex());

@@ -3735,6 +3856,8 @@
     const POOL_DESC& GetDesc() const { return m_Desc; }

     BlockVector* GetBlockVector() { return m_BlockVector; }

     

+    HRESULT SetMinBytes(UINT64 minBytes) { return m_BlockVector->SetMinBytes(minBytes); }

+    

     void CalculateStats(StatInfo& outStats);

 

     void SetName(LPCWSTR Name);

@@ -3768,10 +3891,12 @@
     }

 #endif

 

+    UINT maxBlockCount = desc.MaxBlockCount != 0 ? desc.MaxBlockCount : UINT_MAX;

+

     m_BlockVector = D3D12MA_NEW(allocator->GetAllocs(), BlockVector)(

         allocator, desc.HeapType, heapFlags,

         preferredBlockSize,

-        desc.MinBlockCount, desc.MaxBlockCount,

+        desc.MinBlockCount, maxBlockCount,

         explicitBlockSize);

 }

 

@@ -3839,6 +3964,11 @@
     return m_Pimpl->GetDesc();

 }

 

+HRESULT Pool::SetMinBytes(UINT64 minBytes)

+{

+    return m_Pimpl->SetMinBytes(minBytes);

+}

+

 void Pool::CalculateStats(StatInfo* pStats)

 {

     D3D12MA_ASSERT(pStats);

@@ -3892,6 +4022,12 @@
     ZeroMemory(m_pCommittedAllocations, sizeof(m_pCommittedAllocations));

     ZeroMemory(m_pPools, sizeof(m_pPools));

     ZeroMemory(m_BlockVectors, sizeof(m_BlockVectors));

+    ZeroMemory(m_DefaultPoolTier1MinBytes, sizeof(m_DefaultPoolTier1MinBytes));

+

+    for(UINT i = 0; i < HEAP_TYPE_COUNT; ++i)

+    {

+        m_DefaultPoolHeapTypeMinBytes[i] = UINT64_MAX;

+    }

 

     for(UINT heapTypeIndex = 0; heapTypeIndex < HEAP_TYPE_COUNT; ++heapTypeIndex)

     {

@@ -4233,6 +4369,71 @@
         ppvResource);

 }

 

+HRESULT AllocatorPimpl::SetDefaultHeapMinBytes(

+    D3D12_HEAP_TYPE heapType,

+    D3D12_HEAP_FLAGS heapFlags,

+    UINT64 minBytes)

+{

+    if(!IsHeapTypeValid(heapType))

+    {

+        D3D12MA_ASSERT(0 && "Allocator::SetDefaultHeapMinBytes: Invalid heapType passed.");

+        return E_INVALIDARG;

+    }

+

+    if(SupportsResourceHeapTier2())

+    {

+        if(heapFlags != D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES &&

+            heapFlags != D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS &&

+            heapFlags != D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES &&

+            heapFlags != D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES)

+        {

+            D3D12MA_ASSERT(0 && "Allocator::SetDefaultHeapMinBytes: Invalid heapFlags passed.");

+            return E_INVALIDARG;

+        }

+

+        UINT64 newMinBytes = UINT64_MAX;

+

+        {

+            MutexLockWrite lock(m_DefaultPoolMinBytesMutex, m_UseMutex);

+

+            if(heapFlags == D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES)

+            {

+                m_DefaultPoolHeapTypeMinBytes[HeapTypeToIndex(heapType)] = minBytes;

+                newMinBytes = minBytes;

+            }

+            else

+            {

+                const UINT defaultPoolTier1Index = CalcDefaultPoolIndex(heapType, heapFlags, false);

+                m_DefaultPoolTier1MinBytes[defaultPoolTier1Index] = minBytes;

+

+                newMinBytes = m_DefaultPoolHeapTypeMinBytes[HeapTypeToIndex(heapType)];

+                if(newMinBytes == UINT64_MAX)

+                {

+                    newMinBytes = m_DefaultPoolTier1MinBytes[CalcDefaultPoolIndex(heapType, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS, false)] +

+                        m_DefaultPoolTier1MinBytes[CalcDefaultPoolIndex(heapType, D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES, false)] +

+                        m_DefaultPoolTier1MinBytes[CalcDefaultPoolIndex(heapType, D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES, false)];

+                }

+            }

+        }

+

+        const UINT defaultPoolIndex = CalcDefaultPoolIndex(heapType, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES);

+        return m_BlockVectors[defaultPoolIndex]->SetMinBytes(newMinBytes);

+    }

+    else

+    {

+        if(heapFlags != D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS &&

+            heapFlags != D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES &&

+            heapFlags != D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES)

+        {

+            D3D12MA_ASSERT(0 && "Allocator::SetDefaultHeapMinBytes: Invalid heapFlags passed.");

+            return E_INVALIDARG;

+        }

+        

+        const UINT defaultPoolIndex = CalcDefaultPoolIndex(heapType, heapFlags);

+        return m_BlockVectors[defaultPoolIndex]->SetMinBytes(minBytes);

+    }

+}

+

 bool AllocatorPimpl::PrefersCommittedAllocation(const D3D12_RESOURCE_DESC& resourceDesc)

 {

     // Intentional. It may change in the future.

@@ -4402,16 +4603,16 @@
     return poolIndex;

 }

 

-UINT AllocatorPimpl::CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc) const

+UINT AllocatorPimpl::CalcDefaultPoolIndex(D3D12_HEAP_TYPE heapType, D3D12_HEAP_FLAGS heapFlags, bool supportsResourceHeapTier2)

 {

-    const D3D12_HEAP_FLAGS extraHeapFlags = allocDesc.ExtraHeapFlags & ~GetExtraHeapFlagsToIgnore();

+    const D3D12_HEAP_FLAGS extraHeapFlags = heapFlags & ~GetExtraHeapFlagsToIgnore();

     if(extraHeapFlags != 0)

     {

         return UINT32_MAX;

     }

 

     UINT poolIndex = UINT_MAX;

-    switch(allocDesc.HeapType)

+    switch(heapType)

     {

     case D3D12_HEAP_TYPE_DEFAULT:  poolIndex = 0; break;

     case D3D12_HEAP_TYPE_UPLOAD:   poolIndex = 1; break;

@@ -4419,13 +4620,13 @@
     default: D3D12MA_ASSERT(0);

     }

 

-    if(!SupportsResourceHeapTier2())

+    if(!supportsResourceHeapTier2)

     {

         poolIndex *= 3;

 

-        const bool allowBuffers = (allocDesc.ExtraHeapFlags & D3D12_HEAP_FLAG_DENY_BUFFERS) == 0;

-        const bool allowRtDsTextures = (allocDesc.ExtraHeapFlags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) == 0;

-        const bool allowNonRtDsTextures = (allocDesc.ExtraHeapFlags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) == 0;

+        const bool allowBuffers = (heapFlags & D3D12_HEAP_FLAG_DENY_BUFFERS) == 0;

+        const bool allowRtDsTextures = (heapFlags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) == 0;

+        const bool allowNonRtDsTextures = (heapFlags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) == 0;

 

         const uint8_t allowedGroupCount = (allowBuffers ? 1 : 0) + (allowRtDsTextures ? 1 : 0) + (allowNonRtDsTextures ? 1 : 0);

         if(allowedGroupCount != 1)

@@ -5263,7 +5464,7 @@
 {

     if(!pPoolDesc || !ppPool ||

         !IsHeapTypeValid(pPoolDesc->HeapType) ||

-        pPoolDesc->MinBlockCount > pPoolDesc->MaxBlockCount)

+        (pPoolDesc->MaxBlockCount > 0 && pPoolDesc->MaxBlockCount < pPoolDesc->MinBlockCount))

     {

         D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreatePool.");

         return E_INVALIDARG;

@@ -5288,6 +5489,15 @@
     return hr;

 }

 

+HRESULT Allocator::SetDefaultHeapMinBytes(

+    D3D12_HEAP_TYPE heapType,

+    D3D12_HEAP_FLAGS heapFlags,

+    UINT64 minBytes)

+{

+    D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK

+    return m_Pimpl->SetDefaultHeapMinBytes(heapType, heapFlags, minBytes);

+}

+

 void Allocator::SetCurrentFrameIndex(UINT frameIndex)

 {

     D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK

diff --git a/src/D3D12MemAlloc.h b/src/D3D12MemAlloc.h
index 61f2f72..87dde11 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-03-24)

+<b>Version 2.0.0-development</b> (2020-04-07)

 

 Copyright (c) 2019-2020 Advanced Micro Devices, Inc. All rights reserved. \n

 License: MIT

@@ -38,6 +38,7 @@
         - [Project setup](@ref quick_start_project_setup)

         - [Creating resources](@ref quick_start_creating_resources)

         - [Mapping memory](@ref quick_start_mapping_memory)

+    - \subpage reserving_memory

 - \subpage configuration

   - [Custom CPU memory allocator](@ref custom_memory_allocator)

 - \subpage general_considerations

@@ -235,6 +236,43 @@
 \endcode

 

 

+\page reserving_memory Reserving minimum amount of memory

+

+The library automatically allocates and frees memory heaps.

+It also applies some hysteresis so that it doesn't allocate and free entire heap

+when you repeatedly create and release a single resource.

+However, if you want to make sure certain number of bytes is always allocated as heaps in a specific pool,

+you can use functions designed for this purpose:

+

+- For default heaps use D3D12MA::Allocator::SetDefaultHeapMinBytes.

+- For custom heaps use D3D12MA::Pool::SetMinBytes.

+

+Default is 0. You can change this parameter any time.

+Setting it to higher value may cause new heaps to be allocated.

+If this allocation fails, the function returns appropriate error code, but the parameter remains set to the new value.

+Setting it to lower value may cause some empty heaps to be released.

+

+You can always call D3D12MA::Allocator::SetDefaultHeapMinBytes for 3 sets of heap flags separately:

+`D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`, `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES`.

+When ResourceHeapTier = 2, so that all types of resourced are kept together,

+these 3 values as simply summed up to calculate minimum amount of bytes for default pool with certain heap type.

+Alternatively, when ResourceHeapTier = 2, you can call this function with

+`D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES` = 0. This will set a single value for the default pool and

+will override the sum of those three.

+

+Reservation of minimum number of bytes interacts correctly with

+D3D12MA::POOL_DESC::MinBlockCount and D3D12MA::POOL_DESC::MaxBlockCount.

+For example, free blocks (heaps) of a custom pool will be released only when

+their number doesn't fall below `MinBlockCount` and their sum size doesn't fall below `MinBytes`.

+

+Some restrictions apply:

+

+- Setting `MinBytes` doesn't interact with memory budget. The allocator tries

+  to create additional heaps when necessary without checking if they will exceed the budget.

+- Resources created as committed don't count into the number of bytes compared with `MinBytes` set.

+  Only placed resources are considered.

+

+

 \page configuration Configuration

 

 Please check file `D3D12MemAlloc.cpp` lines between "Configuration Begin" and

@@ -695,12 +733,19 @@
     released before calling this function!

     */

     void Release();

+    

     /** \brief Returns copy of parameters of the pool.

 

     These are the same parameters as passed to D3D12MA::Allocator::CreatePool.

     */

     POOL_DESC GetDesc() const;

 

+    /** \brief Sets the minimum number of bytes that should always be allocated (reserved) in this pool.

+

+    See also: \subpage reserving_memory.

+    */

+    HRESULT SetMinBytes(UINT64 minBytes);

+

     /** \brief Retrieves statistics from the current state of this pool.

     */

     void CalculateStats(StatInfo* pStats);

@@ -984,6 +1029,20 @@
         const POOL_DESC* pPoolDesc,

         Pool** ppPool);

 

+    /** \brief Sets the minimum number of bytes that should always be allocated (reserved) in a specific default pool.

+

+    \param heapType Must be one of: `D3D12_HEAP_TYPE_DEFAULT`, `D3D12_HEAP_TYPE_UPLOAD`, `D3D12_HEAP_TYPE_READBACK`.

+    \param heapFlags Must be one of: `D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`,

+        `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES`. If ResourceHeapTier = 2, it can also be `D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES`.

+    \param minBytes Minimum number of bytes to keep allocated.

+

+    See also: \subpage reserving_memory.

+    */

+    HRESULT SetDefaultHeapMinBytes(

+        D3D12_HEAP_TYPE heapType,

+        D3D12_HEAP_FLAGS heapFlags,

+        UINT64 minBytes);

+

     /** \brief Sets the index of the current frame.

 

     This function is used to set the frame index in the allocator when a new game frame begins.

diff --git a/src/Tests.cpp b/src/Tests.cpp
index 662e059..6d19d62 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -434,13 +434,29 @@
     CHECK_BOOL( poolStats.BlockCount == 1 );

     CHECK_BOOL( poolStats.AllocationCount == 0 );

     CHECK_BOOL( poolStats.UsedBytes == 0 );

-    CHECK_BOOL( poolStats.UnusedBytes == poolDesc.BlockSize );

+    CHECK_BOOL( poolStats.UnusedBytes == poolStats.BlockCount * poolDesc.BlockSize );

 

     // # SetName and GetName

     static const wchar_t* NAME = L"Custom pool name 1";

     pool->SetName(NAME);

     CHECK_BOOL( wcscmp(pool->GetName(), NAME) == 0 );

 

+    // # SetMinBytes

+

+    CHECK_HR( pool->SetMinBytes(15 * MEGABYTE) );

+    pool->CalculateStats(&poolStats);

+    CHECK_BOOL( poolStats.BlockCount == 2 );

+    CHECK_BOOL( poolStats.AllocationCount == 0 );

+    CHECK_BOOL( poolStats.UsedBytes == 0 );

+    CHECK_BOOL( poolStats.UnusedBytes == poolStats.BlockCount * poolDesc.BlockSize );

+

+    CHECK_HR( pool->SetMinBytes(0) );

+    pool->CalculateStats(&poolStats);

+    CHECK_BOOL( poolStats.BlockCount == 1 );

+    CHECK_BOOL( poolStats.AllocationCount == 0 );

+    CHECK_BOOL( poolStats.UsedBytes == 0 );

+    CHECK_BOOL( poolStats.UnusedBytes == poolStats.BlockCount * poolDesc.BlockSize );

+

     // # Create buffers 2x 5 MB

 

     D3D12MA::ALLOCATION_DESC allocDesc = {};

@@ -544,6 +560,26 @@
         IID_PPV_ARGS(&res)) );

 }

 

+static void TestDefaultPoolMinBytes(const TestContext& ctx)

+{

+    D3D12MA::Stats stats;

+    ctx.allocator->CalculateStats(&stats);

+    const UINT64 gpuAllocatedBefore = stats.HeapType[0].UsedBytes + stats.HeapType[0].UnusedBytes;

+

+    const UINT64 gpuAllocatedMin = gpuAllocatedBefore * 105 / 100;

+    CHECK_HR( ctx.allocator->SetDefaultHeapMinBytes(D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,            CeilDiv(gpuAllocatedMin, 3ull)) );

+    CHECK_HR( ctx.allocator->SetDefaultHeapMinBytes(D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES, CeilDiv(gpuAllocatedMin, 3ull)) );

+    CHECK_HR( ctx.allocator->SetDefaultHeapMinBytes(D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,     CeilDiv(gpuAllocatedMin, 3ull)) );

+

+    ctx.allocator->CalculateStats(&stats);

+    const UINT64 gpuAllocatedAfter = stats.HeapType[0].UsedBytes + stats.HeapType[0].UnusedBytes;

+    CHECK_BOOL(gpuAllocatedAfter >= gpuAllocatedMin);

+

+    CHECK_HR( ctx.allocator->SetDefaultHeapMinBytes(D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,            0) );

+    CHECK_HR( ctx.allocator->SetDefaultHeapMinBytes(D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES, 0) );

+    CHECK_HR( ctx.allocator->SetDefaultHeapMinBytes(D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,     0) );

+}

+

 static void TestAliasingMemory(const TestContext& ctx)

 {

     wprintf(L"Test aliasing memory\n");

@@ -1148,6 +1184,7 @@
     TestCustomHeapFlags(ctx);

     TestPlacedResources(ctx);

     TestCustomPools(ctx);

+    TestDefaultPoolMinBytes(ctx);

     TestAliasingMemory(ctx);

     TestMapping(ctx);

     TestStats(ctx);