From 98218850d6cef23f28f2e8492e6ad81d569c2917 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Thu, 24 Mar 2022 12:14:27 +0100 Subject: [PATCH] New JSON dump format Unified across VMA and D3D12MA. Updated Python script for visualization - now called GpuMemDumpVis.py. Code by @medranSolus --- README.md | 2 +- include/D3D12MemAlloc.h | 7 +- src/D3D12MemAlloc.cpp | 536 ++++++++++++------ src/Tests.cpp | 213 +++++-- tools/D3d12maDumpVis/D3d12maDumpVis.py | 282 --------- tools/GpuMemDumpVis/GpuMemDumpVis.py | 334 +++++++++++ tools/GpuMemDumpVis/README.md | 45 ++ .../README_files/ExampleOutput.png | Bin 0 -> 98005 bytes .../GpuMemDumpVis/README_files/Legend_Bkg.png | Bin 0 -> 196 bytes .../README_files/Legend_Buffer_1.png | Bin 0 -> 1705 bytes .../README_files/Legend_Buffer_2.png | Bin 0 -> 1590 bytes .../README_files/Legend_Buffer_3.png | Bin 0 -> 1877 bytes .../README_files/Legend_Buffer_4.png | Bin 0 -> 2195 bytes .../README_files/Legend_Details.png | Bin 0 -> 964 bytes .../README_files/Legend_Image_1.png | Bin 0 -> 1739 bytes .../README_files/Legend_Image_2.png | Bin 0 -> 1592 bytes .../README_files/Legend_Image_3.png | Bin 0 -> 1719 bytes .../README_files/Legend_Image_4.png | Bin 0 -> 1925 bytes .../README_files/Legend_Image_Linear.png | Bin 0 -> 1432 bytes .../README_files/Legend_Image_Unknown.png | Bin 0 -> 1449 bytes .../README_files/Legend_Texture_1.png | Bin 0 -> 1739 bytes .../README_files/Legend_Texture_2.png | Bin 0 -> 1592 bytes .../README_files/Legend_Texture_3.png | Bin 0 -> 1719 bytes .../README_files/Legend_Texture_4.png | Bin 0 -> 1925 bytes .../README_files/Legend_Unknown.png | Bin 0 -> 1356 bytes tools/GpuMemDumpVis/Sample.json | 426 ++++++++++++++ 26 files changed, 1354 insertions(+), 491 deletions(-) delete mode 100644 tools/D3d12maDumpVis/D3d12maDumpVis.py create mode 100644 tools/GpuMemDumpVis/GpuMemDumpVis.py create mode 100644 tools/GpuMemDumpVis/README.md create mode 100644 tools/GpuMemDumpVis/README_files/ExampleOutput.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Bkg.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Buffer_1.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Buffer_2.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Buffer_3.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Buffer_4.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Details.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Image_1.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Image_2.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Image_3.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Image_4.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Image_Linear.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Image_Unknown.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Texture_1.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Texture_2.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Texture_3.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Texture_4.png create mode 100644 tools/GpuMemDumpVis/README_files/Legend_Unknown.png create mode 100644 tools/GpuMemDumpVis/Sample.json diff --git a/README.md b/README.md index 65a9ff5..28b17a1 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Additional features: - Statistics: Obtain brief or detailed statistics about the amount of memory used, unused, number of allocated heaps, number of allocations etc. - globally and per memory heap type. Current memory usage and budget as reported by the system can also be queried. - Debug annotations: Associate custom `void* pPrivateData` and debug `LPCWSTR 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 using attached Python script. +- Convert this JSON dump into a picture to visualize your memory. See [tools/GpuMemDumpVis](tools/GpuMemDumpVis/README.md). - Virtual allocator - an API that exposes the core allocation algorithm to be used without allocating real GPU memory, to allocate your own stuff, e.g. sub-allocate pieces of one large buffer. # Prerequisites diff --git a/include/D3D12MemAlloc.h b/include/D3D12MemAlloc.h index 46fa934..6e3ecf7 100644 --- a/include/D3D12MemAlloc.h +++ b/include/D3D12MemAlloc.h @@ -237,7 +237,6 @@ enum ALLOCATION_FLAGS */ ALLOCATION_FLAG_UPPER_ADDRESS = 0x8, - /** Set this flag if the allocated memory will have aliasing resources. Use this when calling D3D12MA::Allocator::CreateResource() and similar to @@ -570,7 +569,6 @@ private: UINT64 m_Size; UINT64 m_Alignment; ID3D12Resource* m_Resource; - UINT m_CreationFrameIndex; void* m_pPrivateData; wchar_t* m_Name; @@ -1246,8 +1244,9 @@ public: */ void CalculateStatistics(TotalStatistics* pStats); - /// Builds and returns statistics as a string in JSON format. - /** @param[out] ppStatsString Must be freed using Allocator::FreeStatsString. + /** \brief Builds and returns statistics as a string in JSON format. + * + @param[out] ppStatsString Must be freed using Allocator::FreeStatsString. @param DetailedMap `TRUE` to include full list of allocations (can make the string quite long), `FALSE` to only return statistics. */ void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) const; diff --git a/src/D3D12MemAlloc.cpp b/src/D3D12MemAlloc.cpp index 64aa1b3..6939696 100644 --- a/src/D3D12MemAlloc.cpp +++ b/src/D3D12MemAlloc.cpp @@ -1511,61 +1511,61 @@ void JsonWriter::AddAllocationToObject(const Allocation& alloc) break; default: D3D12MA_ASSERT(0); break; } + WriteString(L"Size"); WriteNumber(alloc.GetSize()); + WriteString(L"Usage"); + WriteNumber((UINT)alloc.m_PackedData.GetResourceFlags()); + + void* privateData = alloc.GetPrivateData(); + if (privateData) + { + WriteString(L"CustomData"); + WriteNumber((uintptr_t)privateData); + } + LPCWSTR name = alloc.GetName(); if (name != NULL) { WriteString(L"Name"); WriteString(name); } - if (alloc.m_PackedData.GetResourceFlags()) - { - WriteString(L"Flags"); - WriteNumber((UINT)alloc.m_PackedData.GetResourceFlags()); - } if (alloc.m_PackedData.GetTextureLayout()) { WriteString(L"Layout"); WriteNumber((UINT)alloc.m_PackedData.GetTextureLayout()); } - if (alloc.m_CreationFrameIndex) - { - WriteString(L"CreationFrameIndex"); - WriteNumber(alloc.m_CreationFrameIndex); - } } void JsonWriter::AddDetailedStatisticsInfoObject(const DetailedStatistics& stats) { BeginObject(); + WriteString(L"BlockCount"); WriteNumber(stats.Stats.BlockCount); - WriteString(L"AllocationCount"); - WriteNumber(stats.Stats.AllocationCount); - WriteString(L"UnusedRangeCount"); - WriteNumber(stats.UnusedRangeCount); WriteString(L"BlockBytes"); WriteNumber(stats.Stats.BlockBytes); + WriteString(L"AllocationCount"); + WriteNumber(stats.Stats.AllocationCount); WriteString(L"AllocationBytes"); WriteNumber(stats.Stats.AllocationBytes); + WriteString(L"UnusedRangeCount"); + WriteNumber(stats.UnusedRangeCount); - WriteString(L"AllocationSize"); - BeginObject(true); - WriteString(L"Min"); - WriteNumber(stats.AllocationSizeMin); - WriteString(L"Max"); - WriteNumber(stats.AllocationSizeMax); - EndObject(); - - WriteString(L"UnusedRangeSize"); - BeginObject(true); - WriteString(L"Min"); - WriteNumber(stats.UnusedRangeSizeMin); - WriteString(L"Max"); - WriteNumber(stats.UnusedRangeSizeMax); - EndObject(); - + if (stats.Stats.AllocationCount > 1) + { + WriteString(L"AllocationSizeMin"); + WriteNumber(stats.AllocationSizeMin); + WriteString(L"AllocationSizeMax"); + WriteNumber(stats.AllocationSizeMax); + } + if (stats.UnusedRangeCount > 1) + { + WriteString(L"UnusedRangeSizeMin"); + WriteNumber(stats.UnusedRangeSizeMin); + WriteString(L"UnusedRangeSizeMax"); + WriteNumber(stats.UnusedRangeSizeMax); + } EndObject(); } @@ -2965,8 +2965,6 @@ BlockMetadata::BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks, bo void BlockMetadata::PrintDetailedMap_Begin(JsonWriter& json, UINT64 unusedBytes, size_t allocationCount, size_t unusedRangeCount) const { - json.BeginObject(); - json.WriteString(L"TotalBytes"); json.WriteNumber(GetSize()); @@ -2993,13 +2991,11 @@ void BlockMetadata::PrintDetailedMap_Allocation(JsonWriter& json, if (IsVirtual()) { - json.WriteString(L"Type"); - json.WriteString(L"ALLOCATION"); json.WriteString(L"Size"); json.WriteNumber(size); if (privateData) { - json.WriteString(L"PrivateData"); + json.WriteString(L"CustomData"); json.WriteNumber((uintptr_t)privateData); } } @@ -3032,7 +3028,6 @@ void BlockMetadata::PrintDetailedMap_UnusedRange(JsonWriter& json, void BlockMetadata::PrintDetailedMap_End(JsonWriter& json) const { json.EndArray(); - json.EndObject(); } #endif // _D3D12MA_BLOCK_METADATA_FUNCTIONS #endif // _D3D12MA_BLOCK_METADATA @@ -5942,6 +5937,7 @@ public: ~BlockVector(); const D3D12_HEAP_PROPERTIES& GetHeapProperties() const { return m_HeapProps; } + D3D12_HEAP_FLAGS GetHeapFlags() const { return m_HeapFlags; } UINT64 GetPreferredBlockSize() const { return m_PreferredBlockSize; } UINT32 GetAlgorithm() const { return m_Algorithm; } // To be used only while the m_Mutex is locked. Used during defragmentation. @@ -6409,13 +6405,13 @@ public: void FreeHeapMemory(Allocation* allocation); void SetCurrentFrameIndex(UINT frameIndex); - - void CalculateStatistics(TotalStatistics& outStats); + // For more deailed stats use outCutomHeaps to access statistics divided into L0 and L1 group + void CalculateStatistics(TotalStatistics& outStats, DetailedStatistics outCutomHeaps[2] = NULL); void GetBudget(Budget* outLocalBudget, Budget* outNonLocalBudget); void GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE heapType); - void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap); + void BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap); void FreeStatsString(WCHAR* pStatsString); private: @@ -6945,7 +6941,7 @@ void AllocatorPimpl::SetCurrentFrameIndex(UINT frameIndex) #endif } -void AllocatorPimpl::CalculateStatistics(TotalStatistics& outStats) +void AllocatorPimpl::CalculateStatistics(TotalStatistics& outStats, DetailedStatistics outCutomHeaps[2]) { // Init stats for (size_t i = 0; i < HEAP_TYPE_COUNT; i++) @@ -6953,6 +6949,11 @@ void AllocatorPimpl::CalculateStatistics(TotalStatistics& outStats) for (size_t i = 0; i < DXGI_MEMORY_SEGMENT_GROUP_COUNT; i++) ClearDetailedStatistics(outStats.MemorySegmentGroup[i]); ClearDetailedStatistics(outStats.Total); + if (outCutomHeaps) + { + ClearDetailedStatistics(outCutomHeaps[0]); + ClearDetailedStatistics(outCutomHeaps[1]); + } // Process default pools. 3 standard heap types only. Add them to outStats.HeapType[i]. if (SupportsResourceHeapTier2()) @@ -7003,8 +7004,13 @@ void AllocatorPimpl::CalculateStatistics(TotalStatistics& outStats) pool->AddDetailedStatistics(tmpStats); AddDetailedStatistics( outStats.HeapType[heapTypeIndex], tmpStats); + + UINT memorySegment = HeapPropertiesToMemorySegmentGroup(poolHeapProps); AddDetailedStatistics( - outStats.MemorySegmentGroup[HeapPropertiesToMemorySegmentGroup(poolHeapProps)], tmpStats); + outStats.MemorySegmentGroup[memorySegment], tmpStats); + + if (outCutomHeaps) + AddDetailedStatistics(outCutomHeaps[memorySegment], tmpStats); } } @@ -7106,155 +7112,354 @@ void AllocatorPimpl::GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE hea } } -void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) +void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap) { StringBuilder sb(GetAllocs()); { - JsonWriter json(GetAllocs(), sb); - Budget localBudget = {}, nonLocalBudget = {}; GetBudget(&localBudget, &nonLocalBudget); TotalStatistics stats; - CalculateStatistics(stats); + DetailedStatistics customHeaps[2]; + CalculateStatistics(stats, customHeaps); - json.BeginObject(); - - json.WriteString(L"Total"); - json.AddDetailedStatisticsInfoObject(stats.Total); - for (size_t heapType = 0; heapType < HEAP_TYPE_COUNT; ++heapType) - { - json.WriteString(HeapTypeNames[heapType]); - json.AddDetailedStatisticsInfoObject(stats.HeapType[heapType]); - } - - json.WriteString(L"Budget"); + JsonWriter json(GetAllocs(), sb); json.BeginObject(); { - json.WriteString(L"Local"); - WriteBudgetToJson(json, localBudget); - json.WriteString(L"NonLocal"); - WriteBudgetToJson(json, nonLocalBudget); - } - json.EndObject(); - - if (DetailedMap) - { - json.WriteString(L"DetailedMap"); + json.WriteString(L"General"); json.BeginObject(); + { + json.WriteString(L"API"); + json.WriteString(L"Direct3D 12"); + + json.WriteString(L"GPU"); + json.WriteString(m_AdapterDesc.Description); + + json.WriteString(L"DedicatedVideoMemory"); + json.WriteNumber(m_AdapterDesc.DedicatedVideoMemory); + json.WriteString(L"DedicatedSystemMemory"); + json.WriteNumber(m_AdapterDesc.DedicatedSystemMemory); + json.WriteString(L"SharedSystemMemory"); + json.WriteNumber(m_AdapterDesc.SharedSystemMemory); + + json.WriteString(L"ResourceHeapTier"); + json.WriteNumber(static_cast(m_D3D12Options.ResourceHeapTier)); + + json.WriteString(L"ResourceBindingTier"); + json.WriteNumber(static_cast(m_D3D12Options.ResourceBindingTier)); + + json.WriteString(L"TiledResourcesTier"); + json.WriteNumber(static_cast(m_D3D12Options.TiledResourcesTier)); + + json.WriteString(L"TileBasedRenderer"); + json.WriteBool(m_D3D12Architecture.TileBasedRenderer); + + json.WriteString(L"UMA"); + json.WriteBool(m_D3D12Architecture.UMA); + json.WriteString(L"CacheCoherentUMA"); + json.WriteBool(m_D3D12Architecture.CacheCoherentUMA); + } + json.EndObject(); + } + { + json.WriteString(L"Total"); + json.AddDetailedStatisticsInfoObject(stats.Total); + } + { + json.WriteString(L"MemoryInfo"); + json.BeginObject(); + { + json.WriteString(L"L0"); + json.BeginObject(); + { + json.WriteString(L"Budget"); + WriteBudgetToJson(json, IsUMA() ? localBudget : nonLocalBudget); // When UMA device only L0 present as local + + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.MemorySegmentGroup[!IsUMA()]); + + json.WriteString(L"MemoryPools"); + json.BeginObject(); + { + if (IsUMA()) + { + json.WriteString(L"DEFAULT"); + json.BeginObject(); + { + json.WriteString(L"Flags"); + json.BeginArray(true); + json.EndArray(); + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[0]); + } + json.EndObject(); + } + json.WriteString(L"UPLOAD"); + json.BeginObject(); + { + json.WriteString(L"Flags"); + json.BeginArray(true); + json.EndArray(); + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[1]); + } + json.EndObject(); + + json.WriteString(L"READBACK"); + json.BeginObject(); + { + json.WriteString(L"Flags"); + json.BeginArray(true); + json.EndArray(); + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[2]); + } + json.EndObject(); + + json.WriteString(L"CUSTOM"); + json.BeginObject(); + { + json.WriteString(L"Flags"); + json.BeginArray(true); + json.EndArray(); + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(customHeaps[!IsUMA()]); + } + json.EndObject(); + } + json.EndObject(); + } + json.EndObject(); + if (!IsUMA()) + { + json.WriteString(L"L1"); + json.BeginObject(); + { + json.WriteString(L"Flags"); + json.BeginArray(true); + json.EndArray(); + + json.WriteString(L"Size"); + json.WriteNumber(0U); + + json.WriteString(L"Budget"); + WriteBudgetToJson(json, localBudget); + + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.MemorySegmentGroup[0]); + + json.WriteString(L"MemoryPools"); + json.BeginObject(); + { + json.WriteString(L"DEFAULT"); + json.BeginObject(); + { + json.WriteString(L"Flags"); + json.BeginArray(true); + json.EndArray(); + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[0]); + } + json.EndObject(); + + json.WriteString(L"CUSTOM"); + json.BeginObject(); + { + json.WriteString(L"Flags"); + json.BeginArray(true); + json.EndArray(); + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(customHeaps[0]); + } + json.EndObject(); + } + json.EndObject(); + } + json.EndObject(); + } + } + json.EndObject(); + } + + if (detailedMap) + { + const auto writeHeapInfo = [&](BlockVector* blockVector, CommittedAllocationList* committedAllocs, bool customHeap) + { + D3D12MA_ASSERT(blockVector); + + D3D12_HEAP_FLAGS flags = blockVector->GetHeapFlags(); + json.WriteString(L"Flags"); + json.BeginArray(true); + { + if (flags & D3D12_HEAP_FLAG_SHARED) + json.WriteString(L"HEAP_FLAG_SHARED"); + if (flags & D3D12_HEAP_FLAG_ALLOW_DISPLAY) + json.WriteString(L"HEAP_FLAG_ALLOW_DISPLAY"); + if (flags & D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER) + json.WriteString(L"HEAP_FLAG_CROSS_ADAPTER"); + if (flags & D3D12_HEAP_FLAG_HARDWARE_PROTECTED) + json.WriteString(L"HEAP_FLAG_HARDWARE_PROTECTED"); + if (flags & D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH) + json.WriteString(L"HEAP_FLAG_ALLOW_WRITE_WATCH"); + if (flags & D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS) + json.WriteString(L"HEAP_FLAG_ALLOW_SHADER_ATOMICS"); +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (flags & D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT) + json.WriteString(L"HEAP_FLAG_CREATE_NOT_RESIDENT"); + if (flags & D3D12_HEAP_FLAG_CREATE_NOT_ZEROED) + json.WriteString(L"HEAP_FLAG_CREATE_NOT_ZEROED"); +#endif + + if (flags & D3D12_HEAP_FLAG_DENY_BUFFERS) + json.WriteString(L"HEAP_FLAG_DENY_BUFFERS"); + if (flags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) + json.WriteString(L"HEAP_FLAG_DENY_RT_DS_TEXTURES"); + if (flags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) + json.WriteString(L"HEAP_FLAG_DENY_NON_RT_DS_TEXTURES"); + + flags &= ~(D3D12_HEAP_FLAG_SHARED + | D3D12_HEAP_FLAG_DENY_BUFFERS + | D3D12_HEAP_FLAG_ALLOW_DISPLAY + | D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER + | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES + | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES + | D3D12_HEAP_FLAG_HARDWARE_PROTECTED + | D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH + | D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS); +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + flags &= ~(D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT + | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED); +#endif + if (flags != 0) + json.WriteNumber((UINT)flags); + + if (customHeap) + { + const D3D12_HEAP_PROPERTIES& properties = blockVector->GetHeapProperties(); + switch (properties.MemoryPoolPreference) + { + default: + D3D12MA_ASSERT(0); + case D3D12_MEMORY_POOL_UNKNOWN: + json.WriteString(L"MEMORY_POOL_UNKNOWN"); + break; + case D3D12_MEMORY_POOL_L0: + json.WriteString(L"MEMORY_POOL_L0"); + break; + case D3D12_MEMORY_POOL_L1: + json.WriteString(L"MEMORY_POOL_L1"); + break; + } + switch (properties.CPUPageProperty) + { + default: + D3D12MA_ASSERT(0); + case D3D12_CPU_PAGE_PROPERTY_UNKNOWN: + json.WriteString(L"CPU_PAGE_PROPERTY_UNKNOWN"); + break; + case D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE: + json.WriteString(L"CPU_PAGE_PROPERTY_NOT_AVAILABLE"); + break; + case D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE: + json.WriteString(L"CPU_PAGE_PROPERTY_WRITE_COMBINE"); + break; + case D3D12_CPU_PAGE_PROPERTY_WRITE_BACK: + json.WriteString(L"CPU_PAGE_PROPERTY_WRITE_BACK"); + break; + } + } + } + json.EndArray(); + + json.WriteString(L"PreferredBlockSize"); + json.WriteNumber(blockVector->GetPreferredBlockSize()); + + json.WriteString(L"Blocks"); + blockVector->WriteBlockInfoToJson(json); + + json.WriteString(L"DedicatedAllocations"); + json.BeginArray(); + if (committedAllocs) + committedAllocs->BuildStatsString(json); + json.EndArray(); + }; json.WriteString(L"DefaultPools"); json.BeginObject(); - - if (SupportsResourceHeapTier2()) { - for (size_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) + if (SupportsResourceHeapTier2()) { - json.WriteString(HeapTypeNames[heapType]); - json.BeginObject(); - - json.WriteString(L"Blocks"); - - BlockVector* blockVector = m_BlockVectors[heapType]; - D3D12MA_ASSERT(blockVector); - blockVector->WriteBlockInfoToJson(json); - - json.EndObject(); // heap name - } - } - else - { - for (size_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) - { - for (size_t heapSubType = 0; heapSubType < 3; ++heapSubType) + for (uint8_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) { - static const WCHAR* const heapSubTypeName[] = { - L" + buffer", - L" + texture", - L" + texture RT or DS", - }; - json.BeginString(); - json.ContinueString(HeapTypeNames[heapType]); - json.ContinueString(heapSubTypeName[heapSubType]); - json.EndString(); + json.WriteString(HeapTypeNames[heapType]); json.BeginObject(); - - json.WriteString(L"Blocks"); - - BlockVector* blockVector = m_BlockVectors[heapType * 3 + heapSubType]; - D3D12MA_ASSERT(blockVector); - blockVector->WriteBlockInfoToJson(json); - - json.EndObject(); // heap name + writeHeapInfo(m_BlockVectors[heapType], m_CommittedAllocations + heapType, false); + json.EndObject(); } } - } - - json.EndObject(); // DefaultPools - - json.WriteString(L"CommittedAllocations"); - json.BeginObject(); - - for (size_t heapTypeIndex = 0; heapTypeIndex < STANDARD_HEAP_TYPE_COUNT; ++heapTypeIndex) - { - json.WriteString(HeapTypeNames[heapTypeIndex]); - json.BeginArray(); - m_CommittedAllocations[heapTypeIndex].BuildStatsString(json); - json.EndArray(); - } - - json.EndObject(); // CommittedAllocations - - json.WriteString(L"Pools"); - json.BeginObject(); - - for (size_t heapTypeIndex = 0; heapTypeIndex < HEAP_TYPE_COUNT; ++heapTypeIndex) - { - json.WriteString(HeapTypeNames[heapTypeIndex]); - json.BeginArray(); - MutexLockRead mutex(m_PoolsMutex[heapTypeIndex], m_UseMutex); - size_t index = 0; - for (auto* item = m_Pools[heapTypeIndex].Front(); item != nullptr; item = PoolList::GetNext(item)) + else { - json.BeginObject(); - json.WriteString(L"Name"); - if (item->GetName() != nullptr) + for (uint8_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) { - json.WriteString(item->GetName()); + for (uint8_t heapSubType = 0; heapSubType < 3; ++heapSubType) + { + static const WCHAR* const heapSubTypeName[] = { + L" - Buffers", + L" - Textures", + L" - Textures RT/DS", + }; + json.BeginString(HeapTypeNames[heapType]); + json.EndString(heapSubTypeName[heapSubType]); + + json.BeginObject(); + writeHeapInfo(m_BlockVectors[heapType + heapSubType], m_CommittedAllocations + heapType, false); + json.EndObject(); + } } - else - { - json.BeginString(); - json.ContinueString(index); - json.EndString(); - } - ++index; - - json.WriteString(L"Blocks"); - item->GetBlockVector()->WriteBlockInfoToJson(json); - - json.WriteString(L"CommittedAllocations"); - json.BeginArray(); - if (item->SupportsCommittedAllocations()) - item->GetCommittedAllocationList()->BuildStatsString(json); - json.EndArray(); - - json.EndObject(); } - json.EndArray(); } + json.EndObject(); - json.EndObject(); // Pools + json.WriteString(L"CustomPools"); + json.BeginObject(); + for (uint8_t heapTypeIndex = 0; heapTypeIndex < HEAP_TYPE_COUNT; ++heapTypeIndex) + { + MutexLockRead mutex(m_PoolsMutex[heapTypeIndex], m_UseMutex); + auto* item = m_Pools[heapTypeIndex].Front(); + if (item != NULL) + { + size_t index = 0; + json.WriteString(HeapTypeNames[heapTypeIndex]); + json.BeginArray(); + do + { + json.BeginObject(); + json.WriteString(L"Name"); + json.BeginString(); + json.ContinueString(index++); + if (item->GetName()) + { + json.WriteString(L" - "); + json.WriteString(item->GetName()); + } + json.EndString(); - json.EndObject(); // DetailedMap + writeHeapInfo(item->GetBlockVector(), item->GetCommittedAllocationList(), heapTypeIndex == 3); + json.EndObject(); + } while ((item = PoolList::GetNext(item)) != NULL); + json.EndArray(); + } + } + json.EndObject(); } json.EndObject(); } const size_t length = sb.GetLength(); - WCHAR* result = AllocateArray(GetAllocs(), length + 1); - memcpy(result, sb.GetData(), length * sizeof(WCHAR)); - result[length] = L'\0'; + WCHAR* result = AllocateArray(GetAllocs(), length + 2); + result[0] = 0xFEFF; + memcpy(result + 1, sb.GetData(), length * sizeof(WCHAR)); + result[length + 1] = L'\0'; *ppStatsString = result; } @@ -7710,18 +7915,10 @@ void AllocatorPimpl::WriteBudgetToJson(JsonWriter& json, const Budget& budget) { json.BeginObject(); { - json.WriteString(L"BlockCount"); - json.WriteNumber(budget.Stats.BlockCount); - json.WriteString(L"AllocationCount"); - json.WriteNumber(budget.Stats.AllocationCount); - json.WriteString(L"BlockBytes"); - json.WriteNumber(budget.Stats.BlockBytes); - json.WriteString(L"AllocationBytes"); - json.WriteNumber(budget.Stats.AllocationBytes); - json.WriteString(L"UsageBytes"); - json.WriteNumber(budget.UsageBytes); json.WriteString(L"BudgetBytes"); json.WriteNumber(budget.BudgetBytes); + json.WriteString(L"UsageBytes"); + json.WriteNumber(budget.UsageBytes); } json.EndObject(); } @@ -8254,7 +8451,9 @@ void BlockVector::WriteBlockInfoToJson(JsonWriter& json) json.ContinueString(pBlock->GetId()); json.EndString(); + json.BeginObject(); pBlock->m_pMetadata->WriteAllocationInfoToJson(json); + json.EndObject(); } json.EndObject(); @@ -9423,7 +9622,6 @@ Allocation::Allocation(AllocatorPimpl* allocator, UINT64 size, UINT64 alignment, m_Size{ size }, m_Alignment{ alignment }, m_Resource{ NULL }, - m_CreationFrameIndex{ allocator->GetCurrentFrameIndex() }, m_Name{ NULL } { D3D12MA_ASSERT(allocator); diff --git a/src/Tests.cpp b/src/Tests.cpp index 33790cc..9d8755a 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -439,51 +439,194 @@ static void TestDebugMarginNotInVirtualAllocator(const TestContext& ctx) } } -static void TestFrameIndexAndJson(const TestContext& ctx) +static void TestJson(const TestContext& ctx) { - const UINT64 bufSize = 32ull * 1024; + wprintf(L"Test JSON\n"); + + std::vector> pools; + std::vector> allocs; D3D12MA::ALLOCATION_DESC allocDesc = {}; - allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; - allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; + D3D12_RESOURCE_DESC resDesc = {}; + resDesc.Alignment = 0; + resDesc.MipLevels = 1; + resDesc.SampleDesc.Count = 1; + resDesc.SampleDesc.Quality = 0; - D3D12_RESOURCE_DESC resourceDesc; - FillResourceDescForBuffer(resourceDesc, bufSize); + D3D12_RESOURCE_ALLOCATION_INFO allocInfo = {}; + allocInfo.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + allocInfo.SizeInBytes = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; - const UINT BEGIN_INDEX = 10; - const UINT END_INDEX = 20; - for (UINT frameIndex = BEGIN_INDEX; frameIndex < END_INDEX; ++frameIndex) + // Select if using custom pool or default + for (UINT8 poolType = 0; poolType < 2; ++poolType) { - ctx.allocator->SetCurrentFrameIndex(frameIndex); - D3D12MA::Allocation* alloc = nullptr; - CHECK_HR(ctx.allocator->CreateResource( - &allocDesc, - &resourceDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, - NULL, - &alloc, - IID_NULL, - NULL)); - - WCHAR* statsString; - ctx.allocator->BuildStatsString(&statsString, TRUE); - const UINT BUFFER_SIZE = 1024; - WCHAR buffer[BUFFER_SIZE]; - for (UINT testIndex = BEGIN_INDEX; testIndex < END_INDEX; ++testIndex) + // Select different heaps + for (UINT8 heapType = 0; heapType < 5; ++heapType) { - swprintf(buffer, BUFFER_SIZE, L"\"CreationFrameIndex\": %u", testIndex); - if (testIndex == frameIndex) + D3D12_RESOURCE_STATES state; + D3D12_CPU_PAGE_PROPERTY cpuPageType = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + D3D12_MEMORY_POOL memoryPool = D3D12_MEMORY_POOL_UNKNOWN; + switch (heapType) { - CHECK_BOOL(wcsstr(statsString, buffer) != NULL); + case 0: + allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; + state = D3D12_RESOURCE_STATE_COMMON; + break; + case 1: + allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; + state = D3D12_RESOURCE_STATE_GENERIC_READ; + break; + case 2: + allocDesc.HeapType = D3D12_HEAP_TYPE_READBACK; + state = D3D12_RESOURCE_STATE_COPY_DEST; + break; + case 3: + allocDesc.HeapType = D3D12_HEAP_TYPE_CUSTOM; + state = D3D12_RESOURCE_STATE_COMMON; + cpuPageType = D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE; + memoryPool = ctx.allocator->IsUMA() ? D3D12_MEMORY_POOL_L0 : D3D12_MEMORY_POOL_L1; + break; + case 4: + allocDesc.HeapType = D3D12_HEAP_TYPE_CUSTOM; + state = D3D12_RESOURCE_STATE_GENERIC_READ; + cpuPageType = D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE; + memoryPool = D3D12_MEMORY_POOL_L0; + break; } - else + // Skip custom heaps for default pools + if (poolType == 0 && heapType > 2) + continue; + const bool texturesPossible = heapType == 0 || heapType == 3; + + // Select different resource region types + for (UINT8 resType = 0; resType < 3; ++resType) { - CHECK_BOOL(wcsstr(statsString, buffer) == NULL); + allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; + D3D12_RESOURCE_FLAGS resFlags = D3D12_RESOURCE_FLAG_NONE; + if (texturesPossible) + { + switch (resType) + { + case 1: + allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES; + break; + case 2: + allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; + resFlags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + break; + } + } + + switch (poolType) + { + case 0: + allocDesc.CustomPool = nullptr; + break; + case 1: + { + ComPtr pool; + D3D12MA::POOL_DESC poolDesc = {}; + poolDesc.HeapFlags = allocDesc.ExtraHeapFlags; + poolDesc.HeapProperties.Type = allocDesc.HeapType; + poolDesc.HeapProperties.CPUPageProperty = cpuPageType; + poolDesc.HeapProperties.MemoryPoolPreference = memoryPool; + CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); + + allocDesc.CustomPool = pool.Get(); + pools.emplace_back(std::move(pool)); + break; + } + } + + // Select different allocation flags + for (UINT8 allocFlag = 0; allocFlag < 2; ++allocFlag) + { + switch (allocFlag) + { + case 0: + allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE; + break; + case 1: + allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; + break; + } + + // Select different alloc types (block, buffer, texture, etc.) + for (UINT8 allocType = 0; allocType < 5; ++allocType) + { + // Select different data stored in the allocation + for (UINT8 data = 0; data < 4; ++data) + { + ComPtr alloc; + + if (texturesPossible && resType != 0) + { + resDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + switch (allocType % 3) + { + case 0: + resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE1D; + resDesc.Width = 512; + resDesc.Height = 1; + resDesc.DepthOrArraySize = 1; + resDesc.Flags = resFlags; + CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); + break; + case 1: + resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resDesc.Width = 1024; + resDesc.Height = 512; + resDesc.DepthOrArraySize = 1; + resDesc.Flags = resFlags; + CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); + break; + case 2: + resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE3D; + resDesc.Width = 512; + resDesc.Height = 256; + resDesc.DepthOrArraySize = 128; + resDesc.Flags = resFlags; + CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); + break; + } + } + else + { + switch (allocType % 2) + { + case 0: + CHECK_HR(ctx.allocator->AllocateMemory(&allocDesc, &allocInfo, &alloc)); + break; + case 1: + FillResourceDescForBuffer(resDesc, 1024); + CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); + break; + } + } + + switch (data) + { + case 1: + alloc->SetPrivateData((void*)16112007); + break; + case 2: + alloc->SetName(L"SHEPURD"); + break; + case 3: + alloc->SetPrivateData((void*)26012010); + alloc->SetName(L"JOKER"); + break; + } + allocs.emplace_back(std::move(alloc)); + } + } + + } } } - ctx.allocator->FreeStatsString(statsString); - alloc->Release(); } + SaveStatsStringToFile(ctx, L"JSON_D3D12.json"); } static void TestCommittedResourcesAndJson(const TestContext& ctx) @@ -2689,8 +2832,8 @@ static void TestVirtualBlocks(const TestContext& ctx) block->BuildStatsString(&json); { std::wstring str(json); - CHECK_BOOL(str.find(L"\"PrivateData\": 1") != std::wstring::npos); - CHECK_BOOL(str.find(L"\"PrivateData\": 2") != std::wstring::npos); + CHECK_BOOL(str.find(L"\"CustomData\": 1") != std::wstring::npos); + CHECK_BOOL(str.find(L"\"CustomData\": 2") != std::wstring::npos); } block->FreeStatsString(json); @@ -3839,7 +3982,7 @@ static void TestGroupBasics(const TestContext& ctx) TestDebugMargin(ctx); TestDebugMarginNotInVirtualAllocator(ctx); #else - TestFrameIndexAndJson(ctx); + TestJson(ctx); TestCommittedResourcesAndJson(ctx); TestCustomHeapFlags(ctx); TestPlacedResources(ctx); diff --git a/tools/D3d12maDumpVis/D3d12maDumpVis.py b/tools/D3d12maDumpVis/D3d12maDumpVis.py deleted file mode 100644 index cbd5a3c..0000000 --- a/tools/D3d12maDumpVis/D3d12maDumpVis.py +++ /dev/null @@ -1,282 +0,0 @@ -# -# Copyright (c) 2019-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 = 'D3D12MA Dump Visualization 1.0.0' -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 D3D12 Memory Allocator JSON dump.') -argParser.add_argument('DumpFile', type=argparse.FileType(mode='r', encoding='utf_16_le'), help='Path to source JSON file with memory dump created by 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)') -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.get('Flags', 0)), int(objSuballoc.get('Layout', 0)))) - dstBlockList.append(dstBlockObj) - - -def GetDataForHeapType(sHeapType): - global data - if sHeapType in data: - return data[sHeapType] - else: - newHeapTypeData = {'CommittedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPools':{}} - data[sHeapType] = newHeapTypeData - return newHeapTypeData - - -# 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['CommittedAllocations'] - 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']) - for poolData in dictMemType['CustomPools'].values(): - iImgSizeY += len(poolData) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) - for objBlock in poolData['Blocks']: - iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) - for tDedicatedAlloc in poolData['CommittedAllocations']: - iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1]) - fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize) - return iImgSizeY, fPixelsPerByte - - -def TypeToColor(sType, iFlags, iLayout): - if sType == 'FREE': - return 220, 220, 220, 255 - elif sType == 'BUFFER': - return 255, 255, 0, 255 # Yellow - elif sType == 'TEXTURE2D' or sType == 'TEXTURE1D' or sType == 'TEXTURE3D': - if iLayout != 0: # D3D12_TEXTURE_LAYOUT_UNKNOWN - return 0, 255, 0, 255 # Green - else: - if (iFlags & 0x2) != 0: # D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL - return 246, 128, 255, 255 # Pink - elif (iFlags & 0x5) != 0: # D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS - return 179, 179, 255, 255 # Blue - elif (iFlags & 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: - return 175, 175, 175, 255 # Gray - assert False - return 0, 0, 0, 255 - - -def DrawCommittedAllocationBlock(draw, y, tAlloc): - global fPixelsPerByte - iSizeBytes = tAlloc[1] - iSizePixels = int(iSizeBytes * fPixelsPerByte) - draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tAlloc[0], tAlloc[2], tAlloc[3]), 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, 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: - iFlags = tSuballoc[2] - iLayout = tSuballoc[3] - draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType, iFlags, iLayout), 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 KB" % iBytes - iBytes /= 1024 - if iBytes < 1024: - return "%d MB" % iBytes - iBytes /= 1024 - return "%d GB" % iBytes - - -jsonSrc = json.load(args.DumpFile) -objDetailedMap = jsonSrc['DetailedMap'] -if 'CommittedAllocations' in objDetailedMap: - for tType in objDetailedMap['CommittedAllocations'].items(): - sHeapType = tType[0] - typeData = GetDataForHeapType(sHeapType) - for objAlloc in tType[1]: - typeData['CommittedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc.get('Flags', 0)), int(objAlloc.get('Layout', 0)))) -if 'DefaultPools' in objDetailedMap: - for tType in objDetailedMap['DefaultPools'].items(): - sHeapType = tType[0] - typeData = GetDataForHeapType(sHeapType) - for sBlockId, objBlock in tType[1]['Blocks'].items(): - ProcessBlock(typeData['DefaultPoolBlocks'], int(sBlockId), objBlock, '') -if 'Pools' in objDetailedMap: - for tType in objDetailedMap['Pools'].items(): - sHeapType = tType[0] - typeData = GetDataForHeapType(sHeapType) - for pool in tType[1]: - typeData['CustomPools'][pool['Name']] = {'Blocks':[], 'CommittedAllocations':[]} - for sBlockId, objBlock in pool['Blocks'].items(): - ProcessBlock(typeData['CustomPools'][pool['Name']]['Blocks'], int(sBlockId), objBlock, '') - for objAlloc in pool['CommittedAllocations']: - typeData['CustomPools'][pool['Name']]['CommittedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc.get('Flags', 0)), int(objAlloc.get('Layout', 0)))) - -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 sHeapType in data.keys(): - dictMemType = data[sHeapType] - draw.text((IMG_MARGIN, y), sHeapType, fill=COLOR_TEXT_H1, font=font) - y += FONT_SIZE + IMG_MARGIN - index = 0 - for tCommittedAlloc in dictMemType['CommittedAllocations']: - draw.text((IMG_MARGIN, y), "Committed allocation %d" % index, fill=COLOR_TEXT_H2, font=font) - y += FONT_SIZE + IMG_MARGIN - DrawCommittedAllocationBlock(draw, y, tCommittedAlloc) - 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 - for sPoolName, pool in dictMemType['CustomPools'].items(): - index = 0 - 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 - index = 0 - for objAlloc in pool['CommittedAllocations']: - draw.text((IMG_MARGIN, y), "Custom pool %s%s committed allocation %d" % (sPoolName, sAlgorithm, index), fill=COLOR_TEXT_H2, font=font) - y += FONT_SIZE + IMG_MARGIN - DrawCommittedAllocationBlock(draw, y, objAlloc) - y += MAP_SIZE + IMG_MARGIN - index += 1 -del draw -img.save(args.output) - -""" -Main data structure - variable `data` - is a dictionary. Key is string - heap type ('DEFAULT', 'UPLOAD', or 'READBACK'). Value is dictionary of: -- Fixed key 'CommittedAllocations'. Value is list of tuples, each containing: - - [0]: Type : string - - [1]: Size : integer - - [2]: Flags : integer (0 if unknown) - - [3]: Layout : 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 'CommittedAllocations'. Value is list of tuples as above. -""" 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..0213f52 --- /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. +* ![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 or 1D/2D/3D texture with usage containing D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL. +* ![Image 2](README_files/Legend_Image_2.png "Image 2") Image with OPTIMAL tiling and usage containing INPUT_ATTACHMENT, TRANSIENT_ATTACHMENT or COLOR_ATTACHMENT, or 1D/2D/3D texture with usage containing D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET or D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS. +* ![Image 3](README_files/Legend_Image_3.png "Image 3") Image with OPTIMAL tiling and usage containing SAMPLED or 1D/2D/3D texture with usage not containing D3D12_RESOURCE_FLAG_DENY_SHARED_RESOURCE. +* ![Image 4](README_files/Legend_Image_4.png "Image 4") Other image with OPTIMAL tiling or 1D/2D/3D texture. +* ![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/GpuMemDumpVis/README_files/ExampleOutput.png b/tools/GpuMemDumpVis/README_files/ExampleOutput.png new file mode 100644 index 0000000000000000000000000000000000000000..2646ed225d4efa623bfb460ab7a53d9907b81569 GIT binary patch literal 98005 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 literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Buffer_2.png b/tools/GpuMemDumpVis/README_files/Legend_Buffer_2.png new file mode 100644 index 0000000000000000000000000000000000000000..bc3773fd6283298753e3ae7f16ff43dfc86cf2bb GIT binary patch literal 1590 zcmV-62Fdw}P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=NGmg^`ChTpY{UIIc0!EzYS=^ga)`^Q6S$i1ml zTz&fG+KC5@5WhaaU>g7YJ?SrO5~HJ_mSV;gwva>i1sBQfTiK56!gkzm&S(5Foo)|2 zk_2bjZ&N(d2hu(A`y;&_}Z(+cn)**=bsF5w;7`!u!8Q00XV2E2^6&2h3-yD6ur!%WZo%|3M zhvQa5LG>j`!C7&{wO=WQYcGOhki6Fvv^+V(bw1P zo)6>`p?Ew0GM)7;o!%qexV`fn#-7k68gO>lRP2p2dtjU#xO3sjV>>r)sABNL8_wQ0FWd-;2{5v6 z8}6`np57!&2YfFL%|VdrZ*}3#ZQk59bg}L_CqnCC6vi;Qw9_-0=3Mb%c4-SY?|l7g z*{H=L$4riq4Mcbz-1G?W5#~?Z;wm7hTbL6B7FSmZz+aW$8kcOK1sN6Ry&tjlqX2{m zHBeNB1q57VQI71)dopGOG67IQ)x88?frV9lMfKZ8q zPMAQb09FW!_G2NT4HXp)sv6ZaY1R@Yt{xVn#WkW96APvm%`92ALP=6cnqsn)QcflN zEEsV$qd(@9b1nr=3ra4qF33FCEjcI&C< zUIq^|r4fdXIBcYmN1149Q)ZYtmMJ&2OYuM!Qi%s@Yitui-=& zGZ6EE0o)b=7V>^EbBROni`-)7@)`owqmsJdbY8?jFbwuO`Nr;zoRY+!;YI-Ae}Wt> zbWf1G=k_aVy=>!_Y=l-fTtRh$-q&mKX)72;sf>d6oDUIiI)r4Xm+q`;G>ueR%*}EM z+NGF>tf5d-gTncYqJZn14V|sQwK7K19#>kQ*{1LutZh!<*HJve)`hqR8#O2t%S z;Em-*H3{$y3!Afygc@Pk9oEdI3nMi)Og=E@K&SV$1?iZhtZU4C9-ir|3}2r`s^&?n za$&2SObo+KmX>In7Fk*xw8W*Q*XC9nUBJr-YMZ{-P3zIUbWF!K@oodc>2wwQ zH)8ahbh?WDlulQ%|C!eOnb!Q7*8G{){7=)G^|?h|Dm}WGMo{yiMyw$nZQomZjGYy~ zm|HBtattP2O7_M4h$+`RJ64ETo;EBoJt8Loi_zBi#i-Xw4Kc$yl>7G0#X05ulZ(aj zg!!BG`VtZ1TDyL*#On6m_M_z|LwerlW0&;2&&Mw5d7qD6((^tayQJ4bd+(B73+=s2 zdM&i~F6nv;+70R3p)FccEQ}r%TxImA;ZkhqBFz~_9V@M`-aS&$fHKjCD@z`@%lKh5 zO3;}x>-ZNt2%UtO7HM_>000JJOGiWipa7r%poYrYVE_OC32;bRa{vGf6951U69E94 zoEQKA00(qQO+^Rd1riSzGGm~UW&i*H8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*P zO;A^X4i^9b06R%UK~zY`W1KyE_J0N%0u}}a28I-cdo(e8&I2U|CI%XVQ42;b7`0&3 of>8@bEf}?6a9F?stNm#O0G!GPqLgw#9RL6T07*qoM6N<$f`YyJ7ytkO literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Buffer_3.png b/tools/GpuMemDumpVis/README_files/Legend_Buffer_3.png new file mode 100644 index 0000000000000000000000000000000000000000..992d8b295330b6a876b6edb6390727ea5fc7cfe2 GIT binary patch literal 1877 zcmZ8iX*3&X7Y>Gq2>Ky*(m|^RrM8wCMS{kXF=*`>G(qhm#MTO0id3gctEeq#Eu)sE z6g9Pvj4ds-Dyu`gBCN&5ACKfd=p_qpeN?sM<^=Q-!5*`ur=Lb5^t004r#gmBJG#aB!aR%K-o&K!|m6!8>>pU}3mW-)liw7(ONp3&TcV^92B+rzozjxSI1~N6#Dw zPB^LOJfQpE$(0+}Jgr*H+S85Nt~;4$A$TUo)w($EFyX7nAu!|WizPioQJbNQ2d>{& z9?_^76Fk{WnMGv=9(kbdf}s{kuY#V?))@*6=q?M5-j6y*-Svc5u1it3Z?zd>=yM(D z8+etrNUyz=GwYDF*|@0bn8_4ueEPx{u~l@!+PD#IlK>MN|{2=chK4CPODooLMM5N<44tKXC zaYn`KSr?Iq=NIp95v{q&EzT#4|Z7YF(7``5-%tyjxS&4=Ej}^up#)C=^DyvP+JoT)Ewe!!p6OSxD zX*Od(nopljgl^x4Ps;%;Op5_sy!<4;-}Zu%C=$75n46|DBRXk8i5fKla;9zrr$xiH z!6SxjP+OH7#a)LaX8ts~Q-x%&2G{r?u_^9>5XMSIR;}v$kCshl6zL9po_|7KhIQ~m zOm2$7g&vh6r1TG^eMo1PG524G>wBA2V>IcJn8wTTmrqNTFoZbDSN=tbbAnG!One44$gw-F?r0DK((ACZR^k ziSDW2nw=-v1=r6`3DfI%i^2y8^weAxRIK3Dz_3OK5K%)YMuqPw*8w34pwh9R*HK4} zY2>bOG{UyKpt^aHEN3S8S)5ACX7e+SM_IaKZs>MZo01EV)0LLmpWXTmmaoW3rPk(e zQzDBT`uqmIrb?*zaAya3xXPV$- zakf1JRPaee@$o%Ej&ZEr%=$~R?p7_%%5TY&k=Z`F$4kKLZwxh)s}l*m3Z9TQU4PE| z2JVdj?dHBpu{m7}E28a~F4?ZM*(*_P2r2~XWu^Af6D~Npi%7=pZp1_foY3vK43pWW zIf@e>bm|$Ankt}rks=vR$@?Z7W6)F8Z^=Fas;H0tRM)}Vk=rPDfw`gd#(T>vM~ls^ zUn@#hd$WAo`B3pMon?@E6GSWcZTkx2pBJH%kYVa?jwulzXk`dUpmh1Qsw#3(-IlG; zb!vJWy*u8zUx}|+sgwza4`&yMZ-M0SmcY5qVWuf2)C1;qS}p_JV%z(^|Lg4W9q^%+ z2^u2$9c`>bdTCM%X779Ys=wprw82)(b@>G|0*fvv+7hVr7e?7|jqa7qp6cu1pQYT* z58v^N9`XEqzVjO;$e{wmcg(+06UD82oB_C56A=?;?e{5u!9S3=dTud1%#4+)NWqVr zh!}7M@!W#+r}Brizv}KP|NOt6rl6&D7JZ)k!wJP5`1(gfI#WTJ)AqtvvG7M#y%kC% zs=sRAC+67Ld(D=C1ZzqqhepP0L_Df&+pYJwzIouXta9Ko)Nl^E)ZiJDSoYIdKvYXj z8*$6pb+j#u7t~ka%#!MO{mL4arU`}N*r{t%zIBm+lv@Jy2LfB+jb1@wxRXH66q50+8K z=SeZ5p~pWRk~SiUXT$z$v7x_PsS4lBXQaHY{{LoUGXv%y^Bmnmw&G&uno25eXB2um z+|k0>ZYve7+Q}Pr+A=1a{kGSR6X3h|t{9y~W~JetBfj!savP$&4`~q2CKB+mu|fcf zxi^J6<;`J0VV7JZ006K!H+TSf1>Cg-#3Su2fQ$V95tY_ZG;E}DC>gwk3mzUC9E=UY z1K>EUHy(?D5w79=VOB^xd*?(EDUQepi7<1z)W;gDhX7#0d}hh#)NL7SLXyfZr3Xr{ vont3Vgsx;gf0s!jxH~Rz^xSg{SbddH1uobCj%WROWW=SA7AQomxmVJ^@1u*F literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Buffer_4.png b/tools/GpuMemDumpVis/README_files/Legend_Buffer_4.png new file mode 100644 index 0000000000000000000000000000000000000000..d29f54e89c314be12ee15d25eee46fd3198315b4 GIT binary patch literal 2195 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 V2r^V86(s-w002ovPDHLkV1h9f6r2D6 literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Details.png b/tools/GpuMemDumpVis/README_files/Legend_Details.png new file mode 100644 index 0000000000000000000000000000000000000000..a9c8535273bfcc354651f6beb04256d84c34bfda GIT binary patch literal 964 zcmV;#13UbQP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=H0lH@21hWDIej({YD#Bnf>h`zxbe|~K2%BrrY zsfq5zEE*z#l!YYpA!mm1_m3I=LL;#plA7m|b3{ue6|Pu#yjnd+is>A$c0Z%{@<8_k zhDb2W*3Vl$AZO_FmbY!ELar}{%GeK%_Ydke#J(Ne5p738mxH(shq?_3Z_nxaeq6_j zxwyR^p>6$HUyh1TSVI!yteGrfOdQQ}!P+?{L*DudRP6NsIQsZK+^US2cnC?vct$7) zUtB6?#fUX8iDAu{mwX=?v^Ye=y4-pFx_uRSxI^Dq-yv1~9QCt$AsxkLP#tDbJ3FT)6Sg*?rD&ovx;gBjz>B6OC-2e6PTcLd2vBm)kr0KT!u=u~CpONEvzxmYuZ4@?jjILqSiF={E1XtX zuGn3nQ0u{m9O=*_A9narj@mdhX{l+;&6>B;YUeH;b=-5e?!ELnXy7W1G<4))!$%o) z;;K!VY3j_=rq2>)eW=kc+mHP}P@_$aPf|Fwg`{}=EW>J zrNoQeVipUdDwGkVE;yYQF%V3HSSLN~-pFZ?{Cl_sfZ*RjE-ZB4Aonx3i!zPIEg zw90TLH43V4mN~q#*Y!*J zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3#vdg~|*h5u_6vjh+b!E*R~p4q`He-1WDLz>&$ zx`|t3V;Lm992%ASfBz2p2Or15kQa?1g<$aU$tTXZi01s{<7|yT&gaeTnce&8{(#Zt z(7N+BhSlHE&#>p!U)MMp{qitS>gz$-ALKO<$2Gy~zfI)qIe}RNQ(gl(UNigizMNs1 zkfJ_@P?!B!&yO_Vm*&Yk}I)n4! zp@aJ_x{OwE#x>6##Wf%O_?(`C7L!q2r(0gX{(R**-CSSDH>CF~`-lDq(sU&B_P{+6 z3itM9zm<=EdPI6~dzd+d`^;NEA}CwTH)`{Dj26e}XLQ*IgdA2-_FOFIf`MI`**e|| zI26BP;!O5D5{?K-RN!oQr{JhISq8>oy=^wzVf)cmTj8ST&We_~va^-Y837}4J#hPZ z*umv<^qE{82+EP>hyb%Ga|GZoVm}s_J3u=a3ffagR@)i?DS`(A zXRHGO6RZ;(tWjoCYmvwZKpC#H^IU+%ig1II?41jM0~KIyyrcFoW#O|M8zDf53j`5H z5Xyk%yXs9BwQ6%DEyHEE{kqK^_o)EHxmIdO8> zaV0^IDW;Ti#-te|XS|(ZkaOYUiZ4<^(Gp84xiVE(eU%!j)>u=`jhk!GaSKgbY^mjL zJC;)SU3%!+V^2K~EVbc=A2Pzw5l5nt@6;C7Psb0a*+q>HQqxfH)X=MX8^X@*L?$y3 zV{ZXmCjsk-c``H6de4*GWM&dW6~b^*CY)v`F%b0KvJ87?_d!m`h4*kHfZ%tKqlxYt z&UP}%;PYjn@t=AZ;jS<)Hx7rsvRv2ffbV8 zpPtV*MyX>Ur*m|Y=tglJRsk`zCxhKLABxctw85x7`A$#H$B|Z*bb&e4P2Y` zkV0>%4?{9>}pX5{{C;FAYC7^ zmbL=3Fc>x#H)jE#prsx&3eE4O^WwMd7XwL{F`&Iiq0eC|_R~J_UAPe|Ic*VVPR^qvh;0$o?vE-t#RuDzvq{7sv*=(@UganW^k z?Jd0p83Li}>e|Ic*VVPR^lMxz2)eGWU0ifsU3*KvLiMob=$q<7RjVeUG!c|3mVMWl zbA$V?F)Hq?#684Ri_f{{2*)8jDar zkIe?<399zd8mf~xb2eSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{ z0028lL_t(Y$77s5d-i_@8Uhvu1_p-crztcse5v#t1||j?gHa1cEf}?6)Phk9MlBe% hU~pK#0;~OL1pu3q2Vmvl@n--4002ovPDHLkV1iZ1HKqUn literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Image_2.png b/tools/GpuMemDumpVis/README_files/Legend_Image_2.png new file mode 100644 index 0000000000000000000000000000000000000000..fc35c7cd559f3fc0f3371f5ad5c621b0fb30d94e GIT binary patch literal 1592 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 LNkvXXu0mjf%lmym literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Image_Linear.png b/tools/GpuMemDumpVis/README_files/Legend_Image_Linear.png new file mode 100644 index 0000000000000000000000000000000000000000..36d8be511ceec7d382aa8beceaef31c986481ccc GIT binary patch literal 1432 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 Dx~rUc literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Texture_1.png b/tools/GpuMemDumpVis/README_files/Legend_Texture_1.png new file mode 100644 index 0000000000000000000000000000000000000000..dc180afb93147f9033eda6cb1031a3722a768c02 GIT binary patch literal 1739 zcmV;+1~mDJP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3#vdg~|*h5u_6vjh+b!E*R~p4q`He-1WDLz>&$ zx`|t3V;Lm992%ASfBz2p2Or15kQa?1g<$aU$tTXZi01s{<7|yT&gaeTnce&8{(#Zt z(7N+BhSlHE&#>p!U)MMp{qitS>gz$-ALKO<$2Gy~zfI)qIe}RNQ(gl(UNigizMNs1 zkfJ_@P?!B!&yO_Vm*&Yk}I)n4! zp@aJ_x{OwE#x>6##Wf%O_?(`C7L!q2r(0gX{(R**-CSSDH>CF~`-lDq(sU&B_P{+6 z3itM9zm<=EdPI6~dzd+d`^;NEA}CwTH)`{Dj26e}XLQ*IgdA2-_FOFIf`MI`**e|| zI26BP;!O5D5{?K-RN!oQr{JhISq8>oy=^wzVf)cmTj8ST&We_~va^-Y837}4J#hPZ z*umv<^qE{82+EP>hyb%Ga|GZoVm}s_J3u=a3ffagR@)i?DS`(A zXRHGO6RZ;(tWjoCYmvwZKpC#H^IU+%ig1II?41jM0~KIyyrcFoW#O|M8zDf53j`5H z5Xyk%yXs9BwQ6%DEyHEE{kqK^_o)EHxmIdO8> zaV0^IDW;Ti#-te|XS|(ZkaOYUiZ4<^(Gp84xiVE(eU%!j)>u=`jhk!GaSKgbY^mjL zJC;)SU3%!+V^2K~EVbc=A2Pzw5l5nt@6;C7Psb0a*+q>HQqxfH)X=MX8^X@*L?$y3 zV{ZXmCjsk-c``H6de4*GWM&dW6~b^*CY)v`F%b0KvJ87?_d!m`h4*kHfZ%tKqlxYt z&UP}%;PYjn@t=AZ;jS<)Hx7rsvRv2ffbV8 zpPtV*MyX>Ur*m|Y=tglJRsk`zCxhKLABxctw85x7`A$#H$B|Z*bb&e4P2Y` zkV0>%4?{9>}pX5{{C;FAYC7^ zmbL=3Fc>x#H)jE#prsx&3eE4O^WwMd7XwL{F`&Iiq0eC|_R~J_UAPe|Ic*VVPR^qvh;0$o?vE-t#RuDzvq{7sv*=(@UganW^k z?Jd0p83Li}>e|Ic*VVPR^lMxz2)eGWU0ifsU3*KvLiMob=$q<7RjVeUG!c|3mVMWl zbA$V?F)Hq?#684Ri_f{{2*)8jDar zkIe?<399zd8mf~xb2eSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{ z0028lL_t(Y$77s5d-i_@8Uhvu1_p-crztcse5v#t1||j?gHa1cEf}?6)Phk9MlBe% hU~pK#0;~OL1pu3q2Vmvl@n--4002ovPDHLkV1iZ1HKqUn literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Texture_2.png b/tools/GpuMemDumpVis/README_files/Legend_Texture_2.png new file mode 100644 index 0000000000000000000000000000000000000000..fc35c7cd559f3fc0f3371f5ad5c621b0fb30d94e GIT binary patch literal 1592 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 LNkvXXu0mjf%lmym literal 0 HcmV?d00001 diff --git a/tools/GpuMemDumpVis/README_files/Legend_Unknown.png b/tools/GpuMemDumpVis/README_files/Legend_Unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..3053726c104abc5ce6e33d69c0413bc8e2403dd5 GIT binary patch literal 1356 zcmV-S1+)5zP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1bxvfU^Q{MRaG2^Js(%Mq`t*}*J-8tn5-a&wb| zT?ShMA$3dGrvBf*gZ{zc)CUTpDdrdt93hA73^(aG4_hrCVYTPOO~1qAE}Bj7>Y_x}fHt^8HD=r=cLW7)Z93|8t$$zW{h4k<9ei@#vp>)-4?W!X z$s2k>i+eXEihJ+Fl3$*JEk}pqKHqTt`uNIoy19N){zB>fj(;J4Vwx79H-&#=DBk;@ z%2^(AdZu*o_Ox=0_myAdfsgzW^NqE6jnUE={fy2+M9A?9iqB*@69(hTL~G58I2D&1 zX@Wh^j5DDmD(GyOQ*c(!>_Fq-z>SRu_tjUfa51><6uooRd9DOu0vXxWMR%Ad50qi{ z4dgptkq(n?clU+YvUx36QDW_AoCw*&Fvghj(MjJKG{(e-`A7?HKKXSsv*E>Lj+qX| zFfhX7;!Q4qFPPts$*Tv0vS3aK(2g=Kfc_%_{Afo6Jqlj_gns;^3rAavD#@z+mX?WbmE42XjI`?%|CgBd zjJ0NK-|~y0S%wqT!=w6IF_)}Nh>HVG0L8jb7;gh{w^3ZsZViS|2r;D5qzpgx@~Z!~ znm(KUP}67AA8N{*VxzJtHu?~ngwG;02TBBQ3+38Gp>x)o=`i!75A1DLO3b4h-gv0V z77q4}gUl}wdFx4jq4?AO-4J%&gdni7J>vKh?XR&G+JyDHp%d$8FTX_lf%TwGSPN~!+UR>o|1PhEX%p6O zhWB1Lw}#wX0!?GgZAXLG409gFspwH)-Q%W$ZPYo z3G1`gKF@2=zbCI}_3y#@+0Z9>?FVhbdez#7ydItQ_CX>@2HM@dakSAh-}0000xNkl_MlBe%VAO(93q~y%wP0{qzyhoNX$1hE!U)I(YBXB_ O0000