Preparations for adding support for alternative algorithms in virtual blocks and tests for them
diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h
index 528480a..9201e10 100644
--- a/include/vk_mem_alloc.h
+++ b/include/vk_mem_alloc.h
@@ -8887,14 +8887,20 @@
     if(!suballocations1st.empty())

     {

         // Null item at the beginning should be accounted into m_1stNullItemsBeginCount.

-        VMA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].userData != VMA_NULL);

-        // Null item at the end should be just pop_back().

-        VMA_VALIDATE(suballocations1st.back().userData != VMA_NULL);

+        if(!IsVirtual())

+        {

+            VMA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].userData != VMA_NULL);

+            // Null item at the end should be just pop_back().

+            VMA_VALIDATE(suballocations1st.back().userData != VMA_NULL);

+        }

     }

     if(!suballocations2nd.empty())

     {

-        // Null item at the end should be just pop_back().

-        VMA_VALIDATE(suballocations2nd.back().userData != VMA_NULL);

+        if(!IsVirtual())

+        {

+            // Null item at the end should be just pop_back().

+            VMA_VALIDATE(suballocations2nd.back().userData != VMA_NULL);

+        }

     }

 

     VMA_VALIDATE(m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount <= suballocations1st.size());

@@ -10555,7 +10561,7 @@
 

         // Find more null items at the beginning of 1st vector.

         while(m_1stNullItemsBeginCount < suballoc1stCount &&

-            suballocations1st[m_1stNullItemsBeginCount].userData == VMA_NULL)

+            suballocations1st[m_1stNullItemsBeginCount].type == VMA_SUBALLOCATION_TYPE_FREE)

         {

             ++m_1stNullItemsBeginCount;

             --m_1stNullItemsMiddleCount;

@@ -10563,7 +10569,7 @@
 

         // Find more null items at the end of 1st vector.

         while(m_1stNullItemsMiddleCount > 0 &&

-            suballocations1st.back().userData == VMA_NULL)

+            suballocations1st.back().type == VMA_SUBALLOCATION_TYPE_FREE)

         {

             --m_1stNullItemsMiddleCount;

             suballocations1st.pop_back();

@@ -10571,7 +10577,7 @@
 

         // Find more null items at the end of 2nd vector.

         while(m_2ndNullItemsCount > 0 &&

-            suballocations2nd.back().userData == VMA_NULL)

+            suballocations2nd.back().type == VMA_SUBALLOCATION_TYPE_FREE)

         {

             --m_2ndNullItemsCount;

             suballocations2nd.pop_back();

@@ -10579,7 +10585,7 @@
 

         // Find more null items at the beginning of 2nd vector.

         while(m_2ndNullItemsCount > 0 &&

-            suballocations2nd[0].userData == VMA_NULL)

+            suballocations2nd[0].type == VMA_SUBALLOCATION_TYPE_FREE)

         {

             --m_2ndNullItemsCount;

             VmaVectorRemove(suballocations2nd, 0);

@@ -10591,7 +10597,7 @@
             size_t srcIndex = m_1stNullItemsBeginCount;

             for(size_t dstIndex = 0; dstIndex < nonNullItemCount; ++dstIndex)

             {

-                while(suballocations1st[srcIndex].userData == VMA_NULL)

+                while(suballocations1st[srcIndex].type == VMA_SUBALLOCATION_TYPE_FREE)

                 {

                     ++srcIndex;

                 }

@@ -10624,7 +10630,7 @@
                 m_2ndVectorMode = SECOND_VECTOR_EMPTY;

                 m_1stNullItemsMiddleCount = m_2ndNullItemsCount;

                 while(m_1stNullItemsBeginCount < suballocations2nd.size() &&

-                    suballocations2nd[m_1stNullItemsBeginCount].userData == VMA_NULL)

+                    suballocations2nd[m_1stNullItemsBeginCount].type == VMA_SUBALLOCATION_TYPE_FREE)

                 {

                     ++m_1stNullItemsBeginCount;

                     --m_1stNullItemsMiddleCount;

diff --git a/src/Tests.cpp b/src/Tests.cpp
index db1d7e7..50b34cc 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -2817,6 +2817,144 @@
     }
 }
 
+static void TestVirtualBlocksAlgorithms()
+{
+    wprintf(L"Test virtual blocks algorithms\n");
+
+    RandomNumberGenerator rand{3454335};
+    auto calcRandomAllocSize = [&rand]() -> VkDeviceSize { return rand.Generate() % 20 + 5; };
+
+    for(size_t algorithmIndex = 0; algorithmIndex < 1/*3*/; ++algorithmIndex)
+    {
+        // Create the block
+        VmaVirtualBlockCreateInfo blockCreateInfo = {};
+        blockCreateInfo.pAllocationCallbacks = g_Allocs;
+        blockCreateInfo.size = 10'000;
+        switch(algorithmIndex)
+        {
+        case 1: blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT; break;
+        case 2: blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_BUDDY_ALGORITHM_BIT; break;
+        }
+        VmaVirtualBlock block = nullptr;
+        VkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block);
+        TEST(res == VK_SUCCESS);
+
+        struct AllocData
+        {
+            VkDeviceSize offset, size;
+        };
+        std::vector<AllocData> allocations;
+        
+        // Make some allocations
+        for(size_t i = 0; i < 20; ++i)
+        {
+            VmaVirtualAllocationCreateInfo allocCreateInfo = {};
+            allocCreateInfo.size = calcRandomAllocSize();
+            allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
+            if(i < 10) { }
+            else if(i < 12) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;
+            else if(i < 14) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
+            else if(i < 16) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT;
+            else if(i < 18 && algorithmIndex == 1) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
+
+            AllocData alloc = {};
+            alloc.size = allocCreateInfo.size;
+            res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.offset);
+            TEST(res == VK_SUCCESS);
+            allocations.push_back(alloc);
+        }
+
+        // Free some of the allocations
+        for(size_t i = 0; i < 5; ++i)
+        {
+            const size_t index = rand.Generate() % allocations.size();
+            vmaVirtualFree(block, allocations[index].offset);
+            allocations.erase(allocations.begin() + index);
+        }
+
+        // Allocate some more
+        for(size_t i = 0; i < 6; ++i)
+        {
+            VmaVirtualAllocationCreateInfo allocCreateInfo = {};
+            allocCreateInfo.size = calcRandomAllocSize();
+            allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
+
+            AllocData alloc = {};
+            alloc.size = allocCreateInfo.size;
+            res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.offset);
+            TEST(res == VK_SUCCESS);
+            allocations.push_back(alloc);
+        }
+
+        // Allocate some with extra alignment
+        for(size_t i = 0; i < 3; ++i)
+        {
+            VmaVirtualAllocationCreateInfo allocCreateInfo = {};
+            allocCreateInfo.size = calcRandomAllocSize();
+            allocCreateInfo.alignment = 16;
+            allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
+
+            AllocData alloc = {};
+            alloc.size = allocCreateInfo.size;
+            res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.offset);
+            TEST(res == VK_SUCCESS);
+            TEST(alloc.offset % 16 == 0);
+            allocations.push_back(alloc);
+        }
+
+        // Check if the allocations don't overlap
+        std::sort(allocations.begin(), allocations.end(), [](const AllocData& lhs, const AllocData& rhs) {
+            return lhs.offset < rhs.offset; });
+        for(size_t i = 0; i < allocations.size() - 1; ++i)
+        {
+            TEST(allocations[i+1].offset >= allocations[i].offset + allocations[i].size);
+        }
+
+        // Check pUserData
+        {
+            const AllocData& alloc = allocations.back();
+            VmaVirtualAllocationInfo allocInfo = {};
+            vmaGetVirtualAllocationInfo(block, alloc.offset, &allocInfo);
+            TEST((uintptr_t)allocInfo.pUserData == alloc.size * 10);
+
+            vmaSetVirtualAllocationUserData(block, alloc.offset, (void*)(uintptr_t)666);
+            vmaGetVirtualAllocationInfo(block, alloc.offset, &allocInfo);
+            TEST((uintptr_t)allocInfo.pUserData == 666);
+        }
+
+        // Calculate statistics
+        {
+            VkDeviceSize allocSizeMin = VK_WHOLE_SIZE, allocSizeMax = 0, allocSizeSum = 0;
+            std::for_each(allocations.begin(), allocations.end(), [&](const AllocData& a) {
+                allocSizeMin = std::min(allocSizeMin, a.size);
+                allocSizeMax = std::max(allocSizeMax, a.size);
+                allocSizeSum += a.size;
+            });
+
+            VmaStatInfo statInfo = {};
+            vmaCalculateVirtualBlockStats(block, &statInfo);
+            TEST(statInfo.allocationCount == allocations.size());
+            TEST(statInfo.blockCount == 1);
+            TEST(statInfo.usedBytes + statInfo.unusedBytes == blockCreateInfo.size);
+            TEST(statInfo.allocationSizeMax == allocSizeMax);
+            TEST(statInfo.allocationSizeMin == allocSizeMin);
+            TEST(statInfo.usedBytes >= allocSizeSum);
+        }
+
+        // Build JSON dump string
+        {
+            char* json = nullptr;
+            vmaBuildVirtualBlockStatsString(block, &json, VK_TRUE);
+            int I = 0; // put a breakpoint here to debug
+            vmaFreeVirtualBlockStatsString(block, json);
+        }
+
+        // Final cleanup
+        vmaClearVirtualBlock(block);
+        vmaDestroyVirtualBlock(block);
+    }
+}
+
 static void TestAllocationVersusResourceSize()
 {
     wprintf(L"Test allocation versus resource size\n");
@@ -6696,7 +6834,7 @@
     {
         ////////////////////////////////////////////////////////////////////////////////
         // Temporarily insert custom tests here:
-        TestVirtualBlocks();
+        TestVirtualBlocksAlgorithms();
         return;
     }