VulkanMemoryAllocator/tools/VmaDumpVis/VmaDumpVis.py

263 lines
11 KiB
Python
Raw Normal View History

2018-03-09 14:48:18 +00:00
#
# Copyright (c) 2018 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 = 'VMA Dump Visualization 1.0.0'
IMG_SIZE_X = 800
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)
2018-03-09 14:48:18 +00:00
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, objBlock):
iBlockSize = int(objBlock['TotalBytes'])
arrSuballocs = objBlock['Suballocations']
dstBlockObj = {'Size':iBlockSize, 'Suballocations':[]}
dstBlockList.append(dstBlockObj)
for objSuballoc in arrSuballocs:
dstBlockObj['Suballocations'].append((objSuballoc['Type'], int(objSuballoc['Size']), int(objSuballoc['Usage']) if ('Usage' in objSuballoc) else 0))
2018-03-09 14:48:18 +00:00
def GetDataForMemoryType(iMemTypeIndex):
global data
if iMemTypeIndex in data:
return data[iMemTypeIndex]
else:
newMemTypeData = {'DedicatedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPoolBlocks':[]}
data[iMemTypeIndex] = newMemTypeData
return newMemTypeData
# 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
iImgSizeY += len(dictMemType['DedicatedAllocations']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
for tDedicatedAlloc in dictMemType['DedicatedAllocations']:
iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1])
iImgSizeY += len(dictMemType['DefaultPoolBlocks']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
for objBlock in dictMemType['DefaultPoolBlocks']:
iMaxBlockSize = max(iMaxBlockSize, objBlock['Size'])
iImgSizeY += len(dictMemType['CustomPoolBlocks']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
for objBlock in dictMemType['CustomPoolBlocks']:
iMaxBlockSize = max(iMaxBlockSize, objBlock['Size'])
fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize)
return iImgSizeY, fPixelsPerByte
def TypeToColor(sType, iUsage):
2018-03-09 14:48:18 +00:00
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
2018-03-09 14:48:18 +00:00
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
2018-03-09 14:48:18 +00:00
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
2018-03-09 14:48:18 +00:00
assert False
return 0, 0, 0, 255
def DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc):
2018-03-09 14:48:18 +00:00
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)
2018-03-09 14:48:18 +00:00
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)
2018-03-09 14:48:18 +00:00
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)
2018-03-09 14:48:18 +00:00
# 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))
2018-03-09 14:48:18 +00:00
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 objBlock in tType[1]['Blocks']:
ProcessBlock(typeData['DefaultPoolBlocks'], objBlock)
if 'Pools' in jsonSrc:
arrPools = jsonSrc['Pools']
for objPool in arrPools:
iType = int(objPool['MemoryTypeIndex'])
typeData = GetDataForMemoryType(iType)
arrBlocks = objPool['Blocks']
for objBlock in arrBlocks:
ProcessBlock(typeData['CustomPoolBlocks'], objBlock)
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
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 iX + 32 < IMG_SIZE_X - 2 * IMG_MARGIN:
draw.text((iX + IMG_MARGIN + FONT_SIZE/4, y), BytesToStr(iByte), 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
index = 0
for objBlock in dictMemType['DefaultPoolBlocks']:
draw.text((IMG_MARGIN, y), "Default pool block %d" % index, fill=COLOR_TEXT_H2, font=font)
y += FONT_SIZE + IMG_MARGIN
DrawBlock(draw, y, objBlock)
y += MAP_SIZE + IMG_MARGIN
index += 1
index = 0
for objBlock in dictMemType['CustomPoolBlocks']:
draw.text((IMG_MARGIN, y), "Custom pool block %d" % index, fill=COLOR_TEXT_H2, font=font)
y += FONT_SIZE + IMG_MARGIN
DrawBlock(draw, y, objBlock)
y += MAP_SIZE + IMG_MARGIN
index += 1
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)
2018-03-09 14:48:18 +00:00
- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with:
- Fixed key 'Size'. Value is int.
- Fixed key 'Suballocations'. Value is list of tuples as above.
- Fixed key 'CustomPoolBlocks'. Value is list of objects, each containing dictionary with:
- Fixed key 'Size'. Value is int.
- Fixed key 'Suballocations'. Value is list of tuples as above.
"""