1# 2# Copyright (c) 2019 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 = 'D3D12MA Dump Visualization 1.0.0' 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 D3D12 Memory Allocator JSON dump.') 41argParser.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') 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.get('Flags', 0)), int(objSuballoc.get('Layout', 0)))) 56 dstBlockList.append(dstBlockObj) 57 58 59def GetDataForHeapType(sHeapType): 60 global data 61 if sHeapType in data: 62 return data[sHeapType] 63 else: 64 newHeapTypeData = {'CommittedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPools':{}} 65 data[sHeapType] = newHeapTypeData 66 return newHeapTypeData 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['CommittedAllocations'] 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 """ 88 dCustomPools = dictMemType['CustomPools'] 89 for lBlocks in dCustomPools.values(): 90 iImgSizeY += len(lBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) 91 for objBlock in lBlocks: 92 iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) 93 """ 94 fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize) 95 return iImgSizeY, fPixelsPerByte 96 97 98def TypeToColor(sType, iFlags, iLayout): 99 if sType == 'FREE': 100 return 220, 220, 220, 255 101 elif sType == 'BUFFER': 102 return 255, 255, 0, 255 # Yellow 103 elif sType == 'TEXTURE2D' or sType == 'TEXTURE1D' or sType == 'TEXTURE3D': 104 if iLayout != 0: # D3D12_TEXTURE_LAYOUT_UNKNOWN 105 return 0, 255, 0, 255 # Green 106 else: 107 if (iFlags & 0x2) != 0: # D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL 108 return 246, 128, 255, 255 # Pink 109 elif (iFlags & 0x5) != 0: # D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS 110 return 179, 179, 255, 255 # Blue 111 elif (iFlags & 0x8) == 0: # Not having D3D12_RESOURCE_FLAG_DENY_SHARED_RESOURCE 112 return 0, 255, 255, 255 # Aqua 113 else: 114 return 183, 255, 255, 255 # Light aqua 115 else: 116 return 175, 175, 175, 255 # Gray 117 assert False 118 return 0, 0, 0, 255 119 120 121def DrawCommittedAllocationBlock(draw, y, tAlloc): 122 global fPixelsPerByte 123 iSizeBytes = tAlloc[1] 124 iSizePixels = int(iSizeBytes * fPixelsPerByte) 125 draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tAlloc[0], tAlloc[2], tAlloc[3]), outline=COLOR_OUTLINE) 126 127 128def DrawBlock(draw, y, objBlock): 129 global fPixelsPerByte 130 iSizeBytes = objBlock['Size'] 131 iSizePixels = int(iSizeBytes * fPixelsPerByte) 132 draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0, 0), outline=None) 133 iByte = 0 134 iX = 0 135 iLastHardLineX = -1 136 for tSuballoc in objBlock['Suballocations']: 137 sType = tSuballoc[0] 138 iByteEnd = iByte + tSuballoc[1] 139 iXEnd = int(iByteEnd * fPixelsPerByte) 140 if sType != 'FREE': 141 if iXEnd > iX + 1: 142 iFlags = tSuballoc[2] 143 iLayout = tSuballoc[3] 144 draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType, iFlags, iLayout), outline=COLOR_OUTLINE) 145 # Hard line was been overwritten by rectangle outline: redraw it. 146 if iLastHardLineX == iX: 147 draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) 148 else: 149 draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) 150 iLastHardLineX = iX 151 iByte = iByteEnd 152 iX = iXEnd 153 154 155def BytesToStr(iBytes): 156 if iBytes < 1024: 157 return "%d B" % iBytes 158 iBytes /= 1024 159 if iBytes < 1024: 160 return "%d KB" % iBytes 161 iBytes /= 1024 162 if iBytes < 1024: 163 return "%d MB" % iBytes 164 iBytes /= 1024 165 return "%d GB" % iBytes 166 167 168jsonSrc = json.load(args.DumpFile) 169objDetailedMap = jsonSrc['DetailedMap'] 170if 'CommittedAllocations' in objDetailedMap: 171 for tType in objDetailedMap['CommittedAllocations'].items(): 172 sHeapType = tType[0] 173 typeData = GetDataForHeapType(sHeapType) 174 for objAlloc in tType[1]: 175 typeData['CommittedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc.get('Flags', 0)), int(objAlloc.get('Layout', 0)))) 176if 'DefaultPools' in objDetailedMap: 177 for tType in objDetailedMap['DefaultPools'].items(): 178 sHeapType = tType[0] 179 typeData = GetDataForHeapType(sHeapType) 180 for sBlockId, objBlock in tType[1]['Blocks'].items(): 181 ProcessBlock(typeData['DefaultPoolBlocks'], int(sBlockId), objBlock, '') 182""" 183if 'Pools' in jsonSrc: 184 objPools = jsonSrc['Pools'] 185 for sPoolId, objPool in objPools.items(): 186 iType = int(objPool['MemoryTypeIndex']) 187 typeData = GetDataForHeapType(iType) 188 objBlocks = objPool['Blocks'] 189 sAlgorithm = objPool.get('Algorithm', '') 190 sName = objPool.get('Name', None) 191 if sName: 192 sFullName = sPoolId + ' "' + sName + '"' 193 else: 194 sFullName = sPoolId 195 dstBlockArray = [] 196 typeData['CustomPools'][sFullName] = dstBlockArray 197 for sBlockId, objBlock in objBlocks.items(): 198 ProcessBlock(dstBlockArray, int(sBlockId), objBlock, sAlgorithm) 199""" 200 201iImgSizeY, fPixelsPerByte = CalcParams() 202 203img = Image.new('RGB', (IMG_SIZE_X, iImgSizeY), 'white') 204draw = ImageDraw.Draw(img) 205 206try: 207 font = ImageFont.truetype('segoeuib.ttf') 208except: 209 font = ImageFont.load_default() 210 211y = IMG_MARGIN 212 213# Draw grid lines 214iBytesBetweenGridLines = 32 215while iBytesBetweenGridLines * fPixelsPerByte < 64: 216 iBytesBetweenGridLines *= 2 217iByte = 0 218TEXT_MARGIN = 4 219while True: 220 iX = int(iByte * fPixelsPerByte) 221 if iX > IMG_SIZE_X - 2 * IMG_MARGIN: 222 break 223 draw.line([iX + IMG_MARGIN, 0, iX + IMG_MARGIN, iImgSizeY], fill=COLOR_GRID_LINE) 224 if iByte == 0: 225 draw.text((iX + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font) 226 else: 227 text = BytesToStr(iByte) 228 textSize = draw.textsize(text, font=font) 229 draw.text((iX + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font) 230 iByte += iBytesBetweenGridLines 231y += FONT_SIZE + IMG_MARGIN 232 233# Draw main content 234for sHeapType in data.keys(): 235 dictMemType = data[sHeapType] 236 draw.text((IMG_MARGIN, y), sHeapType, fill=COLOR_TEXT_H1, font=font) 237 y += FONT_SIZE + IMG_MARGIN 238 index = 0 239 for tCommittedAlloc in dictMemType['CommittedAllocations']: 240 draw.text((IMG_MARGIN, y), "Committed allocation %d" % index, fill=COLOR_TEXT_H2, font=font) 241 y += FONT_SIZE + IMG_MARGIN 242 DrawCommittedAllocationBlock(draw, y, tCommittedAlloc) 243 y += MAP_SIZE + IMG_MARGIN 244 index += 1 245 for objBlock in dictMemType['DefaultPoolBlocks']: 246 draw.text((IMG_MARGIN, y), "Default pool block %d" % objBlock['ID'], fill=COLOR_TEXT_H2, font=font) 247 y += FONT_SIZE + IMG_MARGIN 248 DrawBlock(draw, y, objBlock) 249 y += MAP_SIZE + IMG_MARGIN 250 """ 251 index = 0 252 for sPoolName, listPool in dictMemType['CustomPools'].items(): 253 for objBlock in listPool: 254 if 'Algorithm' in objBlock and objBlock['Algorithm']: 255 sAlgorithm = ' (Algorithm: %s)' % (objBlock['Algorithm']) 256 else: 257 sAlgorithm = '' 258 draw.text((IMG_MARGIN, y), "Custom pool %s%s block %d" % (sPoolName, sAlgorithm, objBlock['ID']), fill=COLOR_TEXT_H2, font=font) 259 y += FONT_SIZE + IMG_MARGIN 260 DrawBlock(draw, y, objBlock) 261 y += MAP_SIZE + IMG_MARGIN 262 index += 1 263 """ 264del draw 265img.save(args.output) 266 267""" 268Main data structure - variable `data` - is a dictionary. Key is string - heap type ('DEFAULT', 'UPLOAD', or 'READBACK'). Value is dictionary of: 269- Fixed key 'CommittedAllocations'. Value is list of tuples, each containing: 270 - [0]: Type : string 271 - [1]: Size : integer 272 - [2]: Flags : integer (0 if unknown) 273 - [3]: Layout : integer (0 if unknown) 274- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with: 275 - Fixed key 'ID'. Value is int. 276 - Fixed key 'Size'. Value is int. 277 - Fixed key 'Suballocations'. Value is list of tuples as above. 278X- Fixed key 'CustomPools'. Value is dictionary. 279 - Key is string with pool ID/name. Value is list of objects representing memory blocks, each containing dictionary with: 280 - Fixed key 'ID'. Value is int. 281 - Fixed key 'Size'. Value is int. 282 - Fixed key 'Algorithm'. Optional. Value is string. 283 - Fixed key 'Suballocations'. Value is list of tuples as above. 284""" 285