2021-12-17 10:00:00 +00:00
#
2022-01-21 11:56:18 +00:00
# Copyright (c) 2018-2022 Advanced Micro Devices, Inc. All rights reserved.
2021-12-17 10:00:00 +00:00
#
# 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 :
2021-12-21 15:23:13 +00:00
for lBlockList in dictMemType [ ' CustomPools ' ] . values ( ) [ ' Blocks ' ] :
2021-12-17 10:00:00 +00:00
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 ' ]
2021-12-21 15:23:13 +00:00
for poolData in dCustomPools . values ( ) :
iImgSizeY + = len ( poolData [ ' Blocks ' ] ) * ( IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE )
for objBlock in poolData [ ' Blocks ' ] :
2021-12-17 10:00:00 +00:00
iMaxBlockSize = max ( iMaxBlockSize , objBlock [ ' Size ' ] )
2021-12-21 15:23:13 +00:00
iImgSizeY + = len ( poolData [ ' DedicatedAllocations ' ] ) * ( IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE )
for tDedicatedAlloc in poolData [ ' DedicatedAllocations ' ] :
iMaxBlockSize = max ( iMaxBlockSize , tDedicatedAlloc [ 1 ] )
2021-12-17 10:00:00 +00:00
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
2021-12-21 15:23:13 +00:00
typeData [ ' CustomPools ' ] [ sFullName ] = { ' Blocks ' : [ ] , ' DedicatedAllocations ' : [ ] }
2021-12-17 10:00:00 +00:00
for sBlockId , objBlock in objBlocks . items ( ) :
2021-12-21 15:23:13 +00:00
ProcessBlock ( typeData [ ' CustomPools ' ] [ sFullName ] [ ' Blocks ' ] , int ( sBlockId ) , objBlock , sAlgorithm )
2021-12-17 10:00:00 +00:00
if ' DedicatedAllocations ' in objPool :
2021-12-21 15:23:13 +00:00
for objAlloc in objPool [ ' DedicatedAllocations ' ] :
typeData [ ' CustomPools ' ] [ sFullName ] [ ' DedicatedAllocations ' ] . append ( ( objAlloc [ ' Type ' ] , int ( objAlloc [ ' Size ' ] ) , int ( objAlloc [ ' Usage ' ] ) if ( ' Usage ' in objAlloc ) else 0 ) )
2021-12-17 10:00:00 +00:00
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
2021-12-21 15:23:13 +00:00
for sPoolName , pool in dictMemType [ ' CustomPools ' ] . items ( ) :
for objBlock in pool [ ' Blocks ' ] :
2021-12-17 10:00:00 +00:00
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 )
2021-12-21 15:23:13 +00:00
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 )
2021-12-17 10:00:00 +00:00
y + = FONT_SIZE + IMG_MARGIN
2021-12-21 15:23:13 +00:00
DrawDedicatedAllocationBlock ( draw , y , objAlloc )
2021-12-17 10:00:00 +00:00
y + = MAP_SIZE + IMG_MARGIN
2021-12-21 15:23:13 +00:00
alloc_index + = 1
2021-12-17 10:00:00 +00:00
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 .
2021-12-21 15:23:13 +00:00
- 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 .
2021-12-17 10:00:00 +00:00
- Fixed key ' DedicatedAllocations ' . Value is list of tuples as above .
"""