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 69# Returns tuple: 70# [0] image height : integer 71# [1] pixels per byte : float 72def CalcParams(): 73 global data 74 iImgSizeY = IMG_MARGIN 75 iImgSizeY += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes 76 iMaxBlockSize = 0 77 for dictMemType in data.values(): 78 iImgSizeY += IMG_MARGIN + FONT_SIZE 79 lDedicatedAllocations = dictMemType['DedicatedAllocations'] 80 iImgSizeY += len(lDedicatedAllocations) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) 81 for tDedicatedAlloc in lDedicatedAllocations: 82 iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1]) 83 lDefaultPoolBlocks = dictMemType['DefaultPoolBlocks'] 84 iImgSizeY += len(lDefaultPoolBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) 85 for objBlock in lDefaultPoolBlocks: 86 iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) 87 dCustomPools = dictMemType['CustomPools'] 88 for lBlocks in dCustomPools.values(): 89 iImgSizeY += len(lBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) 90 for objBlock in lBlocks: 91 iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) 92 fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize) 93 return iImgSizeY, fPixelsPerByte 94 95 96def TypeToColor(sType, iUsage): 97 if sType == 'FREE': 98 return 220, 220, 220, 255 99 elif sType == 'BUFFER': 100 if (iUsage & 0x1C0) != 0: # INDIRECT_BUFFER | VERTEX_BUFFER | INDEX_BUFFER 101 return 255, 148, 148, 255 # Red 102 elif (iUsage & 0x28) != 0: # STORAGE_BUFFER | STORAGE_TEXEL_BUFFER 103 return 255, 187, 121, 255 # Orange 104 elif (iUsage & 0x14) != 0: # UNIFORM_BUFFER | UNIFORM_TEXEL_BUFFER 105 return 255, 255, 0, 255 # Yellow 106 else: 107 return 255, 255, 165, 255 # Light yellow 108 elif sType == 'IMAGE_OPTIMAL': 109 if (iUsage & 0x20) != 0: # DEPTH_STENCIL_ATTACHMENT 110 return 246, 128, 255, 255 # Pink 111 elif (iUsage & 0xD0) != 0: # INPUT_ATTACHMENT | TRANSIENT_ATTACHMENT | COLOR_ATTACHMENT 112 return 179, 179, 255, 255 # Blue 113 elif (iUsage & 0x4) != 0: # SAMPLED 114 return 0, 255, 255, 255 # Aqua 115 else: 116 return 183, 255, 255, 255 # Light aqua 117 elif sType == 'IMAGE_LINEAR': 118 return 0, 255, 0, 255 # Green 119 elif sType == 'IMAGE_UNKNOWN': 120 return 0, 255, 164, 255 # Green/aqua 121 elif sType == 'UNKNOWN': 122 return 175, 175, 175, 255 # Gray 123 assert False 124 return 0, 0, 0, 255 125 126 127def DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc): 128 global fPixelsPerByte 129 iSizeBytes = tDedicatedAlloc[1] 130 iSizePixels = int(iSizeBytes * fPixelsPerByte) 131 draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tDedicatedAlloc[0], tDedicatedAlloc[2]), outline=COLOR_OUTLINE) 132 133 134def DrawBlock(draw, y, objBlock): 135 global fPixelsPerByte 136 iSizeBytes = objBlock['Size'] 137 iSizePixels = int(iSizeBytes * fPixelsPerByte) 138 draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0), outline=None) 139 iByte = 0 140 iX = 0 141 iLastHardLineX = -1 142 for tSuballoc in objBlock['Suballocations']: 143 sType = tSuballoc[0] 144 iByteEnd = iByte + tSuballoc[1] 145 iXEnd = int(iByteEnd * fPixelsPerByte) 146 if sType != 'FREE': 147 if iXEnd > iX + 1: 148 iUsage = tSuballoc[2] 149 draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType, iUsage), outline=COLOR_OUTLINE) 150 # Hard line was been overwritten by rectangle outline: redraw it. 151 if iLastHardLineX == iX: 152 draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) 153 else: 154 draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) 155 iLastHardLineX = iX 156 iByte = iByteEnd 157 iX = iXEnd 158 159 160def BytesToStr(iBytes): 161 if iBytes < 1024: 162 return "%d B" % iBytes 163 iBytes /= 1024 164 if iBytes < 1024: 165 return "%d KiB" % iBytes 166 iBytes /= 1024 167 if iBytes < 1024: 168 return "%d MiB" % iBytes 169 iBytes /= 1024 170 return "%d GiB" % iBytes 171 172 173jsonSrc = json.load(args.DumpFile) 174if 'DedicatedAllocations' in jsonSrc: 175 for tType in jsonSrc['DedicatedAllocations'].items(): 176 sType = tType[0] 177 assert sType[:5] == 'Type ' 178 iType = int(sType[5:]) 179 typeData = GetDataForMemoryType(iType) 180 for objAlloc in tType[1]: 181 typeData['DedicatedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc['Usage']) if ('Usage' in objAlloc) else 0)) 182if 'DefaultPools' in jsonSrc: 183 for tType in jsonSrc['DefaultPools'].items(): 184 sType = tType[0] 185 assert sType[:5] == 'Type ' 186 iType = int(sType[5:]) 187 typeData = GetDataForMemoryType(iType) 188 for sBlockId, objBlock in tType[1]['Blocks'].items(): 189 ProcessBlock(typeData['DefaultPoolBlocks'], int(sBlockId), objBlock, '') 190if 'Pools' in jsonSrc: 191 objPools = jsonSrc['Pools'] 192 for sPoolId, objPool in objPools.items(): 193 iType = int(objPool['MemoryTypeIndex']) 194 typeData = GetDataForMemoryType(iType) 195 objBlocks = objPool['Blocks'] 196 sAlgorithm = objPool.get('Algorithm', '') 197 sName = objPool.get('Name', None) 198 if sName: 199 sFullName = sPoolId + ' "' + sName + '"' 200 else: 201 sFullName = sPoolId 202 dstBlockArray = [] 203 typeData['CustomPools'][sFullName] = dstBlockArray 204 for sBlockId, objBlock in objBlocks.items(): 205 ProcessBlock(dstBlockArray, int(sBlockId), objBlock, sAlgorithm) 206 207iImgSizeY, fPixelsPerByte = CalcParams() 208 209img = Image.new('RGB', (IMG_SIZE_X, iImgSizeY), 'white') 210draw = ImageDraw.Draw(img) 211 212try: 213 font = ImageFont.truetype('segoeuib.ttf') 214except: 215 font = ImageFont.load_default() 216 217y = IMG_MARGIN 218 219# Draw grid lines 220iBytesBetweenGridLines = 32 221while iBytesBetweenGridLines * fPixelsPerByte < 64: 222 iBytesBetweenGridLines *= 2 223iByte = 0 224TEXT_MARGIN = 4 225while True: 226 iX = int(iByte * fPixelsPerByte) 227 if iX > IMG_SIZE_X - 2 * IMG_MARGIN: 228 break 229 draw.line([iX + IMG_MARGIN, 0, iX + IMG_MARGIN, iImgSizeY], fill=COLOR_GRID_LINE) 230 if iByte == 0: 231 draw.text((iX + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font) 232 else: 233 text = BytesToStr(iByte) 234 textSize = draw.textsize(text, font=font) 235 draw.text((iX + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font) 236 iByte += iBytesBetweenGridLines 237y += FONT_SIZE + IMG_MARGIN 238 239# Draw main content 240for iMemTypeIndex in sorted(data.keys()): 241 dictMemType = data[iMemTypeIndex] 242 draw.text((IMG_MARGIN, y), "Memory type %d" % iMemTypeIndex, fill=COLOR_TEXT_H1, font=font) 243 y += FONT_SIZE + IMG_MARGIN 244 index = 0 245 for tDedicatedAlloc in dictMemType['DedicatedAllocations']: 246 draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font) 247 y += FONT_SIZE + IMG_MARGIN 248 DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc) 249 y += MAP_SIZE + IMG_MARGIN 250 index += 1 251 for objBlock in dictMemType['DefaultPoolBlocks']: 252 draw.text((IMG_MARGIN, y), "Default pool block %d" % objBlock['ID'], fill=COLOR_TEXT_H2, font=font) 253 y += FONT_SIZE + IMG_MARGIN 254 DrawBlock(draw, y, objBlock) 255 y += MAP_SIZE + IMG_MARGIN 256 index = 0 257 for sPoolName, listPool in dictMemType['CustomPools'].items(): 258 for objBlock in listPool: 259 if 'Algorithm' in objBlock and objBlock['Algorithm']: 260 sAlgorithm = ' (Algorithm: %s)' % (objBlock['Algorithm']) 261 else: 262 sAlgorithm = '' 263 draw.text((IMG_MARGIN, y), "Custom pool %s%s block %d" % (sPoolName, sAlgorithm, objBlock['ID']), fill=COLOR_TEXT_H2, font=font) 264 y += FONT_SIZE + IMG_MARGIN 265 DrawBlock(draw, y, objBlock) 266 y += MAP_SIZE + IMG_MARGIN 267 index += 1 268del draw 269img.save(args.output) 270 271""" 272Main data structure - variable `data` - is a dictionary. Key is integer - memory type index. Value is dictionary of: 273- Fixed key 'DedicatedAllocations'. Value is list of tuples, each containing: 274 - [0]: Type : string 275 - [1]: Size : integer 276 - [2]: Usage : integer (0 if unknown) 277- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with: 278 - Fixed key 'ID'. Value is int. 279 - Fixed key 'Size'. Value is int. 280 - Fixed key 'Suballocations'. Value is list of tuples as above. 281- Fixed key 'CustomPools'. Value is dictionary. 282 - Key is string with pool ID/name. Value is list of objects representing memory blocks, each containing dictionary with: 283 - Fixed key 'ID'. Value is int. 284 - Fixed key 'Size'. Value is int. 285 - Fixed key 'Algorithm'. Optional. Value is string. 286 - Fixed key 'Suballocations'. Value is list of tuples as above. 287""" 288