Added comprehensive tests for all kinds of statistics
diff --git a/src/Tests.cpp b/src/Tests.cpp
index 61fbd72..b13e5c8 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -117,6 +117,23 @@
     }
 }
 
+static inline bool operator==(const VmaStatistics& lhs, const VmaStatistics& rhs)
+{
+    return lhs.allocationBytes == rhs.allocationBytes &&
+        lhs.allocationCount == rhs.allocationCount &&
+        lhs.blockBytes == rhs.blockBytes &&
+        lhs.blockCount == rhs.blockCount;
+}
+static inline bool operator==(const VmaDetailedStatistics& lhs, const VmaDetailedStatistics& rhs)
+{
+    return lhs.statistics == rhs.statistics &&
+        lhs.unusedRangeCount == rhs.unusedRangeCount &&
+        lhs.allocationSizeMax == rhs.allocationSizeMax &&
+        lhs.allocationSizeMin == rhs.allocationSizeMin &&
+        lhs.unusedRangeSizeMax == rhs.unusedRangeSizeMax &&
+        lhs.unusedRangeSizeMin == rhs.unusedRangeSizeMin;
+}
+
 struct AllocationSize
 {
     uint32_t Probability;
@@ -6213,22 +6230,70 @@
     vmaDestroyAllocator(localAllocator);
 }
 
-static void TestBudget()
+static void InitEmptyDetailedStatistics(VmaDetailedStatistics& outStats)
 {
-    wprintf(L"Testing budget...\n");
+    outStats = {};
+    outStats.allocationSizeMin = VK_WHOLE_SIZE;
+    outStats.unusedRangeSizeMin = VK_WHOLE_SIZE;
+}
 
-    static const VkDeviceSize BUF_SIZE = 10ull * 1024 * 1024;
-    static const uint32_t BUF_COUNT = 4;
+static void AddDetailedStatistics(VmaDetailedStatistics& inoutSum, const VmaDetailedStatistics& stats)
+{
+    inoutSum.statistics.allocationBytes += stats.statistics.allocationBytes;
+    inoutSum.statistics.allocationCount += stats.statistics.allocationCount;
+    inoutSum.statistics.blockBytes += stats.statistics.blockBytes;
+    inoutSum.statistics.blockCount += stats.statistics.blockCount;
+    inoutSum.unusedRangeCount += stats.unusedRangeCount;
+    inoutSum.allocationSizeMax = std::max(inoutSum.allocationSizeMax, stats.allocationSizeMax);
+    inoutSum.allocationSizeMin = std::min(inoutSum.allocationSizeMin, stats.allocationSizeMin);
+    inoutSum.unusedRangeSizeMax = std::max(inoutSum.unusedRangeSizeMax, stats.unusedRangeSizeMax);
+    inoutSum.unusedRangeSizeMin = std::min(inoutSum.unusedRangeSizeMin, stats.unusedRangeSizeMin);
+}
+
+static void ValidateTotalStatistics(const VmaTotalStatistics& stats)
+{
+    const VkPhysicalDeviceMemoryProperties* memProps = nullptr;
+    vmaGetMemoryProperties(g_hAllocator, &memProps);
+
+    VmaDetailedStatistics sum;
+    InitEmptyDetailedStatistics(sum);
+    for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
+        AddDetailedStatistics(sum, stats.memoryHeap[i]);
+    TEST(sum == stats.total);
+
+    InitEmptyDetailedStatistics(sum);
+    for(uint32_t i = 0; i < memProps->memoryTypeCount; ++i)
+        AddDetailedStatistics(sum, stats.memoryType[i]);
+    TEST(sum == stats.total);
+}
+
+static void TestStatistics()
+{
+    wprintf(L"Testing statistics...\n");
+
+    constexpr VkDeviceSize BUF_SIZE = 10ull * 1024 * 1024;
+    constexpr uint32_t BUF_COUNT = 4;
+    constexpr VkDeviceSize PREALLOCATED_BLOCK_SIZE = BUF_SIZE * (BUF_COUNT + 1);
 
     const VkPhysicalDeviceMemoryProperties* memProps = {};
     vmaGetMemoryProperties(g_hAllocator, &memProps);
 
-    for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
+    /*
+    Test 0: VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.
+    Test 1: normal allocations.
+    Test 2: allocations in a custom pool.
+    Test 3: allocations in a custom pool, DEDICATED_MEMORY.
+    Test 4: allocations in a custom pool with preallocated memory.
+    */
+    uint32_t memTypeIndex = UINT32_MAX;
+    for(uint32_t testIndex = 0; testIndex < 5; ++testIndex)
     {
         vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex);
 
         VmaBudget budgetBeg[VK_MAX_MEMORY_HEAPS] = {};
         vmaGetHeapBudgets(g_hAllocator, budgetBeg);
+        VmaTotalStatistics statsBeg = {};
+        vmaCalculateStatistics(g_hAllocator, &statsBeg);
 
         for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
         {
@@ -6237,18 +6302,45 @@
             TEST(budgetBeg[i].statistics.allocationBytes <= budgetBeg[i].statistics.blockBytes);
         }
 
+        // Create pool.
+        const bool usePool = testIndex >= 2;
+        const bool useDedicated = testIndex == 0 || testIndex == 3;
+        const bool usePreallocated = testIndex == 4;
+        VmaPool pool = VK_NULL_HANDLE;
+        if(usePool)
+        {
+            assert(memTypeIndex != UINT32_MAX);
+            VmaPoolCreateInfo poolCreateInfo = {};
+            poolCreateInfo.memoryTypeIndex = memTypeIndex;
+            if(usePreallocated)
+            {
+                poolCreateInfo.blockSize = PREALLOCATED_BLOCK_SIZE;
+                poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
+            }
+            TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
+        }
+
+        VmaStatistics poolStatsBeg = {};
+        VmaDetailedStatistics detailedPoolStatsBeg = {};
+        if(usePool)
+        {
+            vmaGetPoolStatistics(g_hAllocator, pool, &poolStatsBeg);
+            vmaCalculatePoolStatistics(g_hAllocator, pool, &detailedPoolStatsBeg);
+        }
+
+        // CREATE BUFFERS
         VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
         bufInfo.size = BUF_SIZE;
         bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
 
         VmaAllocationCreateInfo allocCreateInfo = {};
-        allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
-        if(testIndex == 0)
-        {
+        if(usePool)
+            allocCreateInfo.pool = pool;
+        else
+            allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
+        if(useDedicated)
             allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
-        }
 
-        // CREATE BUFFERS
         uint32_t heapIndex = 0;
         BufferInfo bufInfos[BUF_COUNT] = {};
         for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
@@ -6259,6 +6351,8 @@
             TEST(res == VK_SUCCESS);
             if(bufIndex == 0)
             {
+                if(testIndex == 1)
+                    memTypeIndex = allocInfo.memoryType;
                 heapIndex = MemoryTypeToHeap(allocInfo.memoryType);
             }
             else
@@ -6270,6 +6364,16 @@
 
         VmaBudget budgetWithBufs[VK_MAX_MEMORY_HEAPS] = {};
         vmaGetHeapBudgets(g_hAllocator, budgetWithBufs);
+        VmaTotalStatistics statsWithBufs = {};
+        vmaCalculateStatistics(g_hAllocator, &statsWithBufs);
+
+        VmaStatistics poolStatsWithBufs = {};
+        VmaDetailedStatistics detailedPoolStatsWithBufs = {};
+        if(usePool)
+        {
+            vmaGetPoolStatistics(g_hAllocator, pool, &poolStatsWithBufs);
+            vmaCalculatePoolStatistics(g_hAllocator, pool, &detailedPoolStatsWithBufs);
+        }
 
         // DESTROY BUFFERS
         for(size_t bufIndex = BUF_COUNT; bufIndex--; )
@@ -6277,25 +6381,133 @@
             vmaDestroyBuffer(g_hAllocator, bufInfos[bufIndex].Buffer, bufInfos[bufIndex].Allocation);
         }
 
+        VmaStatistics poolStatsEnd = {};
+        VmaDetailedStatistics detailedPoolStatsEnd = {};
+        if(usePool)
+        {
+            vmaGetPoolStatistics(g_hAllocator, pool, &poolStatsEnd);
+            vmaCalculatePoolStatistics(g_hAllocator, pool, &detailedPoolStatsEnd);
+        }
+
+        // Destroy the pool.
+        vmaDestroyPool(g_hAllocator, pool);
+
         VmaBudget budgetEnd[VK_MAX_MEMORY_HEAPS] = {};
         vmaGetHeapBudgets(g_hAllocator, budgetEnd);
+        VmaTotalStatistics statsEnd = {};
+        vmaCalculateStatistics(g_hAllocator, &statsEnd);
 
-        // CHECK
+        // CHECK MEMORY HEAPS
         for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
         {
             TEST(budgetEnd[i].statistics.allocationBytes <= budgetEnd[i].statistics.blockBytes);
+
+            // The heap in which we allocated the testing buffers.
             if(i == heapIndex)
             {
+                // VmaBudget::usage
+                TEST(budgetWithBufs[i].usage >= budgetBeg[i].usage);
+                TEST(budgetEnd[i].usage <= budgetWithBufs[i].usage);
+
+                // VmaBudget - VmaStatistics::allocationBytes
                 TEST(budgetEnd[i].statistics.allocationBytes == budgetBeg[i].statistics.allocationBytes);
                 TEST(budgetWithBufs[i].statistics.allocationBytes == budgetBeg[i].statistics.allocationBytes + BUF_SIZE * BUF_COUNT);
-                TEST(budgetWithBufs[i].statistics.blockBytes >= budgetEnd[i].statistics.blockBytes);
+                
+                // VmaBudget - VmaStatistics::blockBytes
+                if(usePool)
+                {
+                    TEST(budgetEnd[i].statistics.blockBytes == budgetBeg[i].statistics.blockBytes);
+                    TEST(budgetWithBufs[i].statistics.blockBytes > budgetBeg[i].statistics.blockBytes);
+                }
+                else
+                    TEST(budgetWithBufs[i].statistics.blockBytes >= budgetBeg[i].statistics.blockBytes);
+
+                // VmaBudget - VmaStatistics::allocationCount
+                TEST(budgetEnd[i].statistics.allocationCount == budgetBeg[i].statistics.allocationCount);
+                TEST(budgetWithBufs[i].statistics.allocationCount == budgetBeg[i].statistics.allocationCount + BUF_COUNT);
+
+                // VmaBudget - VmaStatistics::blockCount
+                if(useDedicated)
+                {
+                    TEST(budgetEnd[i].statistics.blockCount == budgetBeg[i].statistics.blockCount);
+                    TEST(budgetWithBufs[i].statistics.blockCount == budgetBeg[i].statistics.blockCount + BUF_COUNT);
+                }
+                else if(usePool)
+                {
+                    TEST(budgetEnd[i].statistics.blockCount == budgetBeg[i].statistics.blockCount);
+                    if(usePreallocated)
+                        TEST(budgetWithBufs[i].statistics.blockCount == budgetBeg[i].statistics.blockCount + 1);
+                    else
+                        TEST(budgetWithBufs[i].statistics.blockCount > budgetBeg[i].statistics.blockCount);
+                }
             }
             else
             {
-                TEST(budgetEnd[i].statistics.allocationBytes == budgetEnd[i].statistics.allocationBytes &&
+                TEST(budgetEnd[i].statistics.allocationBytes == budgetBeg[i].statistics.allocationBytes &&
                     budgetEnd[i].statistics.allocationBytes == budgetWithBufs[i].statistics.allocationBytes);
-                TEST(budgetEnd[i].statistics.blockBytes == budgetEnd[i].statistics.blockBytes &&
+                TEST(budgetEnd[i].statistics.blockBytes == budgetBeg[i].statistics.blockBytes &&
                     budgetEnd[i].statistics.blockBytes == budgetWithBufs[i].statistics.blockBytes);
+                TEST(budgetEnd[i].statistics.allocationCount == budgetBeg[i].statistics.allocationCount &&
+                    budgetEnd[i].statistics.allocationCount == budgetWithBufs[i].statistics.allocationCount);
+                TEST(budgetEnd[i].statistics.blockCount == budgetBeg[i].statistics.blockCount &&
+                    budgetEnd[i].statistics.blockCount == budgetWithBufs[i].statistics.blockCount);
+            }
+
+            // Validate that statistics per heap and per type sum up to total correctly.
+            ValidateTotalStatistics(statsBeg);
+            ValidateTotalStatistics(statsWithBufs);
+            ValidateTotalStatistics(statsEnd);
+
+            // Compare vmaCalculateStatistics per heap with vmaGetBudget.
+            TEST(statsBeg.memoryHeap[i].statistics == budgetBeg[i].statistics);
+            TEST(statsWithBufs.memoryHeap[i].statistics == budgetWithBufs[i].statistics);
+            TEST(statsEnd.memoryHeap[i].statistics == budgetEnd[i].statistics);
+
+            if(usePool)
+            {
+                // Compare simple stats with calculated stats to make sure they are identical.
+                TEST(poolStatsBeg == detailedPoolStatsBeg.statistics);
+                TEST(poolStatsWithBufs == detailedPoolStatsWithBufs.statistics);
+                TEST(poolStatsEnd == detailedPoolStatsEnd.statistics);
+
+                // Validate stats of an empty pool.
+                TEST(detailedPoolStatsBeg.allocationSizeMax == 0);
+                TEST(detailedPoolStatsEnd.allocationSizeMax == 0);
+                TEST(detailedPoolStatsBeg.allocationSizeMin == VK_WHOLE_SIZE);
+                TEST(detailedPoolStatsEnd.allocationSizeMin == VK_WHOLE_SIZE);
+                TEST(poolStatsBeg.allocationCount == 0);
+                TEST(poolStatsBeg.allocationBytes == 0);
+                TEST(poolStatsEnd.allocationCount == 0);
+                TEST(poolStatsEnd.allocationBytes == 0);
+                if(usePreallocated)
+                {
+                    TEST(poolStatsBeg.blockCount      == 1);
+                    TEST(poolStatsEnd.blockCount      == 1);
+                    TEST(poolStatsBeg.blockBytes      == PREALLOCATED_BLOCK_SIZE);
+                    TEST(poolStatsEnd.blockBytes      == PREALLOCATED_BLOCK_SIZE);
+                }
+                else
+                {
+                    TEST(poolStatsBeg.blockCount == 0);
+                    TEST(poolStatsBeg.blockBytes == 0);
+                    // Not checking poolStatsEnd.blockCount, blockBytes, because an empty block may stay allocated.
+                }
+
+                // Validate stats of a pool with buffers.
+                TEST(detailedPoolStatsWithBufs.allocationSizeMin == BUF_SIZE);
+                TEST(detailedPoolStatsWithBufs.allocationSizeMax == BUF_SIZE);
+                TEST(poolStatsWithBufs.allocationCount == BUF_COUNT);
+                TEST(poolStatsWithBufs.allocationBytes == BUF_COUNT * BUF_SIZE);
+                if(usePreallocated)
+                {
+                    TEST(poolStatsWithBufs.blockCount == 1);
+                    TEST(poolStatsWithBufs.blockBytes == PREALLOCATED_BLOCK_SIZE);
+                }
+                else
+                {
+                    TEST(poolStatsWithBufs.blockCount > 0);
+                    TEST(poolStatsWithBufs.blockBytes >= poolStatsWithBufs.allocationBytes);
+                }
             }
         }
     }
@@ -8146,7 +8358,7 @@
 #endif
     TestMemoryUsage();
     TestDeviceCoherentMemory();
-    TestBudget();
+    TestStatistics();
     TestAliasing();
     TestAllocationAliasing();
     TestMapping();