New JSON dump format

Unified across VMA and D3D12MA.
Updated Python script for visualization - now called GpuMemDumpVis.py.

Also a fix for bug in EXTENSIVE defragmentation algorithm - see #232

Code by @medranSolus
diff --git a/README.md b/README.md
index 7959d74..0096ab6 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@
 - Statistics: Obtain brief or detailed statistics about the amount of memory used, unused, number of allocated blocks, number of allocations etc. - globally, per memory heap, and per memory type.
 - Debug annotations: Associate custom `void* pUserData` and debug `char* pName` with each allocation.
 - JSON dump: Obtain a string in JSON format with detailed map of internal state, including list of allocations, their string names, and gaps between them.
-- Convert this JSON dump into a picture to visualize your memory. See [tools/VmaDumpVis](tools/VmaDumpVis/README.md).
+- Convert this JSON dump into a picture to visualize your memory. See [tools/GpuMemDumpVis](tools/GpuMemDumpVis/README.md).
 - Debugging incorrect memory usage: Enable initialization of all allocated memory with a bit pattern to detect usage of uninitialized or freed memory. Enable validation of a magic number after every allocation to detect out-of-bounds memory corruption.
 - Support for interoperability with OpenGL.
 - Virtual allocator: Interface for using core allocation algorithm to allocate any custom data, e.g. pieces of one large buffer.
diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h
index f5da499..d6bd74b 100644
--- a/include/vk_mem_alloc.h
+++ b/include/vk_mem_alloc.h
@@ -5765,41 +5765,29 @@
 

     json.WriteString("BlockCount");

     json.WriteNumber(stat.statistics.blockCount);

-

+    json.WriteString("BlockBytes");

+    json.WriteNumber(stat.statistics.blockBytes);

     json.WriteString("AllocationCount");

     json.WriteNumber(stat.statistics.allocationCount);

-

+    json.WriteString("AllocationBytes");

+    json.WriteNumber(stat.statistics.allocationBytes);

     json.WriteString("UnusedRangeCount");

     json.WriteNumber(stat.unusedRangeCount);

 

-    json.WriteString("BlockBytes");

-    json.WriteNumber(stat.statistics.blockBytes);

-

-    json.WriteString("AllocationBytes");

-    json.WriteNumber(stat.statistics.allocationBytes);

-

     if (stat.statistics.allocationCount > 1)

     {

-        json.WriteString("AllocationSize");

-        json.BeginObject(true);

-        json.WriteString("Min");

+        json.WriteString("AllocationSizeMin");

         json.WriteNumber(stat.allocationSizeMin);

-        json.WriteString("Max");

+        json.WriteString("AllocationSizeMax");

         json.WriteNumber(stat.allocationSizeMax);

-        json.EndObject();

     }

-

     if (stat.unusedRangeCount > 1)

     {

-        json.WriteString("UnusedRangeSize");

-        json.BeginObject(true);

-        json.WriteString("Min");

+        json.WriteString("UnusedRangeSizeMin");

         json.WriteNumber(stat.unusedRangeSizeMin);

-        json.WriteString("Max");

+        json.WriteString("UnusedRangeSizeMax");

         json.WriteNumber(stat.unusedRangeSizeMax);

-        json.EndObject();

     }

-

     json.EndObject();

 }

 #endif // _VMA_JSON_WRITER

@@ -6348,8 +6336,7 @@
     virtual void AddStatistics(VmaStatistics& inoutStats) const = 0;

 

 #if VMA_STATS_STRING_ENABLED

-    // mapRefCount == UINT32_MAX means unspecified.

-    virtual void PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const = 0;

+    virtual void PrintDetailedMap(class VmaJsonWriter& json) const = 0;

 #endif

 

     // Tries to find a place for suballocation with given parameters inside this block.

@@ -6393,8 +6380,7 @@
     void PrintDetailedMap_Begin(class VmaJsonWriter& json,

         VkDeviceSize unusedBytes,

         size_t allocationCount,

-        size_t unusedRangeCount,

-        uint32_t mapRefCount) const;

+        size_t unusedRangeCount) const;

     void PrintDetailedMap_Allocation(class VmaJsonWriter& json,

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

     void PrintDetailedMap_UnusedRange(class VmaJsonWriter& json,

@@ -6448,10 +6434,8 @@
 

 #if VMA_STATS_STRING_ENABLED

 void VmaBlockMetadata::PrintDetailedMap_Begin(class VmaJsonWriter& json,

-    VkDeviceSize unusedBytes, size_t allocationCount, size_t unusedRangeCount, uint32_t mapRefCount) const

+    VkDeviceSize unusedBytes, size_t allocationCount, size_t unusedRangeCount) const

 {

-    json.BeginObject();

-

     json.WriteString("TotalBytes");

     json.WriteNumber(GetSize());

 

@@ -6459,16 +6443,10 @@
     json.WriteNumber(unusedBytes);

 

     json.WriteString("Allocations");

-    json.WriteNumber((uint64_t)allocationCount);

+    json.WriteNumber(allocationCount);

 

     json.WriteString("UnusedRanges");

-    json.WriteNumber((uint64_t)unusedRangeCount);

-

-    if(mapRefCount != UINT32_MAX)

-    {

-        json.WriteString("MapRefCount");

-        json.WriteNumber(mapRefCount);

-    }

+    json.WriteNumber(unusedRangeCount);

 

     json.WriteString("Suballocations");

     json.BeginArray();

@@ -6484,15 +6462,11 @@
 

     if (IsVirtual())

     {

-        json.WriteString("Type");

-        json.WriteString("VirtualAllocation");

-

         json.WriteString("Size");

         json.WriteNumber(size);

-

-        if (userData != VMA_NULL)

+        if (userData)

         {

-            json.WriteString("UserData");

+            json.WriteString("CustomData");

             json.BeginString();

             json.ContinueString_Pointer(userData);

             json.EndString();

@@ -6526,7 +6500,6 @@
 void VmaBlockMetadata::PrintDetailedMap_End(class VmaJsonWriter& json) const

 {

     json.EndArray();

-    json.EndObject();

 }

 #endif // VMA_STATS_STRING_ENABLED

 #endif // _VMA_BLOCK_METADATA_FUNCTIONS

@@ -7631,7 +7604,7 @@
     void AddStatistics(VmaStatistics& inoutStats) const override;

 

 #if VMA_STATS_STRING_ENABLED

-    void PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const override;

+    void PrintDetailedMap(class VmaJsonWriter& json) const override;

 #endif

 

     bool CreateAllocationRequest(

@@ -8218,7 +8191,7 @@
 }

 

 #if VMA_STATS_STRING_ENABLED

-void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const

+void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const

 {

     const VkDeviceSize size = GetSize();

     const SuballocationVectorType& suballocations1st = AccessSuballocations1st();

@@ -8380,7 +8353,7 @@
     }

 

     const VkDeviceSize unusedBytes = size - usedBytes;

-    PrintDetailedMap_Begin(json, unusedBytes, alloc1stCount + alloc2ndCount, unusedRangeCount, mapRefCount);

+    PrintDetailedMap_Begin(json, unusedBytes, alloc1stCount + alloc2ndCount, unusedRangeCount);

 

     // SECOND PASS

     lastOffset = 0;

@@ -9988,7 +9961,7 @@
     void AddStatistics(VmaStatistics& inoutStats) const override;

 

 #if VMA_STATS_STRING_ENABLED

-    void PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const override;

+    void PrintDetailedMap(class VmaJsonWriter& json) const override;

 #endif

 

     bool CreateAllocationRequest(

@@ -10261,7 +10234,7 @@
 }

 

 #if VMA_STATS_STRING_ENABLED

-void VmaBlockMetadata_TLSF::PrintDetailedMap(class VmaJsonWriter& json, uint32_t mapRefCount) const

+void VmaBlockMetadata_TLSF::PrintDetailedMap(class VmaJsonWriter& json) const

 {

     size_t blockCount = m_AllocCount + m_BlocksFreeCount;

     VmaStlAllocator<Block*> allocator(GetAllocationCallbacks());

@@ -10278,12 +10251,10 @@
     VmaClearDetailedStatistics(stats);

     AddDetailedStatistics(stats);

 

-    PrintDetailedMap_Begin(

-        json,

+    PrintDetailedMap_Begin(json,

         stats.statistics.blockBytes - stats.statistics.allocationBytes,

         stats.statistics.allocationCount,

-        stats.unusedRangeCount,

-        mapRefCount);

+        stats.unusedRangeCount);

 

     for (; i < blockCount; ++i)

     {

@@ -11380,8 +11351,9 @@
     if (detailedMap)

     {

         json.WriteString("Details");

-        m_Metadata->PrintDetailedMap(json,

-            UINT32_MAX); // mapRefCount

+        json.BeginObject();

+        m_Metadata->PrintDetailedMap(json);

+        json.EndObject();

     }

 

     json.EndObject();

@@ -12280,10 +12252,12 @@
 

     json.WriteString("Size");

     json.WriteNumber(m_Size);

+    json.WriteString("Usage");

+    json.WriteNumber(m_BufferImageUsage);

 

     if (m_pUserData != VMA_NULL)

     {

-        json.WriteString("UserData");

+        json.WriteString("CustomData");

         json.BeginString();

         json.ContinueString_Pointer(m_pUserData);

         json.EndString();

@@ -12293,12 +12267,6 @@
         json.WriteString("Name");

         json.WriteString(m_pName);

     }

-

-    if (m_BufferImageUsage != 0)

-    {

-        json.WriteString("Usage");

-        json.WriteNumber(m_BufferImageUsage);

-    }

 }

 #endif // VMA_STATS_STRING_ENABLED

 

@@ -12951,50 +12919,7 @@
 {

     VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);

 

-    if (IsCustomPool())

-    {

-        const char* poolName = m_hParentPool->GetName();

-        if (poolName != VMA_NULL && poolName[0] != '\0')

-        {

-            json.WriteString("Name");

-            json.WriteString(poolName);

-        }

 

-        json.WriteString("MemoryTypeIndex");

-        json.WriteNumber(m_MemoryTypeIndex);

-

-        json.WriteString("BlockSize");

-        json.WriteNumber(m_PreferredBlockSize);

-

-        json.WriteString("BlockCount");

-        json.BeginObject(true);

-        if (m_MinBlockCount > 0)

-        {

-            json.WriteString("Min");

-            json.WriteNumber((uint64_t)m_MinBlockCount);

-        }

-        if (m_MaxBlockCount < SIZE_MAX)

-        {

-            json.WriteString("Max");

-            json.WriteNumber((uint64_t)m_MaxBlockCount);

-        }

-        json.WriteString("Cur");

-        json.WriteNumber((uint64_t)m_Blocks.size());

-        json.EndObject();

-

-        if (m_Algorithm != 0)

-        {

-            json.WriteString("Algorithm");

-            json.WriteString(VmaAlgorithmToStr(m_Algorithm));

-        }

-    }

-    else

-    {

-        json.WriteString("PreferredBlockSize");

-        json.WriteNumber(m_PreferredBlockSize);

-    }

-

-    json.WriteString("Blocks");

     json.BeginObject();

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

     {

@@ -13002,7 +12927,12 @@
         json.ContinueString(m_Blocks[i]->GetId());

         json.EndString();

 

-        m_Blocks[i]->m_pMetadata->PrintDetailedMap(json, m_Blocks[i]->GetMapRefCount());

+        json.BeginObject();

+        json.WriteString("MapRefCount");

+        json.WriteNumber(m_Blocks[i]->GetMapRefCount());

+

+        m_Blocks[i]->m_pMetadata->PrintDetailedMap(json);

+        json.EndObject();

     }

     json.EndObject();

 }

@@ -13303,9 +13233,15 @@
                 StateExtensive& state = reinterpret_cast<StateExtensive*>(m_AlgorithmState)[vectorIndex];

                 if (state.firstFreeBlock != SIZE_MAX)

                 {

-                    state.firstFreeBlock -= prevCount - currentCount;

-                    if (state.firstFreeBlock != 0)

-                        state.firstFreeBlock -= vector->GetBlock(state.firstFreeBlock - 1)->m_pMetadata->IsEmpty();

+                    const size_t diff = prevCount - currentCount;

+                    if (state.firstFreeBlock >= diff)

+                    {

+                        state.firstFreeBlock -= diff;

+                        if (state.firstFreeBlock != 0)

+                            state.firstFreeBlock -= vector->GetBlock(state.firstFreeBlock - 1)->m_pMetadata->IsEmpty();

+                    }

+                    else

+                        state.firstFreeBlock = 0;

                 }

             }

         }

@@ -13350,7 +13286,10 @@
                                 {

                                     if (i < state.firstFreeBlock - 1)

                                     {

-                                        VMA_SWAP(vector->m_Blocks[i], vector->m_Blocks[--state.firstFreeBlock]);

+                                        if (state.firstFreeBlock > 1)

+                                            VMA_SWAP(vector->m_Blocks[i], vector->m_Blocks[--state.firstFreeBlock]);

+                                        else

+                                            --state.firstFreeBlock;

                                     }

                                 }

                                 swapped = true;

@@ -15968,89 +15907,90 @@
 #if VMA_STATS_STRING_ENABLED

 void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json)

 {

-    bool dedicatedAllocationsStarted = false;

-    for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)

+    json.WriteString("DefaultPools");

+    json.BeginObject();

     {

-        VmaDedicatedAllocationList& dedicatedAllocList = m_DedicatedAllocations[memTypeIndex];

-        if(!dedicatedAllocList.IsEmpty())

-        {

-            if(dedicatedAllocationsStarted == false)

-            {

-                dedicatedAllocationsStarted = true;

-                json.WriteString("DedicatedAllocations");

-                json.BeginObject();

-            }

-

-            json.BeginString("Type ");

-            json.ContinueString(memTypeIndex);

-            json.EndString();

-

-            dedicatedAllocList.BuildStatsString(json);

-        }

-    }

-    if(dedicatedAllocationsStarted)

-    {

-        json.EndObject();

-    }

-

-    {

-        bool allocationsStarted = false;

-        for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)

+        for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)

         {

             VmaBlockVector* pBlockVector = m_pBlockVectors[memTypeIndex];

-            if(pBlockVector != VMA_NULL)

+            VmaDedicatedAllocationList& dedicatedAllocList = m_DedicatedAllocations[memTypeIndex];

+            if (pBlockVector != VMA_NULL)

             {

-                if (pBlockVector->IsEmpty() == false)

-                {

-                    if (allocationsStarted == false)

-                    {

-                        allocationsStarted = true;

-                        json.WriteString("DefaultPools");

-                        json.BeginObject();

-                    }

-

-                    json.BeginString("Type ");

-                    json.ContinueString(memTypeIndex);

-                    json.EndString();

-

-                    json.BeginObject();

-                    pBlockVector->PrintDetailedMap(json);

-                    json.EndObject();

-                }

-            }

-        }

-        if(allocationsStarted)

-        {

-            json.EndObject();

-        }

-    }

-

-    // Custom pools

-    {

-        VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex);

-        if(!m_Pools.IsEmpty())

-        {

-            json.WriteString("Pools");

-            json.BeginObject();

-            for(VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool))

-            {

-                json.BeginString();

-                json.ContinueString(pool->GetId());

+                json.BeginString("Type ");

+                json.ContinueString(memTypeIndex);

                 json.EndString();

-

                 json.BeginObject();

-                pool->m_BlockVector.PrintDetailedMap(json);

-

-                if (!pool->m_DedicatedAllocations.IsEmpty())

                 {

+                    json.WriteString("PreferredBlockSize");

+                    json.WriteNumber(pBlockVector->GetPreferredBlockSize());

+

+                    json.WriteString("Blocks");

+                    pBlockVector->PrintDetailedMap(json);

+

                     json.WriteString("DedicatedAllocations");

-                    pool->m_DedicatedAllocations.BuildStatsString(json);

+                    dedicatedAllocList.BuildStatsString(json);

                 }

                 json.EndObject();

             }

-            json.EndObject();

         }

     }

+    json.EndObject();

+

+    json.WriteString("CustomPools");

+    json.BeginObject();

+    {

+        VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex);

+        if (!m_Pools.IsEmpty())

+        {

+            for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex)

+            {

+                bool displayType = true;

+                size_t index = 0;

+                for (VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool))

+                {

+                    VmaBlockVector& blockVector = pool->m_BlockVector;

+                    if (blockVector.GetMemoryTypeIndex() == memTypeIndex)

+                    {

+                        if (displayType)

+                        {

+                            json.BeginString("Type ");

+                            json.ContinueString(memTypeIndex);

+                            json.EndString();

+                            json.BeginArray();

+                            displayType = false;

+                        }

+

+                        json.BeginObject();

+                        {

+                            json.WriteString("Name");

+                            json.BeginString();

+                            json.ContinueString(index++);

+                            if (pool->GetName())

+                            {

+                                json.WriteString(" - ");

+                                json.WriteString(pool->GetName());

+                            }

+                            json.EndString();

+

+                            json.WriteString("PreferredBlockSize");

+                            json.WriteNumber(blockVector.GetPreferredBlockSize());

+

+                            json.WriteString("Blocks");

+                            blockVector.PrintDetailedMap(json);

+

+                            json.WriteString("DedicatedAllocations");

+                            pool->m_DedicatedAllocations.BuildStatsString(json);

+                        }

+                        json.EndObject();

+                    }

+                }

+

+                if (!displayType)

+                    json.EndArray();

+            }

+        }

+    }

+    json.EndObject();

 }

 #endif // VMA_STATS_STRING_ENABLED

 #endif // _VMA_ALLOCATOR_T_FUNCTIONS

@@ -16161,127 +16101,176 @@
 

     VmaStringBuilder sb(allocator->GetAllocationCallbacks());

     {

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

-        json.BeginObject();

-

         VmaBudget budgets[VK_MAX_MEMORY_HEAPS];

         allocator->GetHeapBudgets(budgets, 0, allocator->GetMemoryHeapCount());

 

         VmaTotalStatistics stats;

         allocator->CalculateStatistics(&stats);

 

-        json.WriteString("Total");

-        VmaPrintDetailedStatistics(json, stats.total);

-

-        for(uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex)

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

+        json.BeginObject();

         {

-            json.BeginString("Heap ");

-            json.ContinueString(heapIndex);

-            json.EndString();

-            json.BeginObject();

-

-            json.WriteString("Size");

-            json.WriteNumber(allocator->m_MemProps.memoryHeaps[heapIndex].size);

-

-            json.WriteString("Flags");

-            json.BeginArray(true);

-            if((allocator->m_MemProps.memoryHeaps[heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0)

-            {

-                json.WriteString("DEVICE_LOCAL");

-            }

-            json.EndArray();

-

-            json.WriteString("Budget");

+            json.WriteString("General");

             json.BeginObject();

             {

-                json.WriteString("BlockBytes");

-                json.WriteNumber(budgets[heapIndex].statistics.blockBytes);

-                json.WriteString("AllocationBytes");

-                json.WriteNumber(budgets[heapIndex].statistics.allocationBytes);

-                json.WriteString("BlockCount");

-                json.WriteNumber(budgets[heapIndex].statistics.blockCount);

-                json.WriteString("AllocationCount");

-                json.WriteNumber(budgets[heapIndex].statistics.allocationCount);

-                json.WriteString("Usage");

-                json.WriteNumber(budgets[heapIndex].usage);

-                json.WriteString("Budget");

-                json.WriteNumber(budgets[heapIndex].budget);

+                const VkPhysicalDeviceProperties& deviceProperties = allocator->m_PhysicalDeviceProperties;

+                const VkPhysicalDeviceMemoryProperties& memoryProperties = allocator->m_MemProps;

+

+                json.WriteString("API");

+                json.WriteString("Vulkan");

+

+                json.WriteString("apiVersion");

+                json.BeginString();

+                json.ContinueString(VK_API_VERSION_MAJOR(deviceProperties.apiVersion));

+                json.ContinueString(".");

+                json.ContinueString(VK_API_VERSION_MINOR(deviceProperties.apiVersion));

+                json.ContinueString(".");

+                json.ContinueString(VK_API_VERSION_PATCH(deviceProperties.apiVersion));

+                json.EndString();

+

+                json.WriteString("GPU");

+                json.WriteString(deviceProperties.deviceName);

+                json.WriteString("deviceType");

+                json.WriteNumber(static_cast<uint32_t>(deviceProperties.deviceType));

+

+                json.WriteString("maxMemoryAllocationCount");

+                json.WriteNumber(deviceProperties.limits.maxMemoryAllocationCount);

+                json.WriteString("bufferImageGranularity");

+                json.WriteNumber(deviceProperties.limits.bufferImageGranularity);

+                json.WriteString("nonCoherentAtomSize");

+                json.WriteNumber(deviceProperties.limits.nonCoherentAtomSize);

+

+                json.WriteString("memoryHeapCount");

+                json.WriteNumber(memoryProperties.memoryHeapCount);

+                json.WriteString("memoryTypeCount");

+                json.WriteNumber(memoryProperties.memoryTypeCount);

             }

             json.EndObject();

-

-            if(stats.memoryHeap[heapIndex].statistics.blockCount > 0)

+        }

+        {

+            json.WriteString("Total");

+            VmaPrintDetailedStatistics(json, stats.total);

+        }

+        {

+            json.WriteString("MemoryInfo");

+            json.BeginObject();

             {

-                json.WriteString("Stats");

-                VmaPrintDetailedStatistics(json, stats.memoryHeap[heapIndex]);

-            }

-

-            for(uint32_t typeIndex = 0; typeIndex < allocator->GetMemoryTypeCount(); ++typeIndex)

-            {

-                if(allocator->MemoryTypeIndexToHeapIndex(typeIndex) == heapIndex)

+                for (uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex)

                 {

-                    json.BeginString("Type ");

-                    json.ContinueString(typeIndex);

+                    json.BeginString("Heap ");

+                    json.ContinueString(heapIndex);

                     json.EndString();

-

                     json.BeginObject();

+                    {

+                        const VkMemoryHeap& heapInfo = allocator->m_MemProps.memoryHeaps[heapIndex];

+                        json.WriteString("Flags");

+                        json.BeginArray(true);

+                        {

+                            if (heapInfo.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)

+                                json.WriteString("DEVICE_LOCAL");

+                        #if VMA_VULKAN_VERSION >= 1001000

+                            if (heapInfo.flags & VK_MEMORY_HEAP_MULTI_INSTANCE_BIT)

+                                json.WriteString("MULTI_INSTANCE");

+                        #endif

 

-                    json.WriteString("Flags");

-                    json.BeginArray(true);

-                    VkMemoryPropertyFlags flags = allocator->m_MemProps.memoryTypes[typeIndex].propertyFlags;

-                    if((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0)

-                    {

-                        json.WriteString("DEVICE_LOCAL");

-                    }

-                    if((flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0)

-                    {

-                        json.WriteString("HOST_VISIBLE");

-                    }

-                    if((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0)

-                    {

-                        json.WriteString("HOST_COHERENT");

-                    }

-                    if((flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0)

-                    {

-                        json.WriteString("HOST_CACHED");

-                    }

-                    if((flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) != 0)

-                    {

-                        json.WriteString("LAZILY_ALLOCATED");

-                    }

-#if VMA_VULKAN_VERSION >= 1001000

-                    if((flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0)

-                    {

-                        json.WriteString("PROTECTED");

-                    }

-#endif // #if VMA_VULKAN_VERSION >= 1001000

-#if VK_AMD_device_coherent_memory

-                    if((flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) != 0)

-                    {

-                        json.WriteString("DEVICE_COHERENT");

-                    }

-                    if((flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY) != 0)

-                    {

-                        json.WriteString("DEVICE_UNCACHED");

-                    }

-#endif // #if VK_AMD_device_coherent_memory

-                    json.EndArray();

+                            VkMemoryHeapFlags flags = heapInfo.flags &

+                                ~(VK_MEMORY_HEAP_DEVICE_LOCAL_BIT

+                        #if VMA_VULKAN_VERSION >= 1001000

+                                    | VK_MEMORY_HEAP_MULTI_INSTANCE_BIT

+                        #endif

+                                    );

+                            if (flags != 0)

+                                json.WriteNumber(flags);

+                        }

+                        json.EndArray();

 

-                    if(stats.memoryType[typeIndex].statistics.blockCount > 0)

-                    {

+                        json.WriteString("Size");

+                        json.WriteNumber(heapInfo.size);

+

+                        json.WriteString("Budget");

+                        json.BeginObject();

+                        {

+                            json.WriteString("BudgetBytes");

+                            json.WriteNumber(budgets[heapIndex].budget);

+                            json.WriteString("UsageBytes");

+                            json.WriteNumber(budgets[heapIndex].usage);

+                        }

+                        json.EndObject();

+

                         json.WriteString("Stats");

-                        VmaPrintDetailedStatistics(json, stats.memoryType[typeIndex]);

-                    }

+                        VmaPrintDetailedStatistics(json, stats.memoryHeap[heapIndex]);

 

+                        json.WriteString("MemoryPools");

+                        json.BeginObject();

+                        {

+                            for (uint32_t typeIndex = 0; typeIndex < allocator->GetMemoryTypeCount(); ++typeIndex)

+                            {

+                                if (allocator->MemoryTypeIndexToHeapIndex(typeIndex) == heapIndex)

+                                {

+                                    json.BeginString("Type ");

+                                    json.ContinueString(typeIndex);

+                                    json.EndString();

+                                    json.BeginObject();

+                                    {

+                                        json.WriteString("Flags");

+                                        json.BeginArray(true);

+                                        {

+                                            VkMemoryPropertyFlags flags = allocator->m_MemProps.memoryTypes[typeIndex].propertyFlags;

+                                            if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)

+                                                json.WriteString("DEVICE_LOCAL");

+                                            if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)

+                                                json.WriteString("HOST_VISIBLE");

+                                            if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)

+                                                json.WriteString("HOST_COHERENT");

+                                            if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)

+                                                json.WriteString("HOST_CACHED");

+                                            if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)

+                                                json.WriteString("LAZILY_ALLOCATED");

+                                        #if VMA_VULKAN_VERSION >= 1001000

+                                            if (flags & VK_MEMORY_PROPERTY_PROTECTED_BIT)

+                                                json.WriteString("PROTECTED");

+                                        #endif

+                                        #if VK_AMD_device_coherent_memory

+                                            if (flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY)

+                                                json.WriteString("DEVICE_COHERENT_AMD");

+                                            if (flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY)

+                                                json.WriteString("DEVICE_UNCACHED_AMD");

+                                        #endif

+

+                                            flags &= ~(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT

+                                        #if VMA_VULKAN_VERSION >= 1001000

+                                                | VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT

+                                        #endif

+                                        #if VK_AMD_device_coherent_memory

+                                                | VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY

+                                                | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY

+                                        #endif

+                                                | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT

+                                                | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT

+                                                | VK_MEMORY_PROPERTY_HOST_CACHED_BIT);

+                                            if (flags != 0)

+                                                json.WriteNumber(flags);

+                                        }

+                                        json.EndArray();

+

+                                        json.WriteString("Stats");

+                                        VmaPrintDetailedStatistics(json, stats.memoryType[typeIndex]);

+                                    }

+                                    json.EndObject();

+                                }

+                            }

+

+                        }

+                        json.EndObject();

+                    }

                     json.EndObject();

                 }

             }

-

             json.EndObject();

         }

-        if(detailedMap == VK_TRUE)

-        {

+

+        if (detailedMap == VK_TRUE)

             allocator->PrintDetailedMap(json);

-        }

 

         json.EndObject();

     }

diff --git a/src/Tests.cpp b/src/Tests.cpp
index 37b1042..baee5c4 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -1653,6 +1653,168 @@
     });
 }
 
+
+static void TestJson()
+{
+    wprintf(L"Test JSON\n");
+
+    std::vector<VmaPool> pools;
+    std::vector<VmaAllocation> allocs;
+
+    VmaAllocationCreateInfo allocCreateInfo = {};
+
+    VkBufferCreateInfo buffCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
+    buffCreateInfo.size = 1024;
+    buffCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+    
+    VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+    imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+    imgCreateInfo.extent.depth = 1;
+    imgCreateInfo.mipLevels = 1;
+    imgCreateInfo.arrayLayers = 1;
+    imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+    imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+    imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+    imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+
+    VkMemoryRequirements memReq = {};
+    {
+        VkBuffer dummyBuffer = VK_NULL_HANDLE;
+        TEST(vkCreateBuffer(g_hDevice, &buffCreateInfo, g_Allocs, &dummyBuffer) == VK_SUCCESS && dummyBuffer);
+
+        vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
+        vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
+    }
+
+    // Select if using custom pool or default
+    for (uint8_t poolType = 0; poolType < 2; ++poolType)
+    {
+        // Select different memoryTypes
+        for (uint8_t memType = 0; memType < 2; ++memType)
+        {
+            switch (memType)
+            {
+            case 0:
+                allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
+                allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DONT_BIND_BIT;
+                break;
+            case 1:
+                allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
+                allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DONT_BIND_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
+                break;
+            }
+
+            switch (poolType)
+            {
+            case 0:
+                allocCreateInfo.pool = nullptr;
+                break;
+            case 1:
+            {
+                VmaPoolCreateInfo poolCreateInfo = {};
+                TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &buffCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
+
+                VmaPool pool;
+                TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
+
+                allocCreateInfo.pool = pool;
+                pools.emplace_back(pool);
+                break;
+            }
+            }
+
+            // Select different allocation flags
+            for (uint8_t allocFlag = 0; allocFlag < 2; ++allocFlag)
+            {
+                switch (allocFlag)
+                {
+                case 1:
+                    allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
+                    break;
+                }
+
+                // Select different alloc types (block, buffer, texture, etc.)
+                for (uint8_t allocType = 0; allocType < 4; ++allocType)
+                {
+                    // Select different data stored in the allocation
+                    for (uint8_t data = 0; data < 4; ++data)
+                    {
+                        VmaAllocation alloc = nullptr;
+
+                        switch (allocType)
+                        {
+                        case 0:
+                        {
+                            VmaAllocationCreateInfo localCreateInfo = allocCreateInfo;
+                            switch (memType)
+                            {
+                            case 0:
+                                localCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+                                break;
+                            case 1:
+                                localCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
+                                break;
+                            }
+                            TEST(vmaAllocateMemory(g_hAllocator, &memReq, &localCreateInfo, &alloc, nullptr) == VK_SUCCESS);
+                            break;
+                        }
+                        case 1:
+                        {
+                            VkBuffer buffer;
+                            TEST(vmaCreateBuffer(g_hAllocator, &buffCreateInfo, &allocCreateInfo, &buffer, &alloc, nullptr) == VK_SUCCESS);
+                            vkDestroyBuffer(g_hDevice, buffer, g_Allocs);
+                            break;
+                        }
+                        case 2:
+                        {
+                            imgCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
+                            imgCreateInfo.extent.width = 512;
+                            imgCreateInfo.extent.height = 1;
+                            VkImage image;
+                            TEST(vmaCreateImage(g_hAllocator, &imgCreateInfo, &allocCreateInfo, &image, &alloc, nullptr) == VK_SUCCESS);
+                            vkDestroyImage(g_hDevice, image, g_Allocs);
+                            break;
+                        }
+                        case 3:
+                        {
+                            imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+                            imgCreateInfo.extent.width = 1024;
+                            imgCreateInfo.extent.height = 512;
+                            VkImage image;
+                            TEST(vmaCreateImage(g_hAllocator, &imgCreateInfo, &allocCreateInfo, &image, &alloc, nullptr) == VK_SUCCESS);
+                            vkDestroyImage(g_hDevice, image, g_Allocs);
+                            break;
+                        }
+                        }
+
+                        switch (data)
+                        {
+                        case 1:
+                            vmaSetAllocationUserData(g_hAllocator, alloc, (void*)16112007);
+                            break;
+                        case 2:
+                            vmaSetAllocationName(g_hAllocator, alloc, "SHEPURD");
+                            break;
+                        case 3:
+                            vmaSetAllocationUserData(g_hAllocator, alloc, (void*)26012010);
+                            vmaSetAllocationName(g_hAllocator, alloc, "JOKER");
+                            break;
+                        }
+                        allocs.emplace_back(alloc);
+                    }
+                }
+
+            }
+        }
+    }
+    SaveAllocatorStatsToFile(L"JSON_VULKAN.json");
+
+    for (auto& alloc : allocs)
+        vmaFreeMemory(g_hAllocator, alloc);
+    for (auto& pool : pools)
+        vmaDestroyPool(g_hAllocator, pool);
+}
+
 void TestDefragmentationSimple()
 {
     wprintf(L"Test defragmentation simple\n");
@@ -3154,8 +3316,8 @@
     vmaBuildVirtualBlockStatsString(block, &json, VK_TRUE);
     {
         std::string str(json);
-        TEST( str.find("\"UserData\": \"0000000000000001\"") != std::string::npos );
-        TEST( str.find("\"UserData\": \"0000000000000002\"") != std::string::npos );
+        TEST( str.find("\"CustomData\": \"0000000000000001\"") != std::string::npos );
+        TEST( str.find("\"CustomData\": \"0000000000000002\"") != std::string::npos );
     }
     vmaFreeVirtualBlockStatsString(block, json);
 #endif
@@ -7821,6 +7983,7 @@
     TestDebugMargin();
     TestDebugMarginNotInVirtualAllocator();
 #else
+    TestJson();
     TestBasics();
     TestVirtualBlocks();
     TestVirtualBlocksAlgorithms();
diff --git a/tools/GpuMemDumpVis/GpuMemDumpVis.py b/tools/GpuMemDumpVis/GpuMemDumpVis.py
new file mode 100644
index 0000000..b225306
--- /dev/null
+++ b/tools/GpuMemDumpVis/GpuMemDumpVis.py
@@ -0,0 +1,334 @@
+#
+# Copyright (c) 2018-2022 Advanced Micro Devices, Inc. All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+import argparse
+import json
+from PIL import Image, ImageDraw, ImageFont
+
+
+PROGRAM_VERSION = 'Vulkan/D3D12 Memory Allocator Dump Visualization 3.0.0'
+IMG_WIDTH = 1200
+IMG_MARGIN = 8
+TEXT_MARGIN = 4
+FONT_SIZE = 10
+MAP_SIZE = 24
+COLOR_TEXT_H1 = (0, 0, 0, 255)
+COLOR_TEXT_H2 = (150, 150, 150, 255)
+COLOR_OUTLINE = (155, 155, 155, 255)
+COLOR_OUTLINE_HARD = (0, 0, 0, 255)
+COLOR_GRID_LINE = (224, 224, 224, 255)
+
+currentApi = ""
+data = {}
+
+
+def ParseArgs():
+    argParser = argparse.ArgumentParser(description='Visualization of Vulkan/D3D12 Memory Allocator JSON dump.')
+    argParser.add_argument('DumpFile', help='Path to source JSON file with memory dump created by Vulkan/D3D12 Memory Allocator library')
+    argParser.add_argument('-v', '--version', action='version', version=PROGRAM_VERSION)
+    argParser.add_argument('-o', '--output', required=True, help='Path to destination image file (e.g. PNG)')
+    return argParser.parse_args()
+
+def GetDataForMemoryPool(poolTypeName):
+    global data
+    if poolTypeName in data:
+        return data[poolTypeName]
+    else:
+        newPoolData = {'DedicatedAllocations':[], 'Blocks':[], 'CustomPools':{}}
+        data[poolTypeName] = newPoolData
+        return newPoolData
+
+def ProcessBlock(poolData, block):
+    blockInfo = {'ID': block[0], 'Size': int(block[1]['TotalBytes']), 'Suballocations':[]}
+    for alloc in block[1]['Suballocations']:
+        allocData = {'Type': alloc['Type'], 'Size': int(alloc['Size']), 'Usage': int(alloc['Usage']) if 'Usage' in alloc else 0 }
+        blockInfo['Suballocations'].append(allocData)
+    poolData['Blocks'].append(blockInfo)
+    
+def IsDataEmpty():
+    global data
+    for poolData in data.values():
+        if len(poolData['DedicatedAllocations']) > 0:
+            return False
+        if len(poolData['Blocks']) > 0:
+            return False
+        for customPool in poolData['CustomPools'].values():
+            if len(customPool['Blocks']) > 0:
+                return False
+            if len(customPool['DedicatedAllocations']) > 0:
+                return False
+    return True
+
+def RemoveEmptyType():
+    global data
+    for poolType in list(data.keys()):
+        if len(data[poolType]['DedicatedAllocations']) > 0:
+           continue
+        if len(data[poolType]['Blocks']) > 0:
+            continue
+        empty = True
+        for customPool in data[poolType]['CustomPools'].values():
+            if len(data[poolType]['Blocks']) > 0:
+                empty = False
+                break
+            if len(data[poolType]['DedicatedAllocations']) > 0:
+                empty = False
+                break
+        if empty:
+            del data[poolType]
+
+# Returns tuple:
+# [0] image height : integer
+# [1] pixels per byte : float
+def CalcParams():
+    global data
+    height = IMG_MARGIN
+    height += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes
+    maxBlockSize = 0
+    # Get height occupied by every memory pool
+    for poolData in data.values():
+        height += IMG_MARGIN + FONT_SIZE
+        height += len(poolData['DedicatedAllocations']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
+        height += len(poolData['Blocks']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
+        # Get longest block size
+        for dedicatedAlloc in poolData['DedicatedAllocations']:
+            maxBlockSize = max(maxBlockSize, dedicatedAlloc['Size'])
+        for block in poolData['Blocks']:
+            maxBlockSize = max(maxBlockSize, block['Size'])
+        # Same for custom pools
+        for customPoolData in poolData['CustomPools'].values():
+            height += len(customPoolData['DedicatedAllocations']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
+            height += len(customPoolData['Blocks']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
+            height += FONT_SIZE * 2 + IMG_MARGIN if len(customPoolData['DedicatedAllocations']) == 0 else 0
+            # Get longest block size
+            for dedicatedAlloc in customPoolData['DedicatedAllocations']:
+                maxBlockSize = max(maxBlockSize, dedicatedAlloc['Size'])
+            for block in customPoolData['Blocks']:
+                maxBlockSize = max(maxBlockSize, block['Size'])
+
+    return height + FONT_SIZE, (IMG_WIDTH - IMG_MARGIN * 2) / float(maxBlockSize)
+
+def BytesToStr(bytes):
+    if bytes < 1024:
+        return "%d B" % bytes
+    bytes /= 1024
+    if bytes < 1024:
+        return "%d KiB" % bytes
+    bytes /= 1024
+    if bytes < 1024:
+        return "%d MiB" % bytes
+    bytes /= 1024
+    return "%d GiB" % bytes
+
+def TypeToColor(type, usage):
+    global currentApi
+    if type == 'FREE':
+        return 220, 220, 220, 255
+    elif type == 'UNKNOWN':
+        return 175, 175, 175, 255 # Gray
+
+    if currentApi == 'Vulkan':
+        if type == 'BUFFER':
+            if (usage & 0x1C0) != 0: # INDIRECT_BUFFER | VERTEX_BUFFER | INDEX_BUFFER
+                return 255, 148, 148, 255 # Red
+            elif (usage & 0x28) != 0: # STORAGE_BUFFER | STORAGE_TEXEL_BUFFER
+                return 255, 187, 121, 255 # Orange
+            elif (usage & 0x14) != 0: # UNIFORM_BUFFER | UNIFORM_TEXEL_BUFFER
+                return 255, 255, 0, 255 # Yellow
+            else:
+                return 255, 255, 165, 255 # Light yellow
+        elif type == 'IMAGE_OPTIMAL':
+            if (usage & 0x20) != 0: # DEPTH_STENCIL_ATTACHMENT
+                return 246, 128, 255, 255 # Pink
+            elif (usage & 0xD0) != 0: # INPUT_ATTACHMENT | TRANSIENT_ATTACHMENT | COLOR_ATTACHMENT
+                return 179, 179, 255, 255 # Blue
+            elif (usage & 0x4) != 0: # SAMPLED
+                return 0, 255, 255, 255 # Aqua
+            else:
+                return 183, 255, 255, 255 # Light aqua
+        elif type == 'IMAGE_LINEAR' :
+            return 0, 255, 0, 255 # Green
+        elif type == 'IMAGE_UNKNOWN':
+            return 0, 255, 164, 255 # Green/aqua
+    elif currentApi == 'Direct3D 12':
+        if type == 'BUFFER':
+                return 255, 255, 165, 255 # Light yellow
+        elif type == 'TEXTURE1D' or type == 'TEXTURE2D' or type == 'TEXTURE3D':
+            if (usage & 0x2) != 0: # D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
+                return 246, 128, 255, 255 # Pink
+            elif (usage & 0x5) != 0: # D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
+                return 179, 179, 255, 255 # Blue
+            elif (usage & 0x8) == 0: # Not having D3D12_RESOURCE_FLAG_DENY_SHARED_RESOURCE
+                return 0, 255, 255, 255 # Aqua
+            else:
+                return 183, 255, 255, 255 # Light aqua
+    else:
+        print("Unknown graphics API!")
+        exit(1)
+    assert False
+    return 0, 0, 0, 255
+
+def DrawBlock(draw, y, block, pixelsPerByte):
+    sizePixels = int(block['Size'] * pixelsPerByte)
+    draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + sizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0), outline=None)
+    byte = 0
+    x = 0
+    lastHardLineX = -1
+    for alloc in block['Suballocations']:
+        byteEnd = byte + alloc['Size']
+        xEnd = int(byteEnd * pixelsPerByte)
+        if alloc['Type'] != 'FREE':
+            if xEnd > x + 1:
+                draw.rectangle([IMG_MARGIN + x, y, IMG_MARGIN + xEnd, y + MAP_SIZE], fill=TypeToColor(alloc['Type'], alloc['Usage']), outline=COLOR_OUTLINE)
+                # Hard line was been overwritten by rectangle outline: redraw it.
+                if lastHardLineX == x:
+                    draw.line([IMG_MARGIN + x, y, IMG_MARGIN + x, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)
+            else:
+                draw.line([IMG_MARGIN + x, y, IMG_MARGIN + x, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)
+                lastHardLineX = x
+        byte = byteEnd
+        x = xEnd
+
+def DrawDedicatedAllocationBlock(draw, y, dedicatedAlloc, pixelsPerByte): 
+    sizePixels = int(dedicatedAlloc['Size'] * pixelsPerByte)
+    draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + sizePixels, y + MAP_SIZE], fill=TypeToColor(dedicatedAlloc['Type'], dedicatedAlloc['Usage']), outline=COLOR_OUTLINE)
+
+
+if __name__ == '__main__':
+    args = ParseArgs()
+    jsonSrc = json.load(open(args.DumpFile, 'rb'))
+ 
+    if 'General' in jsonSrc:
+        currentApi = jsonSrc['General']['API']
+    else:
+        print("Wrong JSON format, cannot determine graphics API!")
+        exit(1)
+        
+    # Process default pools
+    if 'DefaultPools' in jsonSrc:
+        for memoryPool in jsonSrc['DefaultPools'].items():
+            poolData = GetDataForMemoryPool(memoryPool[0])
+            # Get dedicated allocations
+            for dedicatedAlloc in memoryPool[1]['DedicatedAllocations']:
+                allocData = {'Type': dedicatedAlloc['Type'], 'Size': int(dedicatedAlloc['Size']), 'Usage': int(dedicatedAlloc['Usage'])}
+                poolData['DedicatedAllocations'].append(allocData)
+            # Get allocations in block vectors
+            for block in memoryPool[1]['Blocks'].items():
+                ProcessBlock(poolData, block)
+    # Process custom pools
+    if 'CustomPools' in jsonSrc:
+        for memoryPool in jsonSrc['CustomPools'].items():
+            poolData = GetDataForMemoryPool(memoryPool[0])
+            for pool in memoryPool[1]:
+                poolName = pool['Name']
+                poolData['CustomPools'][poolName] = {'DedicatedAllocations':[], 'Blocks':[]}
+                # Get dedicated allocations
+                for dedicatedAlloc in pool['DedicatedAllocations']:
+                    allocData = {'Type': dedicatedAlloc['Type'], 'Size': int(dedicatedAlloc['Size']), 'Usage': int(dedicatedAlloc['Usage'])}
+                    poolData['CustomPools'][poolName]['DedicatedAllocations'].append(allocData)
+                # Get allocations in block vectors
+                for block in pool['Blocks'].items():
+                    ProcessBlock(poolData['CustomPools'][poolName], block)
+
+    if IsDataEmpty():
+        print("There is nothing to put on the image. Please make sure you generated the stats string with detailed map enabled.")
+        exit(1)
+    RemoveEmptyType()
+    # Calculate dimmensions and create data image       
+    imgHeight, pixelsPerByte = CalcParams()
+    img = Image.new('RGB', (IMG_WIDTH, imgHeight), 'white')
+    draw = ImageDraw.Draw(img)
+    try:
+        font = ImageFont.truetype('segoeuib.ttf')
+    except:
+        font = ImageFont.load_default()
+
+    # Draw grid lines
+    bytesBetweenGridLines = 32
+    while bytesBetweenGridLines * pixelsPerByte < 64:
+        bytesBetweenGridLines *= 2
+    byte = 0
+    y = IMG_MARGIN
+    while True:
+        x = int(byte * pixelsPerByte)
+        if x > IMG_WIDTH - 2 * IMG_MARGIN:
+            break
+        draw.line([x + IMG_MARGIN, 0, x + IMG_MARGIN, imgHeight], fill=COLOR_GRID_LINE)
+        if byte == 0:
+            draw.text((x + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font)
+        else:
+            text = BytesToStr(byte)
+            textSize = draw.textsize(text, font=font)
+            draw.text((x + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font)
+        byte += bytesBetweenGridLines
+    y += FONT_SIZE + IMG_MARGIN
+    
+    # Draw main content
+    for memType in sorted(data.keys()):
+        memPoolData = data[memType]
+        draw.text((IMG_MARGIN, y), "Memory pool %s" % memType, fill=COLOR_TEXT_H1, font=font)
+        y += FONT_SIZE + IMG_MARGIN
+        # Draw block vectors
+        for block in memPoolData['Blocks']:
+            draw.text((IMG_MARGIN, y), "Default pool block %s" % block['ID'], fill=COLOR_TEXT_H2, font=font)
+            y += FONT_SIZE + IMG_MARGIN
+            DrawBlock(draw, y, block, pixelsPerByte)
+            y += MAP_SIZE + IMG_MARGIN
+        index = 0
+        # Draw dedicated allocations
+        for dedicatedAlloc in memPoolData['DedicatedAllocations']:
+            draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font)
+            y += FONT_SIZE + IMG_MARGIN
+            DrawDedicatedAllocationBlock(draw, y, dedicatedAlloc, pixelsPerByte)
+            y += MAP_SIZE + IMG_MARGIN
+            index += 1
+        for poolName, pool in memPoolData['CustomPools'].items():
+            for block in pool['Blocks']:
+                draw.text((IMG_MARGIN, y), "Custom pool %s block %s" % (poolName, block['ID']), fill=COLOR_TEXT_H2, font=font)
+                y += FONT_SIZE + IMG_MARGIN
+                DrawBlock(draw, y, block, pixelsPerByte)
+                y += 2 * (FONT_SIZE + IMG_MARGIN)
+            index = 0
+            for dedicatedAlloc in pool['DedicatedAllocations']:
+                draw.text((IMG_MARGIN, y), "Custom pool %s dedicated allocation %d" % (poolName, index), fill=COLOR_TEXT_H2, font=font)
+                y += FONT_SIZE + IMG_MARGIN
+                DrawDedicatedAllocationBlock(draw, y, dedicatedAlloc, pixelsPerByte)
+                y += MAP_SIZE + IMG_MARGIN
+                index += 1
+    del draw
+    img.save(args.output)
+
+"""
+Main data structure - variable `data` - is a dictionary. Key is string - memory type name. Value is dictionary of:
+- Fixed key 'DedicatedAllocations'. Value is list of objects, each containing dictionary with:
+    - Fixed key 'Type'. Value is string.
+    - Fixed key 'Size'. Value is int.
+    - Fixed key 'Usage'. Value is int.
+- Fixed key 'Blocks'. Value is list of objects, each containing dictionary with:
+    - Fixed key 'ID'. Value is int.
+    - Fixed key 'Size'. Value is int.
+    - Fixed key 'Suballocations'. Value is list of objects as above.
+- Fixed key 'CustomPools'. Value is dictionary.
+  - Key is string with pool ID/name. Value is a dictionary with:
+    - Fixed key 'DedicatedAllocations'. Value is list of objects as above.
+    - Fixed key 'Blocks'. Value is a list of objects representing memory blocks as above.
+"""
diff --git a/tools/GpuMemDumpVis/README.md b/tools/GpuMemDumpVis/README.md
new file mode 100644
index 0000000..2bc8249
--- /dev/null
+++ b/tools/GpuMemDumpVis/README.md
@@ -0,0 +1,45 @@
+# GpuMemDumpVis
+
+Vulkan/D3D12 Memory Allocator Dump Visualization. 
+It is an auxiliary tool that can visualize internal state of [Vulkan Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) and
+[D3D12 Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator) libraries on a picture.
+It is a Python script that must be launched from command line with appropriate parameters.
+
+## Requirements
+
+- Python 3 installed
+- [Pillow](http://python-pillow.org/) - Python Imaging Library (Fork) installed
+
+## Usage
+
+```
+python GpuMemDumpVis.py -o OUTPUT_FILE INPUT_FILE
+```
+
+* `INPUT_FILE` - path to source file to be read, containing dump of internal state of the VMA/D3D12MA library in JSON format (encoding: UTF-8/UTF-16), generated using `vmaBuildStatsString()` and `D3D12MA::Allocator::BuildStatsString()` functions.
+* `OUTPUT_FILE` - path to destination file to be written that will contain generated image. Image format is automatically recognized based on file extension. List of supported formats can be found [here](http://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html) and includes: BMP, GIF, JPEG, PNG, TGA.
+
+You can also use typical options:
+
+* `-h` - to see help on command line syntax
+* `-v` - to see program version number
+
+## Example output
+
+![Example output](README_files/ExampleOutput.png "Example output")
+
+## Legend
+
+* ![Free space](README_files/Legend_Bkg.png "Free space") Light gray without border - a space in Vulkan device memory block unused by any allocation.
+* ![Buffer 1](README_files/Legend_Buffer_1.png "Buffer 1") Buffer with usage containing INDIRECT_BUFFER, VERTEX_BUFFER, or INDEX_BUFFER (Vulkan).
+* ![Buffer 2](README_files/Legend_Buffer_2.png "Buffer 2") Buffer with usage containing STORAGE_BUFFER or STORAGE_TEXEL_BUFFER (Vulkan).
+* ![Buffer 3](README_files/Legend_Buffer_3.png "Buffer 3") Buffer with usage containing UNIFORM_BUFFER or UNIFORM_TEXEL_BUFFER (Vulkan).
+* ![Buffer 4](README_files/Legend_Buffer_4.png "Buffer 4") Other buffer.
+* ![Image 1](README_files/Legend_Image_1.png "Image 1") Image with OPTIMAL tiling and usage containing DEPTH_STENCIL_ATTACHMENT (Vulkan) or a texture with usage containing D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL (D3D12).
+* ![Image 2](README_files/Legend_Image_2.png "Image 2") Image with OPTIMAL tiling and usage containing INPUT_ATTACHMENT, TRANSIENT_ATTACHMENT or COLOR_ATTACHMENT (Vulkan), or a texture with usage containing D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET or D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS (D3D12).
+* ![Image 3](README_files/Legend_Image_3.png "Image 3") Image with OPTIMAL tiling and usage containing SAMPLED (Vulkan) or a texture with usage not containing D3D12_RESOURCE_FLAG_DENY_SHARED_RESOURCE (D3D12).
+* ![Image 4](README_files/Legend_Image_4.png "Image 4") Other image with OPTIMAL tiling (Vulkan) or a texture (D3D12).
+* ![Image Linear](README_files/Legend_Image_Linear.png "Image Linear") Image with LINEAR tiling (Vulkan).
+* ![Image Unknown](README_files/Legend_Image_Unknown.png "Image Unknown") Image with tiling unknown to the allocator (Vulkan).
+* ![Unknown](README_files/Legend_Unknown.png "Unknown") Allocation of unknown type.
+* ![Details](README_files/Legend_Details.png "Details") Black bar - one or more allocations of any kind too small to be visualized as filled rectangles.
diff --git a/tools/VmaDumpVis/README_files/ExampleOutput.png b/tools/GpuMemDumpVis/README_files/ExampleOutput.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/ExampleOutput.png
rename to tools/GpuMemDumpVis/README_files/ExampleOutput.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Bkg.png b/tools/GpuMemDumpVis/README_files/Legend_Bkg.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Bkg.png
rename to tools/GpuMemDumpVis/README_files/Legend_Bkg.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Buffer_1.png b/tools/GpuMemDumpVis/README_files/Legend_Buffer_1.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Buffer_1.png
rename to tools/GpuMemDumpVis/README_files/Legend_Buffer_1.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Buffer_2.png b/tools/GpuMemDumpVis/README_files/Legend_Buffer_2.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Buffer_2.png
rename to tools/GpuMemDumpVis/README_files/Legend_Buffer_2.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Buffer_3.png b/tools/GpuMemDumpVis/README_files/Legend_Buffer_3.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Buffer_3.png
rename to tools/GpuMemDumpVis/README_files/Legend_Buffer_3.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Buffer_4.png b/tools/GpuMemDumpVis/README_files/Legend_Buffer_4.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Buffer_4.png
rename to tools/GpuMemDumpVis/README_files/Legend_Buffer_4.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Details.png b/tools/GpuMemDumpVis/README_files/Legend_Details.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Details.png
rename to tools/GpuMemDumpVis/README_files/Legend_Details.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Image_1.png b/tools/GpuMemDumpVis/README_files/Legend_Image_1.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Image_1.png
rename to tools/GpuMemDumpVis/README_files/Legend_Image_1.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Image_2.png b/tools/GpuMemDumpVis/README_files/Legend_Image_2.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Image_2.png
rename to tools/GpuMemDumpVis/README_files/Legend_Image_2.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Image_3.png b/tools/GpuMemDumpVis/README_files/Legend_Image_3.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Image_3.png
rename to tools/GpuMemDumpVis/README_files/Legend_Image_3.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Image_4.png b/tools/GpuMemDumpVis/README_files/Legend_Image_4.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Image_4.png
rename to tools/GpuMemDumpVis/README_files/Legend_Image_4.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Image_Linear.png b/tools/GpuMemDumpVis/README_files/Legend_Image_Linear.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Image_Linear.png
rename to tools/GpuMemDumpVis/README_files/Legend_Image_Linear.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Image_Unknown.png b/tools/GpuMemDumpVis/README_files/Legend_Image_Unknown.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Image_Unknown.png
rename to tools/GpuMemDumpVis/README_files/Legend_Image_Unknown.png
Binary files differ
diff --git a/tools/VmaDumpVis/README_files/Legend_Unknown.png b/tools/GpuMemDumpVis/README_files/Legend_Unknown.png
similarity index 100%
rename from tools/VmaDumpVis/README_files/Legend_Unknown.png
rename to tools/GpuMemDumpVis/README_files/Legend_Unknown.png
Binary files differ
diff --git a/tools/GpuMemDumpVis/Sample.json b/tools/GpuMemDumpVis/Sample.json
new file mode 100644
index 0000000..d12c8b3
--- /dev/null
+++ b/tools/GpuMemDumpVis/Sample.json
@@ -0,0 +1,426 @@
+{
+  "General": {
+    "API": "Vulkan", 
+    "apiVersion": "1.3.203", 
+    "GPU": "AMD Radeon RX 6600 XT", 
+    "deviceType": 2, 
+    "maxMemoryAllocationCount": 4096, 
+    "bufferImageGranularity": 1, 
+    "nonCoherentAtomSize": 128, 
+    "memoryHeapCount": 2, 
+    "memoryTypeCount": 8
+  }, 
+  "Total": {
+    "BlockCount": 69, 
+    "BlockBytes": 201392128, 
+    "AllocationCount": 132, 
+    "AllocationBytes": 73401500, 
+    "UnusedRangeCount": 11, 
+    "AllocationSizeMin": 60, 
+    "AllocationSizeMax": 6095200, 
+    "UnusedRangeSizeMin": 196, 
+    "UnusedRangeSizeMax": 33550336
+  }, 
+  "MemoryInfo": {
+    "Heap 0": {
+      "Flags": [], 
+      "Size": 16862150656, 
+      "Budget": {
+        "BudgetBytes": 16325847040, 
+        "UsageBytes": 122392576
+      }, 
+      "Stats": {
+        "BlockCount": 35, 
+        "BlockBytes": 117473280, 
+        "AllocationCount": 64, 
+        "AllocationBytes": 33619968, 
+        "UnusedRangeCount": 5, 
+        "AllocationSizeMin": 1024, 
+        "AllocationSizeMax": 2097152, 
+        "UnusedRangeSizeMin": 49152, 
+        "UnusedRangeSizeMax": 33550336
+      }, 
+      "MemoryPools": {
+        "Type 1": {
+          "Flags": ["HOST_VISIBLE", "HOST_COHERENT"], 
+          "Stats": {
+            "BlockCount": 5, 
+            "BlockBytes": 33558528, 
+            "AllocationCount": 8, 
+            "AllocationBytes": 8192, 
+            "UnusedRangeCount": 1, 
+            "AllocationSizeMin": 1024, 
+            "AllocationSizeMax": 1024
+          }
+        }, 
+        "Type 3": {
+          "Flags": ["HOST_VISIBLE", "HOST_COHERENT", "HOST_CACHED"], 
+          "Stats": {
+            "BlockCount": 30, 
+            "BlockBytes": 83914752, 
+            "AllocationCount": 56, 
+            "AllocationBytes": 33611776, 
+            "UnusedRangeCount": 4, 
+            "AllocationSizeMin": 1024, 
+            "AllocationSizeMax": 2097152, 
+            "UnusedRangeSizeMin": 49152, 
+            "UnusedRangeSizeMax": 25100288
+          }
+        }, 
+        "Type 5": {
+          "Flags": ["HOST_VISIBLE", "HOST_COHERENT", "DEVICE_COHERENT_AMD", "DEVICE_UNCACHED_AMD"], 
+          "Stats": {
+            "BlockCount": 0, 
+            "BlockBytes": 0, 
+            "AllocationCount": 0, 
+            "AllocationBytes": 0, 
+            "UnusedRangeCount": 0
+          }
+        }, 
+        "Type 7": {
+          "Flags": ["HOST_VISIBLE", "HOST_COHERENT", "HOST_CACHED", "DEVICE_COHERENT_AMD", "DEVICE_UNCACHED_AMD"], 
+          "Stats": {
+            "BlockCount": 0, 
+            "BlockBytes": 0, 
+            "AllocationCount": 0, 
+            "AllocationBytes": 0, 
+            "UnusedRangeCount": 0
+          }
+        }
+      }
+    }, 
+    "Heap 1": {
+      "Flags": ["DEVICE_LOCAL", "MULTI_INSTANCE"], 
+      "Size": 8573157376, 
+      "Budget": {
+        "BudgetBytes": 7737008128, 
+        "UsageBytes": 155025408
+      }, 
+      "Stats": {
+        "BlockCount": 34, 
+        "BlockBytes": 83918848, 
+        "AllocationCount": 68, 
+        "AllocationBytes": 39781532, 
+        "UnusedRangeCount": 6, 
+        "AllocationSizeMin": 60, 
+        "AllocationSizeMax": 6095200, 
+        "UnusedRangeSizeMin": 196, 
+        "UnusedRangeSizeMax": 25100288
+      }, 
+      "MemoryPools": {
+        "Type 0": {
+          "Flags": ["DEVICE_LOCAL"], 
+          "Stats": {
+            "BlockCount": 34, 
+            "BlockBytes": 83918848, 
+            "AllocationCount": 68, 
+            "AllocationBytes": 39781532, 
+            "UnusedRangeCount": 6, 
+            "AllocationSizeMin": 60, 
+            "AllocationSizeMax": 6095200, 
+            "UnusedRangeSizeMin": 196, 
+            "UnusedRangeSizeMax": 25100288
+          }
+        }, 
+        "Type 2": {
+          "Flags": ["DEVICE_LOCAL", "HOST_VISIBLE", "HOST_COHERENT"], 
+          "Stats": {
+            "BlockCount": 0, 
+            "BlockBytes": 0, 
+            "AllocationCount": 0, 
+            "AllocationBytes": 0, 
+            "UnusedRangeCount": 0
+          }
+        }, 
+        "Type 4": {
+          "Flags": ["DEVICE_LOCAL", "DEVICE_COHERENT_AMD", "DEVICE_UNCACHED_AMD"], 
+          "Stats": {
+            "BlockCount": 0, 
+            "BlockBytes": 0, 
+            "AllocationCount": 0, 
+            "AllocationBytes": 0, 
+            "UnusedRangeCount": 0
+          }
+        }, 
+        "Type 6": {
+          "Flags": ["DEVICE_LOCAL", "HOST_VISIBLE", "HOST_COHERENT", "DEVICE_COHERENT_AMD", "DEVICE_UNCACHED_AMD"], 
+          "Stats": {
+            "BlockCount": 0, 
+            "BlockBytes": 0, 
+            "AllocationCount": 0, 
+            "AllocationBytes": 0, 
+            "UnusedRangeCount": 0
+          }
+        }
+      }
+    }
+  }, 
+  "DefaultPools": {
+    "Type 0": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+        "0": {
+          "MapRefCount": 0, 
+          "TotalBytes": 33554432, 
+          "UnusedBytes": 18987876, 
+          "Allocations": 20, 
+          "UnusedRanges": 4, 
+          "Suballocations": [
+            {"Offset": 0, "Type": "IMAGE_OPTIMAL", "Size": 65536, "Usage": 6}, 
+            {"Offset": 65536, "Type": "BUFFER", "Size": 768, "Usage": 130}, 
+            {"Offset": 66304, "Type": "BUFFER", "Size": 60, "Usage": 66}, 
+            {"Offset": 66364, "Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+            {"Offset": 67388, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 68412, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+            {"Offset": 69436, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 70460, "Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+            {"Offset": 71484, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 72508, "Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+            {"Offset": 73532, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 74556, "Type": "FREE", "Size": 196}, 
+            {"Offset": 74752, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+            {"Offset": 76800, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 78848, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+            {"Offset": 80896, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 82944, "Type": "FREE", "Size": 48128}, 
+            {"Offset": 131072, "Type": "IMAGE_OPTIMAL", "Size": 6095200, "Usage": 32}, 
+            {"Offset": 6226272, "Type": "FREE", "Size": 65184}, 
+            {"Offset": 6291456, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+            {"Offset": 8388608, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 10485760, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+            {"Offset": 12582912, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 14680064, "Type": "FREE", "Size": 18874368}
+          ]
+        }
+      }, 
+      "DedicatedAllocations": [
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}
+      ]
+    }, 
+    "Type 1": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+        "0": {
+          "MapRefCount": 0, 
+          "TotalBytes": 33554432, 
+          "UnusedBytes": 33550336, 
+          "Allocations": 4, 
+          "UnusedRanges": 1, 
+          "Suballocations": [
+            {"Offset": 0, "Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+            {"Offset": 1024, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 2048, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+            {"Offset": 3072, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 4096, "Type": "FREE", "Size": 33550336}
+          ]
+        }
+      }, 
+      "DedicatedAllocations": [
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+        {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}
+      ]
+    }, 
+    "Type 2": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+      }, 
+      "DedicatedAllocations": [
+      ]
+    }, 
+    "Type 3": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+        "0": {
+          "MapRefCount": 0, 
+          "TotalBytes": 33554432, 
+          "UnusedBytes": 25153536, 
+          "Allocations": 12, 
+          "UnusedRanges": 2, 
+          "Suballocations": [
+            {"Offset": 0, "Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+            {"Offset": 1024, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 2048, "Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+            {"Offset": 3072, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 4096, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+            {"Offset": 6144, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 8192, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+            {"Offset": 10240, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 12288, "Type": "FREE", "Size": 53248}, 
+            {"Offset": 65536, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+            {"Offset": 2162688, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+            {"Offset": 4259840, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+            {"Offset": 6356992, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+            {"Offset": 8454144, "Type": "FREE", "Size": 25100288}
+          ]
+        }
+      }, 
+      "DedicatedAllocations": [
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+        {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+        {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+        {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}
+      ]
+    }, 
+    "Type 4": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+      }, 
+      "DedicatedAllocations": [
+      ]
+    }, 
+    "Type 5": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+      }, 
+      "DedicatedAllocations": [
+      ]
+    }, 
+    "Type 6": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+      }, 
+      "DedicatedAllocations": [
+      ]
+    }, 
+    "Type 7": {
+      "PreferredBlockSize": 268435456, 
+      "Blocks": {
+      }, 
+      "DedicatedAllocations": [
+      ]
+    }
+  }, 
+  "CustomPools": {
+    "Type 0": [
+      {
+        "Name": "0", 
+        "PreferredBlockSize": 268435456, 
+        "Blocks": {
+          "0": {
+            "MapRefCount": 0, 
+            "TotalBytes": 33554432, 
+            "UnusedBytes": 25149440, 
+            "Allocations": 16, 
+            "UnusedRanges": 2, 
+            "Suballocations": [
+              {"Offset": 0, "Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+              {"Offset": 1024, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 2048, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+              {"Offset": 3072, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 4096, "Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+              {"Offset": 5120, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 6144, "Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+              {"Offset": 7168, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 8192, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+              {"Offset": 10240, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 12288, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+              {"Offset": 14336, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 16384, "Type": "FREE", "Size": 49152}, 
+              {"Offset": 65536, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+              {"Offset": 2162688, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 4259840, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+              {"Offset": 6356992, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 8454144, "Type": "FREE", "Size": 25100288}
+            ]
+          }
+        }, 
+        "DedicatedAllocations": [
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}
+        ]
+      }
+    ], 
+    "Type 3": [
+      {
+        "Name": "0", 
+        "PreferredBlockSize": 268435456, 
+        "Blocks": {
+          "0": {
+            "MapRefCount": 0, 
+            "TotalBytes": 33554432, 
+            "UnusedBytes": 25149440, 
+            "Allocations": 16, 
+            "UnusedRanges": 2, 
+            "Suballocations": [
+              {"Offset": 0, "Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+              {"Offset": 1024, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 2048, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+              {"Offset": 3072, "Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 4096, "Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+              {"Offset": 5120, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 6144, "Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+              {"Offset": 7168, "Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 8192, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+              {"Offset": 10240, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 12288, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+              {"Offset": 14336, "Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 16384, "Type": "FREE", "Size": 49152}, 
+              {"Offset": 65536, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+              {"Offset": 2162688, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+              {"Offset": 4259840, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+              {"Offset": 6356992, "Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+              {"Offset": 8454144, "Type": "FREE", "Size": 25100288}
+            ]
+          }
+        }, 
+        "DedicatedAllocations": [
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0}, 
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "0000000000F5D987"}, 
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "Name": "SHEPURD"}, 
+          {"Type": "UNKNOWN", "Size": 1024, "Usage": 0, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "0000000000F5D987"}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3, "Name": "SHEPURD"}, 
+          {"Type": "BUFFER", "Size": 1024, "Usage": 3, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "Name": "SHEPURD"}, 
+          {"Type": "IMAGE_LINEAR", "Size": 2048, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "0000000000F5D987"}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "Name": "SHEPURD"}, 
+          {"Type": "IMAGE_OPTIMAL", "Size": 2097152, "Usage": 7, "CustomData": "00000000018CE96A", "Name": "JOKER"}
+        ]
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tools/VmaDumpVis/README.md b/tools/VmaDumpVis/README.md
deleted file mode 100644
index f238001..0000000
--- a/tools/VmaDumpVis/README.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# VMA Dump Vis
-
-Vulkan Memory Allocator Dump Visualization. It is an auxiliary tool that can visualize internal state of [Vulkan Memory Allocator](../../README.md) library on a picture. It is a Python script that must be launched from command line with appropriate parameters.
-
-## Requirements
-
-- Python 3 installed
-- [Pillow](http://python-pillow.org/) - Python Imaging Library (Fork) installed
-
-## Usage
-
-```
-python VmaDumpVis.py -o OUTPUT_FILE INPUT_FILE
-```
-
-* `INPUT_FILE` - path to source file to be read, containing dump of internal state of the VMA library in JSON format (encoding: UTF-8), generated using `vmaBuildStatsString()` function.
-* `OUTPUT_FILE` - path to destination file to be written that will contain generated image. Image format is automatically recognized based on file extension. List of supported formats can be found [here](http://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html) and includes: BMP, GIF, JPEG, PNG, TGA.
-
-You can also use typical options:
-
-* `-h` - to see help on command line syntax
-* `-v` - to see program version number
-
-## Example output
-
-![Example output](README_files/ExampleOutput.png "Example output")
-
-## Legend
-
-* ![Free space](README_files/Legend_Bkg.png "Free space") Light gray without border - a space in Vulkan device memory block unused by any allocation.
-* ![Buffer 1](README_files/Legend_Buffer_1.png "Buffer 1") Buffer with usage containing INDIRECT_BUFFER, VERTEX_BUFFER, or INDEX_BUFFER.
-* ![Buffer 2](README_files/Legend_Buffer_2.png "Buffer 2") Buffer with usage containing STORAGE_BUFFER or STORAGE_TEXEL_BUFFER.
-* ![Buffer 3](README_files/Legend_Buffer_3.png "Buffer 3") Buffer with usage containing UNIFORM_BUFFER or UNIFORM_TEXEL_BUFFER.
-* ![Buffer 4](README_files/Legend_Buffer_4.png "Buffer 4") Other buffer.
-* ![Image 1](README_files/Legend_Image_1.png "Image 1") Image with OPTIMAL tiling and usage containing DEPTH_STENCIL_ATTACHMENT.
-* ![Image 2](README_files/Legend_Image_2.png "Image 2") Image with OPTIMAL tiling and usage containing INPUT_ATTACHMENT, TRANSIENT_ATTACHMENT, or COLOR_ATTACHMENT.
-* ![Image 3](README_files/Legend_Image_3.png "Image 3") Image with OPTIMAL tiling and usage containing SAMPLED.
-* ![Image 4](README_files/Legend_Image_4.png "Image 4") Other image with OPTIMAL tiling.
-* ![Image Linear](README_files/Legend_Image_Linear.png "Image Linear") Image with LINEAR tiling.
-* ![Image Unknown](README_files/Legend_Image_Unknown.png "Image Unknown") Image with tiling unknown to the allocator.
-* ![Unknown](README_files/Legend_Unknown.png "Unknown") Allocation of unknown type.
-* ![Details](README_files/Legend_Details.png "Details") Black bar - one or more allocations of any kind too small to be visualized as filled rectangles.
diff --git a/tools/VmaDumpVis/Sample.json b/tools/VmaDumpVis/Sample.json
deleted file mode 100644
index 34698a3..0000000
--- a/tools/VmaDumpVis/Sample.json
+++ /dev/null
@@ -1,102 +0,0 @@
-{
-  "Total": {
-    "Blocks": 2,
-    "Allocations": 4,
-    "UnusedRanges": 3,
-    "UsedBytes": 8062124,
-    "UnusedBytes": 59046740,
-    "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760},
-    "UnusedRangeSize": {"Min": 64708, "Avg": 19682247, "Max": 33554432}
-  },
-  "Heap 0": {
-    "Size": 8304721920,
-    "Flags": ["DEVICE_LOCAL"],
-    "Stats": {
-      "Blocks": 1,
-      "Allocations": 4,
-      "UnusedRanges": 2,
-      "UsedBytes": 8062124,
-      "UnusedBytes": 25492308,
-      "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760},
-      "UnusedRangeSize": {"Min": 64708, "Avg": 12746154, "Max": 25427600}
-    },
-    "Type 0": {
-      "Flags": ["DEVICE_LOCAL"],
-      "Stats": {
-        "Blocks": 1,
-        "Allocations": 4,
-        "UnusedRanges": 2,
-        "UsedBytes": 8062124,
-        "UnusedBytes": 25492308,
-        "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760},
-        "UnusedRangeSize": {"Min": 64708, "Avg": 12746154, "Max": 25427600}
-      }
-    }
-  },
-  "Heap 1": {
-    "Size": 8285323264,
-    "Flags": [],
-    "Stats": {
-      "Blocks": 1,
-      "Allocations": 0,
-      "UnusedRanges": 1,
-      "UsedBytes": 0,
-      "UnusedBytes": 33554432
-    },
-    "Type 1": {
-      "Flags": ["HOST_VISIBLE", "HOST_COHERENT"],
-      "Stats": {
-        "Blocks": 1,
-        "Allocations": 0,
-        "UnusedRanges": 1,
-        "UsedBytes": 0,
-        "UnusedBytes": 33554432
-      }
-    },
-    "Type 3": {
-      "Flags": ["HOST_VISIBLE", "HOST_COHERENT", "HOST_CACHED"]
-    }
-  },
-  "Heap 2": {
-    "Size": 268435456,
-    "Flags": ["DEVICE_LOCAL"],
-    "Type 2": {
-      "Flags": ["DEVICE_LOCAL", "HOST_VISIBLE", "HOST_COHERENT"]
-    }
-  },
-  "DefaultPools": {
-    "Type 0": {
-      "PreferredBlockSize": 268435456,
-      "Blocks": {
-        "0": {
-          "TotalBytes": 33554432,
-          "UnusedBytes": 25492308,
-          "Allocations": 4,
-          "UnusedRanges": 2,
-          "Suballocations": [
-            {"Offset": 0, "Type": "IMAGE_OPTIMAL", "Size": 65536, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 6},
-            {"Offset": 65536, "Type": "BUFFER", "Size": 768, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 130},
-            {"Offset": 66304, "Type": "BUFFER", "Size": 60, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 66},
-            {"Offset": 66364, "Type": "FREE", "Size": 64708},
-            {"Offset": 131072, "Type": "IMAGE_OPTIMAL", "Size": 7995760, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 32},
-            {"Offset": 8126832, "Type": "FREE", "Size": 25427600}
-          ]
-        }
-      }
-    },
-    "Type 1": {
-      "PreferredBlockSize": 268435456,
-      "Blocks": {
-        "0": {
-          "TotalBytes": 33554432,
-          "UnusedBytes": 33554432,
-          "Allocations": 0,
-          "UnusedRanges": 1,
-          "Suballocations": [
-            {"Offset": 0, "Type": "FREE", "Size": 33554432}
-          ]
-        }
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/tools/VmaDumpVis/VmaDumpVis.py b/tools/VmaDumpVis/VmaDumpVis.py
deleted file mode 100644
index 03d658c..0000000
--- a/tools/VmaDumpVis/VmaDumpVis.py
+++ /dev/null
@@ -1,320 +0,0 @@
-#

-# Copyright (c) 2018-2022 Advanced Micro Devices, Inc. All rights reserved.

-#

-# Permission is hereby granted, free of charge, to any person obtaining a copy

-# of this software and associated documentation files (the "Software"), to deal

-# in the Software without restriction, including without limitation the rights

-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

-# copies of the Software, and to permit persons to whom the Software is

-# furnished to do so, subject to the following conditions:

-#

-# The above copyright notice and this permission notice shall be included in

-# all copies or substantial portions of the Software.

-#

-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE

-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

-# THE SOFTWARE.

-#

-

-import argparse

-import json

-from PIL import Image, ImageDraw, ImageFont

-

-

-PROGRAM_VERSION = 'VMA Dump Visualization 2.0.1'

-IMG_SIZE_X = 1200

-IMG_MARGIN = 8

-FONT_SIZE = 10

-MAP_SIZE = 24

-COLOR_TEXT_H1 = (0, 0, 0, 255)

-COLOR_TEXT_H2 = (150, 150, 150, 255)

-COLOR_OUTLINE = (155, 155, 155, 255)

-COLOR_OUTLINE_HARD = (0, 0, 0, 255)

-COLOR_GRID_LINE = (224, 224, 224, 255)

-

-

-argParser = argparse.ArgumentParser(description='Visualization of Vulkan Memory Allocator JSON dump.')

-argParser.add_argument('DumpFile', type=argparse.FileType(mode='r', encoding='UTF-8'), help='Path to source JSON file with memory dump created by Vulkan Memory Allocator library')

-argParser.add_argument('-v', '--version', action='version', version=PROGRAM_VERSION)

-argParser.add_argument('-o', '--output', required=True, help='Path to destination image file (e.g. PNG)')

-args = argParser.parse_args()

-

-data = {}

-

-

-def ProcessBlock(dstBlockList, iBlockId, objBlock, sAlgorithm):

-    iBlockSize = int(objBlock['TotalBytes'])

-    arrSuballocs = objBlock['Suballocations']

-    dstBlockObj = {'ID': iBlockId, 'Size':iBlockSize, 'Suballocations':[]}

-    dstBlockObj['Algorithm'] = sAlgorithm

-    for objSuballoc in arrSuballocs:

-        dstBlockObj['Suballocations'].append((objSuballoc['Type'], int(objSuballoc['Size']), int(objSuballoc['Usage']) if ('Usage' in objSuballoc) else 0))

-    dstBlockList.append(dstBlockObj)

-

-

-def GetDataForMemoryType(iMemTypeIndex):

-    global data

-    if iMemTypeIndex in data:

-        return data[iMemTypeIndex]

-    else:

-        newMemTypeData = {'DedicatedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPools':{}}

-        data[iMemTypeIndex] = newMemTypeData

-        return newMemTypeData

-

-

-def IsDataEmpty():

-    global data

-    for dictMemType in data.values():

-        if 'DedicatedAllocations' in dictMemType and len(dictMemType['DedicatedAllocations']) > 0:

-            return False

-        if 'DefaultPoolBlocks' in dictMemType and len(dictMemType['DefaultPoolBlocks']) > 0:

-            return False

-        if 'CustomPools' in dictMemType:

-            for lBlockList in dictMemType['CustomPools'].values()['Blocks']:

-                if len(lBlockList) > 0:

-                    return False

-    return True

-

-

-# Returns tuple:

-# [0] image height : integer

-# [1] pixels per byte : float

-def CalcParams():

-    global data

-    iImgSizeY = IMG_MARGIN

-    iImgSizeY += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes

-    iMaxBlockSize = 0

-    for dictMemType in data.values():

-        iImgSizeY += IMG_MARGIN + FONT_SIZE

-        lDedicatedAllocations = dictMemType['DedicatedAllocations']

-        iImgSizeY += len(lDedicatedAllocations) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)

-        for tDedicatedAlloc in lDedicatedAllocations:

-            iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1])

-        lDefaultPoolBlocks = dictMemType['DefaultPoolBlocks']

-        iImgSizeY += len(lDefaultPoolBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)

-        for objBlock in lDefaultPoolBlocks:

-            iMaxBlockSize = max(iMaxBlockSize, objBlock['Size'])

-        dCustomPools = dictMemType['CustomPools']

-        for poolData in dCustomPools.values():

-            iImgSizeY += len(poolData['Blocks']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)

-            for objBlock in poolData['Blocks']:

-                iMaxBlockSize = max(iMaxBlockSize, objBlock['Size'])

-            iImgSizeY += len(poolData['DedicatedAllocations']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)

-            for tDedicatedAlloc in poolData['DedicatedAllocations']:

-                iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1])

-    fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize)

-    return iImgSizeY, fPixelsPerByte

-

-

-def TypeToColor(sType, iUsage):

-    if sType == 'FREE':

-        return 220, 220, 220, 255

-    elif sType == 'BUFFER':

-        if (iUsage & 0x1C0) != 0: # INDIRECT_BUFFER | VERTEX_BUFFER | INDEX_BUFFER

-            return 255, 148, 148, 255 # Red

-        elif (iUsage & 0x28) != 0: # STORAGE_BUFFER | STORAGE_TEXEL_BUFFER

-            return 255, 187, 121, 255 # Orange

-        elif (iUsage & 0x14) != 0: # UNIFORM_BUFFER | UNIFORM_TEXEL_BUFFER

-            return 255, 255, 0, 255 # Yellow

-        else:

-            return 255, 255, 165, 255 # Light yellow

-    elif sType == 'IMAGE_OPTIMAL':

-        if (iUsage & 0x20) != 0: # DEPTH_STENCIL_ATTACHMENT

-            return 246, 128, 255, 255 # Pink

-        elif (iUsage & 0xD0) != 0: # INPUT_ATTACHMENT | TRANSIENT_ATTACHMENT | COLOR_ATTACHMENT

-            return 179, 179, 255, 255 # Blue

-        elif (iUsage & 0x4) != 0: # SAMPLED

-            return 0, 255, 255, 255 # Aqua

-        else:

-            return 183, 255, 255, 255 # Light aqua

-    elif sType == 'IMAGE_LINEAR':

-        return 0, 255, 0, 255 # Green

-    elif sType == 'IMAGE_UNKNOWN':

-        return 0, 255, 164, 255 # Green/aqua

-    elif sType == 'UNKNOWN':

-        return 175, 175, 175, 255 # Gray

-    assert False

-    return 0, 0, 0, 255

-

-

-def DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc): 

-    global fPixelsPerByte

-    iSizeBytes = tDedicatedAlloc[1]

-    iSizePixels = int(iSizeBytes * fPixelsPerByte)

-    draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tDedicatedAlloc[0], tDedicatedAlloc[2]), outline=COLOR_OUTLINE)

-

-

-def DrawBlock(draw, y, objBlock):

-    global fPixelsPerByte

-    iSizeBytes = objBlock['Size']

-    iSizePixels = int(iSizeBytes * fPixelsPerByte)

-    draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0), outline=None)

-    iByte = 0

-    iX = 0

-    iLastHardLineX = -1

-    for tSuballoc in objBlock['Suballocations']:

-        sType = tSuballoc[0]

-        iByteEnd = iByte + tSuballoc[1]

-        iXEnd = int(iByteEnd * fPixelsPerByte)

-        if sType != 'FREE':

-            if iXEnd > iX + 1:

-                iUsage = tSuballoc[2]

-                draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType, iUsage), outline=COLOR_OUTLINE)

-                # Hard line was been overwritten by rectangle outline: redraw it.

-                if iLastHardLineX == iX:

-                    draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)

-            else:

-                draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)

-                iLastHardLineX = iX

-        iByte = iByteEnd

-        iX = iXEnd

-

-

-def BytesToStr(iBytes):

-    if iBytes < 1024:

-        return "%d B" % iBytes

-    iBytes /= 1024

-    if iBytes < 1024:

-        return "%d KiB" % iBytes

-    iBytes /= 1024

-    if iBytes < 1024:

-        return "%d MiB" % iBytes

-    iBytes /= 1024

-    return "%d GiB" % iBytes

-

-

-jsonSrc = json.load(args.DumpFile)

-if 'DedicatedAllocations' in jsonSrc:

-    for tType in jsonSrc['DedicatedAllocations'].items():

-        sType = tType[0]

-        assert sType[:5] == 'Type '

-        iType = int(sType[5:])

-        typeData = GetDataForMemoryType(iType)

-        for objAlloc in tType[1]:

-            typeData['DedicatedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc['Usage']) if ('Usage' in objAlloc) else 0))

-if 'DefaultPools' in jsonSrc:

-    for tType in jsonSrc['DefaultPools'].items():

-        sType = tType[0]

-        assert sType[:5] == 'Type '

-        iType = int(sType[5:])

-        typeData = GetDataForMemoryType(iType)

-        for sBlockId, objBlock in tType[1]['Blocks'].items():

-            ProcessBlock(typeData['DefaultPoolBlocks'], int(sBlockId), objBlock, '')

-if 'Pools' in jsonSrc:

-    objPools = jsonSrc['Pools']

-    for sPoolId, objPool in objPools.items():

-        iType = int(objPool['MemoryTypeIndex'])

-        typeData = GetDataForMemoryType(iType)

-        objBlocks = objPool['Blocks']

-        sAlgorithm = objPool.get('Algorithm', '')

-        sName = objPool.get('Name', None)

-        if sName:

-            sFullName = sPoolId + ' "' + sName + '"'

-        else:

-            sFullName = sPoolId

-        typeData['CustomPools'][sFullName] = { 'Blocks':[], 'DedicatedAllocations':[] }

-        for sBlockId, objBlock in objBlocks.items():

-            ProcessBlock(typeData['CustomPools'][sFullName]['Blocks'], int(sBlockId), objBlock, sAlgorithm)

-        if 'DedicatedAllocations' in objPool:

-            for objAlloc in objPool['DedicatedAllocations']:

-                typeData['CustomPools'][sFullName]['DedicatedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc['Usage']) if ('Usage' in objAlloc) else 0))

-            

-

-if IsDataEmpty():

-    print("There is nothing to put on the image. Please make sure you generated the stats string with detailed map enabled.")

-    exit(1)

-

-iImgSizeY, fPixelsPerByte = CalcParams()

-

-img = Image.new('RGB', (IMG_SIZE_X, iImgSizeY), 'white')

-draw = ImageDraw.Draw(img)

-

-try:

-    font = ImageFont.truetype('segoeuib.ttf')

-except:

-    font = ImageFont.load_default()

-

-y = IMG_MARGIN

-

-# Draw grid lines

-iBytesBetweenGridLines = 32

-while iBytesBetweenGridLines * fPixelsPerByte < 64:

-    iBytesBetweenGridLines *= 2

-iByte = 0

-TEXT_MARGIN = 4

-while True:

-    iX = int(iByte * fPixelsPerByte)

-    if iX > IMG_SIZE_X - 2 * IMG_MARGIN:

-        break

-    draw.line([iX + IMG_MARGIN, 0, iX + IMG_MARGIN, iImgSizeY], fill=COLOR_GRID_LINE)

-    if iByte == 0:

-        draw.text((iX + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font)

-    else:

-        text = BytesToStr(iByte)

-        textSize = draw.textsize(text, font=font)

-        draw.text((iX + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font)

-    iByte += iBytesBetweenGridLines

-y += FONT_SIZE + IMG_MARGIN

-

-# Draw main content

-for iMemTypeIndex in sorted(data.keys()):

-    dictMemType = data[iMemTypeIndex]

-    draw.text((IMG_MARGIN, y), "Memory type %d" % iMemTypeIndex, fill=COLOR_TEXT_H1, font=font)

-    y += FONT_SIZE + IMG_MARGIN

-    index = 0

-    for tDedicatedAlloc in dictMemType['DedicatedAllocations']:

-        draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font)

-        y += FONT_SIZE + IMG_MARGIN

-        DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc)

-        y += MAP_SIZE + IMG_MARGIN

-        index += 1

-    for objBlock in dictMemType['DefaultPoolBlocks']:

-        draw.text((IMG_MARGIN, y), "Default pool block %d" % objBlock['ID'], fill=COLOR_TEXT_H2, font=font)

-        y += FONT_SIZE + IMG_MARGIN

-        DrawBlock(draw, y, objBlock)

-        y += MAP_SIZE + IMG_MARGIN

-    index = 0

-    for sPoolName, pool in dictMemType['CustomPools'].items():

-        for objBlock in pool['Blocks']:

-            if 'Algorithm' in objBlock and objBlock['Algorithm']:

-                sAlgorithm = ' (Algorithm: %s)' % (objBlock['Algorithm'])

-            else:

-                sAlgorithm = ''

-            draw.text((IMG_MARGIN, y), "Custom pool %s%s block %d" % (sPoolName, sAlgorithm, objBlock['ID']), fill=COLOR_TEXT_H2, font=font)

-            y += FONT_SIZE + IMG_MARGIN

-            DrawBlock(draw, y, objBlock)

-            y += 2 * (FONT_SIZE + IMG_MARGIN)

-            index += 1

-        alloc_index = 0

-        for objAlloc in pool['DedicatedAllocations']:

-            draw.text((IMG_MARGIN, y), "Custom pool %s%s dedicated allocation %d" % (sPoolName, sAlgorithm, alloc_index), fill=COLOR_TEXT_H2, font=font)

-            y += FONT_SIZE + IMG_MARGIN

-            DrawDedicatedAllocationBlock(draw, y, objAlloc)

-            y += MAP_SIZE + IMG_MARGIN

-            alloc_index += 1

-del draw

-img.save(args.output)

-

-"""

-Main data structure - variable `data` - is a dictionary. Key is integer - memory type index. Value is dictionary of:

-- Fixed key 'DedicatedAllocations'. Value is list of tuples, each containing:

-    - [0]: Type : string

-    - [1]: Size : integer

-    - [2]: Usage : integer (0 if unknown)

-- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with:

-    - Fixed key 'ID'. Value is int.

-    - Fixed key 'Size'. Value is int.

-    - Fixed key 'Suballocations'. Value is list of tuples as above.

-- Fixed key 'CustomPools'. Value is dictionary.

-  - Key is string with pool ID/name. Value is a dictionary with: 

-    - Fixed key 'Blocks'. Value is a list of objects representing memory blocks, each containing dictionary with:

-      - Fixed key 'ID'. Value is int.

-      - Fixed key 'Size'. Value is int.

-      - Fixed key 'Algorithm'. Optional. Value is string.

-      - Fixed key 'Suballocations'. Value is list of tuples as above.

-    - Fixed key 'DedicatedAllocations'. Value is list of tuples as above.

-"""