Added support for multiple Vulkan memory blocks in custom pools with VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT. Works with free-at-once and stack, doesn't work with double stack or ring buffer.

Added new structure members VmaPoolStats::blockCount.
diff --git a/src/Tests.cpp b/src/Tests.cpp
index 36e86bc..842fba2 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -1839,14 +1839,139 @@
         bufInfo.clear();

     }

 

-    // Try to create pool with maxBlockCount higher than 1. It should fail.

-    {

-        VmaPoolCreateInfo altPoolCreateInfo = poolCreateInfo;

-        altPoolCreateInfo.maxBlockCount = 2;

+    vmaDestroyPool(g_hAllocator, pool);

+}

 

-        VmaPool altPool = nullptr;

-        res = vmaCreatePool(g_hAllocator, &altPoolCreateInfo, &altPool);

-        assert(res != VK_SUCCESS);

+static void TestLinearAllocatorMultiBlock()

+{

+    wprintf(L"Test linear allocator multi block\n");

+

+    RandomNumberGenerator rand{345673};

+

+    VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };

+    sampleBufCreateInfo.size = 1024 * 1024;

+    sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;

+

+    VmaAllocationCreateInfo sampleAllocCreateInfo = {};

+    sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;

+

+    VmaPoolCreateInfo poolCreateInfo = {};

+    poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;

+    VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);

+    assert(res == VK_SUCCESS);

+

+    VmaPool pool = nullptr;

+    res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);

+    assert(res == VK_SUCCESS);

+

+    VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;

+

+    VmaAllocationCreateInfo allocCreateInfo = {};

+    allocCreateInfo.pool = pool;

+    

+    std::vector<BufferInfo> bufInfo;

+    VmaAllocationInfo allocInfo;

+

+    // Test one-time free.

+    {

+        // Allocate buffers until we move to a second block.

+        VkDeviceMemory lastMem = VK_NULL_HANDLE;

+        for(uint32_t i = 0; ; ++i)

+        {

+            BufferInfo newBufInfo;

+            res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,

+                &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);

+            assert(res == VK_SUCCESS);

+            bufInfo.push_back(newBufInfo);

+            if(lastMem && allocInfo.deviceMemory != lastMem)

+            {

+                break;

+            }

+            lastMem = allocInfo.deviceMemory;

+        }

+

+        assert(bufInfo.size() > 2);

+

+        // Make sure that pool has now two blocks.

+        VmaPoolStats poolStats = {};

+        vmaGetPoolStats(g_hAllocator, pool, &poolStats);

+        assert(poolStats.blockCount == 2);

+

+        // Destroy all the buffers in random order.

+        while(!bufInfo.empty())

+        {

+            const size_t indexToDestroy = rand.Generate() % bufInfo.size();

+            const BufferInfo& currBufInfo = bufInfo[indexToDestroy];

+            vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);

+            bufInfo.erase(bufInfo.begin() + indexToDestroy);

+        }

+

+        // Make sure that pool has now at most one block.

+        vmaGetPoolStats(g_hAllocator, pool, &poolStats);

+        assert(poolStats.blockCount <= 1);

+    }

+

+    // Test stack.

+    {

+        // Allocate buffers until we move to a second block.

+        VkDeviceMemory lastMem = VK_NULL_HANDLE;

+        for(uint32_t i = 0; ; ++i)

+        {

+            BufferInfo newBufInfo;

+            res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,

+                &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);

+            assert(res == VK_SUCCESS);

+            bufInfo.push_back(newBufInfo);

+            if(lastMem && allocInfo.deviceMemory != lastMem)

+            {

+                break;

+            }

+            lastMem = allocInfo.deviceMemory;

+        }

+

+        assert(bufInfo.size() > 2);

+

+        // Add few more buffers.

+        for(uint32_t i = 0; i < 5; ++i)

+        {

+            BufferInfo newBufInfo;

+            res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,

+                &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);

+            assert(res == VK_SUCCESS);

+            bufInfo.push_back(newBufInfo);

+        }

+

+        // Make sure that pool has now two blocks.

+        VmaPoolStats poolStats = {};

+        vmaGetPoolStats(g_hAllocator, pool, &poolStats);

+        assert(poolStats.blockCount == 2);

+        

+        // Delete half of buffers, LIFO.

+        for(size_t i = 0, countToDelete = bufInfo.size() / 2; i < countToDelete; ++i)

+        {

+            const BufferInfo& currBufInfo = bufInfo.back();

+            vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);

+            bufInfo.pop_back();

+        }

+

+        // Add one more buffer.

+        BufferInfo newBufInfo;

+        res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,

+            &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);

+        assert(res == VK_SUCCESS);

+        bufInfo.push_back(newBufInfo);

+

+        // Make sure that pool has now one block.

+        vmaGetPoolStats(g_hAllocator, pool, &poolStats);

+        assert(poolStats.blockCount == 1);

+

+        // Delete all the remaining buffers, LIFO.

+        while(!bufInfo.empty())

+        {

+            const BufferInfo& currBufInfo = bufInfo.back();

+            vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);

+            bufInfo.pop_back();

+        }

     }

 

     vmaDestroyPool(g_hAllocator, pool);

@@ -3841,6 +3966,16 @@
 {

     wprintf(L"TESTING:\n");

 

+    if(false)

+    {

+        // # Temporarily insert custom tests here

+TestLinearAllocator();

+ManuallyTestLinearAllocator();

+TestLinearAllocatorMultiBlock();

+BenchmarkLinearAllocator();

+        return;

+    }

+

     // # Simple tests

 

     TestBasics();

@@ -3857,6 +3992,7 @@
     TestMappingMultithreaded();

     TestLinearAllocator();

     ManuallyTestLinearAllocator();

+    TestLinearAllocatorMultiBlock();

     BenchmarkLinearAllocator();

     TestDefragmentationSimple();

     TestDefragmentationFull();

diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h
index 2e5f400..7dd8ff2 100644
--- a/src/vk_mem_alloc.h
+++ b/src/vk_mem_alloc.h
@@ -592,9 +592,6 @@
 With this one flag, you can create a custom pool that can be used in many ways:

 free-at-once, stack, double stack, and ring buffer. See below for details.

 

-Pools with linear algorithm must have only one memory block -

-VmaPoolCreateInfo::maxBlockCount must be 1.

-

 \subsection linear_algorithm_free_at_once Free-at-once

 

 In a pool that uses linear algorithm, you still need to free all the allocations

@@ -607,6 +604,9 @@
 

 ![Free-at-once](../gfx/Linear_allocator_3_free_at_once.png)

 

+This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount

+value that allows multiple memory blocks.

+

 \subsection linear_algorithm_stack Stack

 

 When you free an allocation that was created last, its space can be reused.

@@ -615,6 +615,9 @@
 

 ![Stack](../gfx/Linear_allocator_4_stack.png)

 

+This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount

+value that allows multiple memory blocks.

+

 \subsection linear_algorithm_double_stack Double stack

 

 The space reserved by a custom pool with linear algorithm may be used by two

@@ -626,12 +629,15 @@
 To make allocation from upper stack, add flag #VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT

 to VmaAllocationCreateInfo::flags.

 

+![Double stack](../gfx/Linear_allocator_7_double_stack.png)

+

+Double stack is available only in pools with one memory block -

+VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.

+

 When the two stacks' ends meet so there is not enough space between them for a

 new allocation, such allocation fails with usual

 `VK_ERROR_OUT_OF_DEVICE_MEMORY` error.

 

-![Double stack](../gfx/Linear_allocator_7_double_stack.png)

-

 \subsection linear_algorithm_ring_buffer Ring buffer

 

 When you free some allocations from the beginning and there is not enough free space

@@ -649,6 +655,9 @@
 

 ![Ring buffer with lost allocations](../gfx/Linear_allocator_6_ring_buffer_lost.png)

 

+Ring buffer is available only in pools with one memory block -

+VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.

+

 

 \page defragmentation Defragmentation

 

@@ -1968,7 +1977,6 @@
     /** \brief Maximum number of blocks that can be allocated in this pool. Optional.

 

     Set to 0 to use default, which is `SIZE_MAX`, which means no limit.

-    When #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT is used, default is 1.

     

     Set to same value as VmaPoolCreateInfo::minBlockCount to have fixed amount of memory allocated

     throughout whole lifetime of this pool.

@@ -2005,13 +2013,16 @@
     /** \brief Number of continuous memory ranges in the pool not used by any #VmaAllocation.

     */

     size_t unusedRangeCount;

-    /** \brief Size of the largest continuous free memory region.

+    /** \brief Size of the largest continuous free memory region available for new allocation.

 

     Making a new allocation of that size is not guaranteed to succeed because of

     possible additional margin required to respect alignment and buffer/image

     granularity.

     */

     VkDeviceSize unusedRangeSizeMax;

+    /** \brief Number of `VkDeviceMemory` blocks allocated for this pool.

+    */

+    size_t blockCount;

 } VmaPoolStats;

 

 /** \brief Allocates Vulkan device memory and creates #VmaPool object.

@@ -4506,6 +4517,7 @@
     virtual bool IsEmpty() const = 0;

 

     virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const = 0;

+    // Shouldn't modify blockCount.

     virtual void AddPoolStats(VmaPoolStats& inoutStats) const = 0;

 

 #if VMA_STATS_STRING_ENABLED

@@ -5014,6 +5026,18 @@
     // after this call.

     void IncrementallySortBlocks();

 

+    // To be used only without CAN_MAKE_OTHER_LOST flag.

+    VkResult AllocateFromBlock(

+        VmaDeviceMemoryBlock* pBlock,

+        VmaPool hCurrentPool,

+        uint32_t currentFrameIndex,

+        VkDeviceSize size,

+        VkDeviceSize alignment,

+        VmaAllocationCreateFlags allocFlags,

+        void* pUserData,

+        VmaSuballocationType suballocType,

+        VmaAllocation* pAllocation);

+

     VkResult CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex);

 };

 

@@ -9374,15 +9398,18 @@
 

 void VmaBlockVector::GetPoolStats(VmaPoolStats* pStats)

 {

+    VmaMutexLock lock(m_Mutex, m_hAllocator->m_UseMutex);

+

+    const size_t blockCount = m_Blocks.size();

+

     pStats->size = 0;

     pStats->unusedSize = 0;

     pStats->allocationCount = 0;

     pStats->unusedRangeCount = 0;

     pStats->unusedRangeSizeMax = 0;

+    pStats->blockCount = blockCount;

 

-    VmaMutexLock lock(m_Mutex, m_hAllocator->m_UseMutex);

-

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

+    for(uint32_t blockIndex = 0; blockIndex < blockCount; ++blockIndex)

     {

         const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];

         VMA_ASSERT(pBlock);

@@ -9411,15 +9438,23 @@
     VmaAllocation* pAllocation)

 {

     const bool isUpperAddress = (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0;

-    const bool canMakeOtherLost = (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) != 0;

+    bool canMakeOtherLost = (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) != 0;

     const bool mapped = (createInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0;

     const bool isUserDataString = (createInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0;

     const bool canCreateNewBlock =

         ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) &&

         (m_Blocks.size() < m_MaxBlockCount);

 

-    // Upper address can only be used with linear allocator.

-    if(isUpperAddress && !m_LinearAlgorithm)

+    // If linearAlgorithm is used, canMakeOtherLost is available only when used as ring buffer.

+    // Which in turn is available only when maxBlockCount = 1.

+    if(m_LinearAlgorithm && m_MaxBlockCount > 1)

+    {

+        canMakeOtherLost = false;

+    }

+

+    // Upper address can only be used with linear allocator and within single memory block.

+    if(isUpperAddress &&

+        (!m_LinearAlgorithm || m_MaxBlockCount > 1))

     {

         return VK_ERROR_FEATURE_NOT_PRESENT;

     }

@@ -9440,65 +9475,55 @@
     if(!canMakeOtherLost || canCreateNewBlock)

     {

         // 1. Search existing allocations. Try to allocate without making other allocations lost.

-        // Forward order in m_Blocks - prefer blocks with smallest amount of free space.

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

-        {

-            VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];

-            VMA_ASSERT(pCurrBlock);

-            VmaAllocationRequest currRequest = {};

-            if(pCurrBlock->m_pMetadata->CreateAllocationRequest(

-                currentFrameIndex,

-                m_FrameInUseCount,

-                m_BufferImageGranularity,

-                size,

-                alignment,

-                isUpperAddress,

-                suballocType,

-                false, // canMakeOtherLost

-                &currRequest))

-            {

-                // Allocate from pCurrBlock.

-                VMA_ASSERT(currRequest.itemsToMakeLostCount == 0);

+        VmaAllocationCreateFlags allocFlagsCopy = createInfo.flags;

+        allocFlagsCopy &= ~VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;

 

-                if(mapped)

-                {

-                    VkResult res = pCurrBlock->Map(m_hAllocator, 1, VMA_NULL);

-                    if(res != VK_SUCCESS)

-                    {

-                        return res;

-                    }

-                }

-            

-                // We no longer have an empty Allocation.

-                if(pCurrBlock->m_pMetadata->IsEmpty())

-                {

-                    m_HasEmptyBlock = false;

-                }

-            

-                *pAllocation = vma_new(m_hAllocator, VmaAllocation_T)(currentFrameIndex, isUserDataString);

-                pCurrBlock->m_pMetadata->Alloc(currRequest, suballocType, size, isUpperAddress, *pAllocation);

-                (*pAllocation)->InitBlockAllocation(

-                    hCurrentPool,

+        if(m_LinearAlgorithm)

+        {

+            // Use only last block.

+            if(!m_Blocks.empty())

+            {

+                VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks.back();

+                VMA_ASSERT(pCurrBlock);

+                VkResult res = AllocateFromBlock(

                     pCurrBlock,

-                    currRequest.offset,

-                    alignment,

+                    hCurrentPool,

+                    currentFrameIndex,

                     size,

+                    alignment,

+                    allocFlagsCopy,

+                    createInfo.pUserData,

                     suballocType,

-                    mapped,

-                    (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0);

-                VMA_HEAVY_ASSERT(pCurrBlock->Validate());

-                VMA_DEBUG_LOG("    Returned from existing allocation #%u", (uint32_t)blockIndex);

-                (*pAllocation)->SetUserData(m_hAllocator, createInfo.pUserData);

-                if(VMA_DEBUG_INITIALIZE_ALLOCATIONS)

+                    pAllocation);

+                if(res == VK_SUCCESS)

                 {

-                    m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED);

+                    VMA_DEBUG_LOG("    Returned from last block #%u", (uint32_t)(m_Blocks.size() - 1));

+                    return VK_SUCCESS;

                 }

-                if(IsCorruptionDetectionEnabled())

+            }

+        }

+        else

+        {

+            // Forward order in m_Blocks - prefer blocks with smallest amount of free space.

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

+            {

+                VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];

+                VMA_ASSERT(pCurrBlock);

+                VkResult res = AllocateFromBlock(

+                    pCurrBlock,

+                    hCurrentPool,

+                    currentFrameIndex,

+                    size,

+                    alignment,

+                    allocFlagsCopy,

+                    createInfo.pUserData,

+                    suballocType,

+                    pAllocation);

+                if(res == VK_SUCCESS)

                 {

-                    VkResult res = pCurrBlock->WriteMagicValueAroundAllocation(m_hAllocator, currRequest.offset, size);

-                    VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to write magic value.");

+                    VMA_DEBUG_LOG("    Returned from existing block #%u", (uint32_t)blockIndex);

+                    return VK_SUCCESS;

                 }

-                return VK_SUCCESS;

             }

         }

 

@@ -9555,56 +9580,24 @@
                 VmaDeviceMemoryBlock* const pBlock = m_Blocks[newBlockIndex];

                 VMA_ASSERT(pBlock->m_pMetadata->GetSize() >= size);

 

-                if(mapped)

-                {

-                    res = pBlock->Map(m_hAllocator, 1, VMA_NULL);

-                    if(res != VK_SUCCESS)

-                    {

-                        return res;

-                    }

-                }

-

-                // Allocate from pBlock. Because it is empty, dstAllocRequest can be trivially filled.

-                VmaAllocationRequest allocRequest;

-                if(pBlock->m_pMetadata->CreateAllocationRequest(

+                res = AllocateFromBlock(

+                    pBlock,

+                    hCurrentPool,

                     currentFrameIndex,

-                    m_FrameInUseCount,

-                    m_BufferImageGranularity,

                     size,

                     alignment,

-                    isUpperAddress,

+                    allocFlagsCopy,

+                    createInfo.pUserData,

                     suballocType,

-                    false, // canMakeOtherLost

-                    &allocRequest))

+                    pAllocation);

+                if(res == VK_SUCCESS)

                 {

-                    *pAllocation = vma_new(m_hAllocator, VmaAllocation_T)(currentFrameIndex, isUserDataString);

-                    pBlock->m_pMetadata->Alloc(allocRequest, suballocType, size, isUpperAddress, *pAllocation);

-                    (*pAllocation)->InitBlockAllocation(

-                        hCurrentPool,

-                        pBlock,

-                        allocRequest.offset,

-                        alignment,

-                        size,

-                        suballocType,

-                        mapped,

-                        (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0);

-                    VMA_HEAVY_ASSERT(pBlock->Validate());

-                    VMA_DEBUG_LOG("    Created new allocation Size=%llu", allocInfo.allocationSize);

-                    (*pAllocation)->SetUserData(m_hAllocator, createInfo.pUserData);

-                    if(VMA_DEBUG_INITIALIZE_ALLOCATIONS)

-                    {

-                        m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED);

-                    }

-                    if(IsCorruptionDetectionEnabled())

-                    {

-                        res = pBlock->WriteMagicValueAroundAllocation(m_hAllocator, allocRequest.offset, size);

-                        VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to write magic value.");

-                    }

+                    VMA_DEBUG_LOG("    Created new block Size=%llu", newBlockSize);

                     return VK_SUCCESS;

                 }

                 else

                 {

-                    // Allocation from empty block failed, possibly due to VMA_DEBUG_MARGIN or alignment.

+                    // Allocation from new block failed, possibly due to VMA_DEBUG_MARGIN or alignment.

                     return VK_ERROR_OUT_OF_DEVICE_MEMORY;

                 }

             }

@@ -9819,17 +9812,93 @@
 

 void VmaBlockVector::IncrementallySortBlocks()

 {

-    // Bubble sort only until first swap.

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

+    if(!m_LinearAlgorithm)

     {

-        if(m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize())

+        // Bubble sort only until first swap.

+        for(size_t i = 1; i < m_Blocks.size(); ++i)

         {

-            VMA_SWAP(m_Blocks[i - 1], m_Blocks[i]);

-            return;

+            if(m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize())

+            {

+                VMA_SWAP(m_Blocks[i - 1], m_Blocks[i]);

+                return;

+            }

         }

     }

 }

 

+VkResult VmaBlockVector::AllocateFromBlock(

+    VmaDeviceMemoryBlock* pBlock,

+    VmaPool hCurrentPool,

+    uint32_t currentFrameIndex,

+    VkDeviceSize size,

+    VkDeviceSize alignment,

+    VmaAllocationCreateFlags allocFlags,

+    void* pUserData,

+    VmaSuballocationType suballocType,

+    VmaAllocation* pAllocation)

+{

+    VMA_ASSERT((allocFlags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) == 0);

+    const bool isUpperAddress = (allocFlags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0;

+    const bool mapped = (allocFlags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0;

+    const bool isUserDataString = (allocFlags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0;

+

+    VmaAllocationRequest currRequest = {};

+    if(pBlock->m_pMetadata->CreateAllocationRequest(

+        currentFrameIndex,

+        m_FrameInUseCount,

+        m_BufferImageGranularity,

+        size,

+        alignment,

+        isUpperAddress,

+        suballocType,

+        false, // canMakeOtherLost

+        &currRequest))

+    {

+        // Allocate from pCurrBlock.

+        VMA_ASSERT(currRequest.itemsToMakeLostCount == 0);

+

+        if(mapped)

+        {

+            VkResult res = pBlock->Map(m_hAllocator, 1, VMA_NULL);

+            if(res != VK_SUCCESS)

+            {

+                return res;

+            }

+        }

+            

+        // We no longer have an empty Allocation.

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

+        {

+            m_HasEmptyBlock = false;

+        }

+            

+        *pAllocation = vma_new(m_hAllocator, VmaAllocation_T)(currentFrameIndex, isUserDataString);

+        pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, isUpperAddress, *pAllocation);

+        (*pAllocation)->InitBlockAllocation(

+            hCurrentPool,

+            pBlock,

+            currRequest.offset,

+            alignment,

+            size,

+            suballocType,

+            mapped,

+            (allocFlags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0);

+        VMA_HEAVY_ASSERT(pBlock->Validate());

+        (*pAllocation)->SetUserData(m_hAllocator, pUserData);

+        if(VMA_DEBUG_INITIALIZE_ALLOCATIONS)

+        {

+            m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED);

+        }

+        if(IsCorruptionDetectionEnabled())

+        {

+            VkResult res = pBlock->WriteMagicValueAroundAllocation(m_hAllocator, currRequest.offset, size);

+            VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to write magic value.");

+        }

+        return VK_SUCCESS;

+    }

+    return VK_ERROR_OUT_OF_DEVICE_MEMORY;

+}

+

 VkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex)

 {

     VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };

@@ -11812,10 +11881,9 @@
 

     if(newCreateInfo.maxBlockCount == 0)

     {

-        newCreateInfo.maxBlockCount = isLinearAlgorithm ? 1 : SIZE_MAX;

+        newCreateInfo.maxBlockCount = SIZE_MAX;

     }

-    if(newCreateInfo.minBlockCount > newCreateInfo.maxBlockCount ||

-        isLinearAlgorithm && newCreateInfo.maxBlockCount > 1)

+    if(newCreateInfo.minBlockCount > newCreateInfo.maxBlockCount)

     {

         return VK_ERROR_INITIALIZATION_FAILED;

     }