1# 2# Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved. 3# 4# Permission is hereby granted, free of charge, to any person obtaining a copy 5# of this software and associated documentation files (the "Software"), to deal 6# in the Software without restriction, including without limitation the rights 7# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8# copies of the Software, and to permit persons to whom the Software is 9# furnished to do so, subject to the following conditions: 10# 11# The above copyright notice and this permission notice shall be included in 12# all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20# THE SOFTWARE. 21# 22 23import argparse 24import json 25from PIL import Image, ImageDraw, ImageFont 26 27 28PROGRAM_VERSION = 'VMA Dump Visualization 2.0.1' 29IMG_SIZE_X = 1200 30IMG_MARGIN = 8 31FONT_SIZE = 10 32MAP_SIZE = 24 33COLOR_TEXT_H1 = (0, 0, 0, 255) 34COLOR_TEXT_H2 = (150, 150, 150, 255) 35COLOR_OUTLINE = (155, 155, 155, 255) 36COLOR_OUTLINE_HARD = (0, 0, 0, 255) 37COLOR_GRID_LINE = (224, 224, 224, 255) 38 39 40argParser = argparse.ArgumentParser(description='Visualization of Vulkan Memory Allocator JSON dump.') 41argParser.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') 42argParser.add_argument('-v', '--version', action='version', version=PROGRAM_VERSION) 43argParser.add_argument('-o', '--output', required=True, help='Path to destination image file (e.g. PNG)') 44args = argParser.parse_args() 45 46data = {} 47 48 49def ProcessBlock(dstBlockList, iBlockId, objBlock, sAlgorithm): 50 iBlockSize = int(objBlock['TotalBytes']) 51 arrSuballocs = objBlock['Suballocations'] 52 dstBlockObj = {'ID': iBlockId, 'Size':iBlockSize, 'Suballocations':[]} 53 dstBlockObj['Algorithm'] = sAlgorithm 54 for objSuballoc in arrSuballocs: 55 dstBlockObj['Suballocations'].append((objSuballoc['Type'], int(objSuballoc['Size']), int(objSuballoc['Usage']) if ('Usage' in objSuballoc) else 0)) 56 dstBlockList.append(dstBlockObj) 57 58 59def GetDataForMemoryType(iMemTypeIndex): 60 global data 61 if iMemTypeIndex in data: 62 return data[iMemTypeIndex] 63 else: 64 newMemTypeData = {'DedicatedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPools':{}} 65 data[iMemTypeIndex] = newMemTypeData 66 return newMemTypeData 67 68 69def IsDataEmpty(): 70 global data 71 for dictMemType in data.values(): 72 if 'DedicatedAllocations' in dictMemType and len(dictMemType['DedicatedAllocations']) > 0: 73 return False 74 if 'DefaultPoolBlocks' in dictMemType and len(dictMemType['DefaultPoolBlocks']) > 0: 75 return False 76 if 'CustomPools' in dictMemType: 77 for lBlockList in dictMemType['CustomPools'].values(): 78 if len(lBlockList) > 0: 79 return False 80 return True 81 82 83# Returns tuple: 84# [0] image height : integer 85# [1] pixels per byte : float 86def CalcParams(): 87 global data 88 iImgSizeY = IMG_MARGIN 89 iImgSizeY += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes 90 iMaxBlockSize = 0 91 for dictMemType in data.values(): 92 iImgSizeY += IMG_MARGIN + FONT_SIZE 93 lDedicatedAllocations = dictMemType['DedicatedAllocations'] 94 iImgSizeY += len(lDedicatedAllocations) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) 95 for tDedicatedAlloc in lDedicatedAllocations: 96 iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1]) 97 lDefaultPoolBlocks = dictMemType['DefaultPoolBlocks'] 98 iImgSizeY += len(lDefaultPoolBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) 99 for objBlock in lDefaultPoolBlocks: 100 iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) 101 dCustomPools = dictMemType['CustomPools'] 102 for lBlocks in dCustomPools.values(): 103 iImgSizeY += len(lBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) 104 for objBlock in lBlocks: 105 iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) 106 fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize) 107 return iImgSizeY, fPixelsPerByte 108 109 110def TypeToColor(sType, iUsage): 111 if sType == 'FREE': 112 return 220, 220, 220, 255 113 elif sType == 'BUFFER': 114 if (iUsage & 0x1C0) != 0: # INDIRECT_BUFFER | VERTEX_BUFFER | INDEX_BUFFER 115 return 255, 148, 148, 255 # Red 116 elif (iUsage & 0x28) != 0: # STORAGE_BUFFER | STORAGE_TEXEL_BUFFER 117 return 255, 187, 121, 255 # Orange 118 elif (iUsage & 0x14) != 0: # UNIFORM_BUFFER | UNIFORM_TEXEL_BUFFER 119 return 255, 255, 0, 255 # Yellow 120 else: 121 return 255, 255, 165, 255 # Light yellow 122 elif sType == 'IMAGE_OPTIMAL': 123 if (iUsage & 0x20) != 0: # DEPTH_STENCIL_ATTACHMENT 124 return 246, 128, 255, 255 # Pink 125 elif (iUsage & 0xD0) != 0: # INPUT_ATTACHMENT | TRANSIENT_ATTACHMENT | COLOR_ATTACHMENT 126 return 179, 179, 255, 255 # Blue 127 elif (iUsage & 0x4) != 0: # SAMPLED 128 return 0, 255, 255, 255 # Aqua 129 else: 130 return 183, 255, 255, 255 # Light aqua 131 elif sType == 'IMAGE_LINEAR': 132 return 0, 255, 0, 255 # Green 133 elif sType == 'IMAGE_UNKNOWN': 134 return 0, 255, 164, 255 # Green/aqua 135 elif sType == 'UNKNOWN': 136 return 175, 175, 175, 255 # Gray 137 assert False 138 return 0, 0, 0, 255 139 140 141def DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc): 142 global fPixelsPerByte 143 iSizeBytes = tDedicatedAlloc[1] 144 iSizePixels = int(iSizeBytes * fPixelsPerByte) 145 draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tDedicatedAlloc[0], tDedicatedAlloc[2]), outline=COLOR_OUTLINE) 146 147 148def DrawBlock(draw, y, objBlock): 149 global fPixelsPerByte 150 iSizeBytes = objBlock['Size'] 151 iSizePixels = int(iSizeBytes * fPixelsPerByte) 152 draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0), outline=None) 153 iByte = 0 154 iX = 0 155 iLastHardLineX = -1 156 for tSuballoc in objBlock['Suballocations']: 157 sType = tSuballoc[0] 158 iByteEnd = iByte + tSuballoc[1] 159 iXEnd = int(iByteEnd * fPixelsPerByte) 160 if sType != 'FREE': 161 if iXEnd > iX + 1: 162 iUsage = tSuballoc[2] 163 draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType, iUsage), outline=COLOR_OUTLINE) 164 # Hard line was been overwritten by rectangle outline: redraw it. 165 if iLastHardLineX == iX: 166 draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) 167 else: 168 draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) 169 iLastHardLineX = iX 170 iByte = iByteEnd 171 iX = iXEnd 172 173 174def BytesToStr(iBytes): 175 if iBytes < 1024: 176 return "%d B" % iBytes 177 iBytes /= 1024 178 if iBytes < 1024: 179 return "%d KiB" % iBytes 180 iBytes /= 1024 181 if iBytes < 1024: 182 return "%d MiB" % iBytes 183 iBytes /= 1024 184 return "%d GiB" % iBytes 185 186 187jsonSrc = json.load(args.DumpFile) 188if 'DedicatedAllocations' in jsonSrc: 189 for tType in jsonSrc['DedicatedAllocations'].items(): 190 sType = tType[0] 191 assert sType[:5] == 'Type ' 192 iType = int(sType[5:]) 193 typeData = GetDataForMemoryType(iType) 194 for objAlloc in tType[1]: 195 typeData['DedicatedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc['Usage']) if ('Usage' in objAlloc) else 0)) 196if 'DefaultPools' in jsonSrc: 197 for tType in jsonSrc['DefaultPools'].items(): 198 sType = tType[0] 199 assert sType[:5] == 'Type ' 200 iType = int(sType[5:]) 201 typeData = GetDataForMemoryType(iType) 202 for sBlockId, objBlock in tType[1]['Blocks'].items(): 203 ProcessBlock(typeData['DefaultPoolBlocks'], int(sBlockId), objBlock, '') 204if 'Pools' in jsonSrc: 205 objPools = jsonSrc['Pools'] 206 for sPoolId, objPool in objPools.items(): 207 iType = int(objPool['MemoryTypeIndex']) 208 typeData = GetDataForMemoryType(iType) 209 objBlocks = objPool['Blocks'] 210 sAlgorithm = objPool.get('Algorithm', '') 211 sName = objPool.get('Name', None) 212 if sName: 213 sFullName = sPoolId + ' "' + sName + '"' 214 else: 215 sFullName = sPoolId 216 dstBlockArray = [] 217 typeData['CustomPools'][sFullName] = dstBlockArray 218 for sBlockId, objBlock in objBlocks.items(): 219 ProcessBlock(dstBlockArray, int(sBlockId), objBlock, sAlgorithm) 220 221if IsDataEmpty(): 222 print("There is nothing to put on the image. Please make sure you generated the stats string with detailed map enabled.") 223 exit(1) 224 225iImgSizeY, fPixelsPerByte = CalcParams() 226 227img = Image.new('RGB', (IMG_SIZE_X, iImgSizeY), 'white') 228draw = ImageDraw.Draw(img) 229 230try: 231 font = ImageFont.truetype('segoeuib.ttf') 232except: 233 font = ImageFont.load_default() 234 235y = IMG_MARGIN 236 237# Draw grid lines 238iBytesBetweenGridLines = 32 239while iBytesBetweenGridLines * fPixelsPerByte < 64: 240 iBytesBetweenGridLines *= 2 241iByte = 0 242TEXT_MARGIN = 4 243while True: 244 iX = int(iByte * fPixelsPerByte) 245 if iX > IMG_SIZE_X - 2 * IMG_MARGIN: 246 break 247 draw.line([iX + IMG_MARGIN, 0, iX + IMG_MARGIN, iImgSizeY], fill=COLOR_GRID_LINE) 248 if iByte == 0: 249 draw.text((iX + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font) 250 else: 251 text = BytesToStr(iByte) 252 textSize = draw.textsize(text, font=font) 253 draw.text((iX + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font) 254 iByte += iBytesBetweenGridLines 255y += FONT_SIZE + IMG_MARGIN 256 257# Draw main content 258for iMemTypeIndex in sorted(data.keys()): 259 dictMemType = data[iMemTypeIndex] 260 draw.text((IMG_MARGIN, y), "Memory type %d" % iMemTypeIndex, fill=COLOR_TEXT_H1, font=font) 261 y += FONT_SIZE + IMG_MARGIN 262 index = 0 263 for tDedicatedAlloc in dictMemType['DedicatedAllocations']: 264 draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font) 265 y += FONT_SIZE + IMG_MARGIN 266 DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc) 267 y += MAP_SIZE + IMG_MARGIN 268 index += 1 269 for objBlock in dictMemType['DefaultPoolBlocks']: 270 draw.text((IMG_MARGIN, y), "Default pool block %d" % objBlock['ID'], fill=COLOR_TEXT_H2, font=font) 271 y += FONT_SIZE + IMG_MARGIN 272 DrawBlock(draw, y, objBlock) 273 y += MAP_SIZE + IMG_MARGIN 274 index = 0 275 for sPoolName, listPool in dictMemType['CustomPools'].items(): 276 for objBlock in listPool: 277 if 'Algorithm' in objBlock and objBlock['Algorithm']: 278 sAlgorithm = ' (Algorithm: %s)' % (objBlock['Algorithm']) 279 else: 280 sAlgorithm = '' 281 draw.text((IMG_MARGIN, y), "Custom pool %s%s block %d" % (sPoolName, sAlgorithm, objBlock['ID']), fill=COLOR_TEXT_H2, font=font) 282 y += FONT_SIZE + IMG_MARGIN 283 DrawBlock(draw, y, objBlock) 284 y += MAP_SIZE + IMG_MARGIN 285 index += 1 286del draw 287img.save(args.output) 288 289""" 290Main data structure - variable `data` - is a dictionary. Key is integer - memory type index. Value is dictionary of: 291- Fixed key 'DedicatedAllocations'. Value is list of tuples, each containing: 292 - [0]: Type : string 293 - [1]: Size : integer 294 - [2]: Usage : integer (0 if unknown) 295- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with: 296 - Fixed key 'ID'. Value is int. 297 - Fixed key 'Size'. Value is int. 298 - Fixed key 'Suballocations'. Value is list of tuples as above. 299- Fixed key 'CustomPools'. Value is dictionary. 300 - Key is string with pool ID/name. Value is list of objects representing memory blocks, each containing dictionary with: 301 - Fixed key 'ID'. Value is int. 302 - Fixed key 'Size'. Value is int. 303 - Fixed key 'Algorithm'. Optional. Value is string. 304 - Fixed key 'Suballocations'. Value is list of tuples as above. 305""" 306