Implemented vmaBuildVirtualBlockStatsString, vmaFreeVirtualBlockStatsString
diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h
index d6e0edb..5d49e23 100644
--- a/include/vk_mem_alloc.h
+++ b/include/vk_mem_alloc.h
@@ -3279,10 +3279,19 @@
         memcpy(result, srcStr, len + 1);

         return result;

     }

-    else

+    return VMA_NULL;

+}

+

+static char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr, size_t strLen)

+{

+    if(srcStr != VMA_NULL)

     {

-        return VMA_NULL;

+        char* const result = vma_new_array(allocs, char, strLen + 1);

+        memcpy(result, srcStr, strLen);

+        result[strLen] = '\0';

+        return result;

     }

+    return VMA_NULL;

 }

 

 static void VmaFreeString(const VkAllocationCallbacks* allocs, char* str)

@@ -5121,8 +5130,7 @@
         size_t allocationCount,

         size_t unusedRangeCount) const;

     void PrintDetailedMap_Allocation(class VmaJsonWriter& json,

-        VkDeviceSize offset,

-        VmaAllocation hAllocation) const;

+        VkDeviceSize offset, VkDeviceSize size, void* userData) const;

     void PrintDetailedMap_UnusedRange(class VmaJsonWriter& json,

         VkDeviceSize offset,

         VkDeviceSize size) const;

@@ -6852,6 +6860,37 @@
 #endif // #if VMA_MEMORY_BUDGET

 };

 

+class VmaStringBuilder;

+

+static void InitStatInfo(VmaStatInfo& outInfo)

+{

+    memset(&outInfo, 0, sizeof(outInfo));

+    outInfo.allocationSizeMin = UINT64_MAX;

+    outInfo.unusedRangeSizeMin = UINT64_MAX;

+}

+

+// Adds statistics srcInfo into inoutInfo, like: inoutInfo += srcInfo.

+static void VmaAddStatInfo(VmaStatInfo& inoutInfo, const VmaStatInfo& srcInfo)

+{

+    inoutInfo.blockCount += srcInfo.blockCount;

+    inoutInfo.allocationCount += srcInfo.allocationCount;

+    inoutInfo.unusedRangeCount += srcInfo.unusedRangeCount;

+    inoutInfo.usedBytes += srcInfo.usedBytes;

+    inoutInfo.unusedBytes += srcInfo.unusedBytes;

+    inoutInfo.allocationSizeMin = VMA_MIN(inoutInfo.allocationSizeMin, srcInfo.allocationSizeMin);

+    inoutInfo.allocationSizeMax = VMA_MAX(inoutInfo.allocationSizeMax, srcInfo.allocationSizeMax);

+    inoutInfo.unusedRangeSizeMin = VMA_MIN(inoutInfo.unusedRangeSizeMin, srcInfo.unusedRangeSizeMin);

+    inoutInfo.unusedRangeSizeMax = VMA_MAX(inoutInfo.unusedRangeSizeMax, srcInfo.unusedRangeSizeMax);

+}

+

+static void VmaPostprocessCalcStatInfo(VmaStatInfo& inoutInfo)

+{

+    inoutInfo.allocationSizeAvg = (inoutInfo.allocationCount > 0) ?

+        VmaRoundDiv<VkDeviceSize>(inoutInfo.usedBytes, inoutInfo.allocationCount) : 0;

+    inoutInfo.unusedRangeSizeAvg = (inoutInfo.unusedRangeCount > 0) ?

+        VmaRoundDiv<VkDeviceSize>(inoutInfo.unusedBytes, inoutInfo.unusedRangeCount) : 0;

+}

+

 struct VmaVirtualBlock_T

 {

     VMA_CLASS_NO_COPY(VmaVirtualBlock_T)

@@ -6894,7 +6933,11 @@
     void CalculateStats(VmaStatInfo& outStatInfo) const

     {

         m_Metadata->CalcAllocationStatInfo(outStatInfo);

+        VmaPostprocessCalcStatInfo(outStatInfo);

     }

+#if VMA_STATS_STRING_ENABLED

+    void BuildStatsString(bool detailedMap, VmaStringBuilder& sb) const;

+#endif

 

 private:

     VmaBlockMetadata* m_Metadata;

@@ -6954,7 +6997,7 @@
 class VmaStringBuilder

 {

 public:

-    VmaStringBuilder(VmaAllocator alloc) : m_Data(VmaStlAllocator<char>(alloc->GetAllocationCallbacks())) { }

+    VmaStringBuilder(const VkAllocationCallbacks* allocationCallbacks) : m_Data(VmaStlAllocator<char>(allocationCallbacks)) { }

     size_t GetLength() const { return m_Data.size(); }

     const char* GetData() const { return m_Data.data(); }

 

@@ -7701,15 +7744,33 @@
 }

 

 void VmaBlockMetadata::PrintDetailedMap_Allocation(class VmaJsonWriter& json,

-    VkDeviceSize offset,

-    VmaAllocation hAllocation) const

+    VkDeviceSize offset, VkDeviceSize size, void* userData) const

 {

     json.BeginObject(true);

 

     json.WriteString("Offset");

     json.WriteNumber(offset);

 

-    hAllocation->PrintParameters(json);

+    if(IsVirtual())

+    {

+        json.WriteString("Type");

+        json.WriteString("VirtualAllocation");

+

+        json.WriteString("Size");

+        json.WriteNumber(size);

+

+        if(userData != VMA_NULL)

+        {

+            json.WriteString("UserData");

+            json.BeginString();

+            json.ContinueString_Pointer(userData);

+            json.EndString();

+        }

+    }

+    else

+    {

+        ((VmaAllocation)userData)->PrintParameters(json);

+    }

 

     json.EndObject();

 }

@@ -7934,8 +7995,7 @@
         }

         else

         {

-            VMA_ASSERT(!IsVirtual()); // TODO?

-            PrintDetailedMap_Allocation(json, suballoc.offset, (VmaAllocation)suballoc.userData);

+            PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData);

         }

     }

 

@@ -9575,7 +9635,7 @@
 

                 // 2. Process this allocation.

                 // There is allocation with suballoc.offset, suballoc.size.

-                PrintDetailedMap_Allocation(json, suballoc.offset, (VmaAllocation)suballoc.userData);

+                PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData);

 

                 // 3. Prepare for next iteration.

                 lastOffset = suballoc.offset + suballoc.size;

@@ -9622,7 +9682,7 @@
 

             // 2. Process this allocation.

             // There is allocation with suballoc.offset, suballoc.size.

-            PrintDetailedMap_Allocation(json, suballoc.offset, (VmaAllocation)suballoc.userData);

+            PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData);

 

             // 3. Prepare for next iteration.

             lastOffset = suballoc.offset + suballoc.size;

@@ -9670,7 +9730,7 @@
 

                 // 2. Process this allocation.

                 // There is allocation with suballoc.offset, suballoc.size.

-                PrintDetailedMap_Allocation(json, suballoc.offset, (VmaAllocation)suballoc.userData);

+                PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData);

 

                 // 3. Prepare for next iteration.

                 lastOffset = suballoc.offset + suballoc.size;

@@ -11123,8 +11183,8 @@
     case Node::TYPE_ALLOCATION:

         {

             VmaAllocation const alloc = (VmaAllocation)node->allocation.userData;

-            PrintDetailedMap_Allocation(json, node->offset, alloc);

             const VkDeviceSize allocSize = alloc->GetSize();

+            PrintDetailedMap_Allocation(json, node->offset, allocSize, node->allocation.userData);

             if(allocSize < levelNodeSize)

             {

                 PrintDetailedMap_UnusedRange(json, node->offset + allocSize, levelNodeSize - allocSize);

@@ -11376,35 +11436,6 @@
     return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext);

 }

 

-static void InitStatInfo(VmaStatInfo& outInfo)

-{

-    memset(&outInfo, 0, sizeof(outInfo));

-    outInfo.allocationSizeMin = UINT64_MAX;

-    outInfo.unusedRangeSizeMin = UINT64_MAX;

-}

-

-// Adds statistics srcInfo into inoutInfo, like: inoutInfo += srcInfo.

-static void VmaAddStatInfo(VmaStatInfo& inoutInfo, const VmaStatInfo& srcInfo)

-{

-    inoutInfo.blockCount += srcInfo.blockCount;

-    inoutInfo.allocationCount += srcInfo.allocationCount;

-    inoutInfo.unusedRangeCount += srcInfo.unusedRangeCount;

-    inoutInfo.usedBytes += srcInfo.usedBytes;

-    inoutInfo.unusedBytes += srcInfo.unusedBytes;

-    inoutInfo.allocationSizeMin = VMA_MIN(inoutInfo.allocationSizeMin, srcInfo.allocationSizeMin);

-    inoutInfo.allocationSizeMax = VMA_MAX(inoutInfo.allocationSizeMax, srcInfo.allocationSizeMax);

-    inoutInfo.unusedRangeSizeMin = VMA_MIN(inoutInfo.unusedRangeSizeMin, srcInfo.unusedRangeSizeMin);

-    inoutInfo.unusedRangeSizeMax = VMA_MAX(inoutInfo.unusedRangeSizeMax, srcInfo.unusedRangeSizeMax);

-}

-

-static void VmaPostprocessCalcStatInfo(VmaStatInfo& inoutInfo)

-{

-    inoutInfo.allocationSizeAvg = (inoutInfo.allocationCount > 0) ?

-        VmaRoundDiv<VkDeviceSize>(inoutInfo.usedBytes, inoutInfo.allocationCount) : 0;

-    inoutInfo.unusedRangeSizeAvg = (inoutInfo.unusedRangeCount > 0) ?

-        VmaRoundDiv<VkDeviceSize>(inoutInfo.unusedBytes, inoutInfo.unusedRangeCount) : 0;

-}

-

 VmaPool_T::VmaPool_T(

     VmaAllocator hAllocator,

     const VmaPoolCreateInfo& createInfo,

@@ -16713,6 +16744,28 @@
     return VK_ERROR_OUT_OF_DEVICE_MEMORY;

 }

 

+#if VMA_STATS_STRING_ENABLED

+void VmaVirtualBlock_T::BuildStatsString(bool detailedMap, VmaStringBuilder& sb) const

+{

+    VmaJsonWriter json(GetAllocationCallbacks(), sb);

+    json.BeginObject();

+

+    VmaStatInfo stat = {};

+    CalculateStats(stat);

+

+    json.WriteString("Stats");

+    VmaPrintStatInfo(json, stat);

+

+    if(detailedMap)

+    {

+        json.WriteString("Details");

+        m_Metadata->PrintDetailedMap(json);

+    }

+

+    json.EndObject();

+}

+#endif // #if VMA_STATS_STRING_ENABLED

+

 ////////////////////////////////////////////////////////////////////////////////

 // Public interface

 

@@ -16819,7 +16872,7 @@
     VMA_ASSERT(allocator && ppStatsString);

     VMA_DEBUG_GLOBAL_MUTEX_LOCK

 

-    VmaStringBuilder sb(allocator);

+    VmaStringBuilder sb(allocator->GetAllocationCallbacks());

     {

         VmaJsonWriter json(allocator->GetAllocationCallbacks(), sb);

         json.BeginObject();

@@ -16942,14 +16995,7 @@
         json.EndObject();

     }

 

-    const size_t len = sb.GetLength();

-    char* const pChars = vma_new_array(allocator, char, len + 1);

-    if(len > 0)

-    {

-        memcpy(pChars, sb.GetData(), len);

-    }

-    pChars[len] = '\0';

-    *ppStatsString = pChars;

+    *ppStatsString = VmaCreateStringCopy(allocator->GetAllocationCallbacks(), sb.GetData(), sb.GetLength());

 }

 

 VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString(

@@ -16959,8 +17005,7 @@
     if(pStatsString != VMA_NULL)

     {

         VMA_ASSERT(allocator);

-        size_t len = strlen(pStatsString);

-        vma_delete_array(allocator, pStatsString, len + 1);

+        VmaFreeString(allocator->GetAllocationCallbacks(), pStatsString);

     }

 }

 

@@ -18431,16 +18476,30 @@
     virtualBlock->CalculateStats(*pStatInfo);

 }

 

+#if VMA_STATS_STRING_ENABLED

+

 VMA_CALL_PRE void VMA_CALL_POST vmaBuildVirtualBlockStatsString(VmaVirtualBlock VMA_NOT_NULL virtualBlock,

     char* VMA_NULLABLE * VMA_NOT_NULL ppStatsString, VkBool32 detailedMap)

 {

-    VMA_ASSERT(0 && "TODO implement");

+    VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && ppStatsString != VMA_NULL);

+    VMA_DEBUG_GLOBAL_MUTEX_LOCK;

+    const VkAllocationCallbacks* allocationCallbacks = virtualBlock->GetAllocationCallbacks();

+    VmaStringBuilder sb(allocationCallbacks);

+    virtualBlock->BuildStatsString(detailedMap != VK_FALSE, sb);

+    *ppStatsString = VmaCreateStringCopy(allocationCallbacks, sb.GetData(), sb.GetLength());

 }

 

+#endif // #if VMA_STATS_STRING_ENABLED

+

 VMA_CALL_PRE void VMA_CALL_POST vmaFreeVirtualBlockStatsString(VmaVirtualBlock VMA_NOT_NULL virtualBlock,

     char* VMA_NULLABLE pStatsString)

 {

-    VMA_ASSERT(0 && "TODO implement");

+    if(pStatsString != VMA_NULL)

+    {

+        VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);

+        VMA_DEBUG_GLOBAL_MUTEX_LOCK;

+        VmaFreeString(virtualBlock->GetAllocationCallbacks(), pStatsString);

+    }

 }

 

 #endif // #ifdef VMA_IMPLEMENTATION

diff --git a/src/Tests.cpp b/src/Tests.cpp
index 0c562df..db1d7e7 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -2756,18 +2756,16 @@
     TEST(statInfo.usedBytes == blockSize);
     TEST(statInfo.unusedBytes + statInfo.usedBytes == blockSize);
 
-#if 0
     // # Generate JSON dump
 
-    WCHAR* json = nullptr;
-    block->BuildStatsString(&json);
+    char* json = nullptr;
+    vmaBuildVirtualBlockStatsString(block, &json, VK_TRUE);
     {
-        std::wstring str(json);
-        CHECK_BOOL( str.find(L"\"UserData\": 1") != std::wstring::npos );
-        CHECK_BOOL( str.find(L"\"UserData\": 2") != std::wstring::npos );
+        std::string str(json);
+        TEST( str.find("\"UserData\": \"0000000000000001\"") != std::string::npos );
+        TEST( str.find("\"UserData\": \"0000000000000002\"") != std::string::npos );
     }
-    block->FreeStatsString(json);
-#endif
+    vmaFreeVirtualBlockStatsString(block, json);
 
     // # Free alloc0, leave alloc2 unfreed.