Skip to content

Commit dad992f

Browse files
Added VmaDumpVis tool.
1 parent ff1cf54 commit dad992f

File tree

8 files changed

+277
-0
lines changed

8 files changed

+277
-0
lines changed

tools/VmaDumpVis/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# VMA Dump Vis
2+
3+
Vulkan Memory Allocator Dump Visualization. It is an auxiliary tool that can visualize internal state of [Vulkan Memory Allocator](../README.md) library on a picture. It is a Python script that must be launched from command line with appropriate parameters.
4+
5+
## Requirements
6+
7+
- Python 3 installed
8+
- [Pillow](http://python-pillow.org/) - Python Imaging Library (Fork) installed
9+
10+
## Usage
11+
12+
```
13+
python VmaDumpVis.py -o OUTPUT_FILE INPUT_FILE
14+
```
15+
16+
* `INPUT_FILE` - path to source file to be read, containing dump of internal state of the VMA library in JSON format (encoding: UTF-8), generated using `vmaBuildStatsString()` function.
17+
* `OUTPUT_FILE` - path to destination file to be written that will contain generated image. Image format is automatically recognized based on file extension. List of supported formats can be found [here](http://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html) and includes: BMP, GIF, JPEG, PNG, TGA.
18+
19+
You can also use typical options:
20+
21+
* `-h` - to see help on command line syntax
22+
* `-v` - to see program version number
23+
24+
## Example output
25+
26+
![Example output](README_files/ExampleOutput.png "Example output")
27+
28+
## Legend
29+
30+
* ![Free space](README_files/Legend_Bkg.png "Free space") Light gray without border - a space in Vulkan device memory block unused by any allocation.
31+
* ![Buffer](README_files/Legend_Buffer.png "Buffer") Yellow rectangle - buffer.
32+
* ![Image Optimal](README_files/Legend_ImageOptimal.png "Image Optimal") Aqua rectangle - image with TILING_OPTIMAL.
33+
* ![Image Linear](README_files/Legend_ImageLinear.png "Image Linear") Green rectangle - image with TILING_LINEAR.
34+
* ![Details](README_files/Legend_Details.png "Details") Black bar or rectangle - one or more allocations of any kind too small to be visualized as filled rectangles.
Loading
196 Bytes
Loading
214 Bytes
Loading
194 Bytes
Loading
Loading
Loading

tools/VmaDumpVis/VmaDumpVis.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
#
2+
# Copyright (c) 2018 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+
23+
import argparse
24+
import json
25+
from PIL import Image, ImageDraw, ImageFont
26+
27+
28+
PROGRAM_VERSION = 'VMA Dump Visualization 1.0.0'
29+
IMG_SIZE_X = 800
30+
IMG_MARGIN = 8
31+
FONT_SIZE = 10
32+
MAP_SIZE = 24
33+
COLOR_TEXT_H1 = (0, 0, 0, 255)
34+
COLOR_TEXT_H2 = (150, 150, 150, 255)
35+
COLOR_OUTLINE = (160, 160, 160, 255)
36+
COLOR_OUTLINE_HARD = (0, 0, 0, 255)
37+
COLOR_GRID_LINE = (224, 224, 224, 255)
38+
39+
40+
argParser = argparse.ArgumentParser(description='Visualization of Vulkan Memory Allocator JSON dump.')
41+
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')
42+
argParser.add_argument('-v', '--version', action='version', version=PROGRAM_VERSION)
43+
argParser.add_argument('-o', '--output', required=True, help='Path to destination image file (e.g. PNG)')
44+
args = argParser.parse_args()
45+
46+
data = {}
47+
48+
49+
def ProcessBlock(dstBlockList, objBlock):
50+
iBlockSize = int(objBlock['TotalBytes'])
51+
arrSuballocs = objBlock['Suballocations']
52+
dstBlockObj = {'Size':iBlockSize, 'Suballocations':[]}
53+
dstBlockList.append(dstBlockObj)
54+
for objSuballoc in arrSuballocs:
55+
dstBlockObj['Suballocations'].append((objSuballoc['Type'], int(objSuballoc['Size'])))
56+
57+
58+
def GetDataForMemoryType(iMemTypeIndex):
59+
global data
60+
if iMemTypeIndex in data:
61+
return data[iMemTypeIndex]
62+
else:
63+
newMemTypeData = {'DedicatedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPoolBlocks':[]}
64+
data[iMemTypeIndex] = newMemTypeData
65+
return newMemTypeData
66+
67+
68+
# Returns tuple:
69+
# [0] image height : integer
70+
# [1] pixels per byte : float
71+
def CalcParams():
72+
global data
73+
iImgSizeY = IMG_MARGIN
74+
iImgSizeY += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes
75+
iMaxBlockSize = 0
76+
for dictMemType in data.values():
77+
iImgSizeY += IMG_MARGIN + FONT_SIZE
78+
iImgSizeY += len(dictMemType['DedicatedAllocations']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
79+
for tDedicatedAlloc in dictMemType['DedicatedAllocations']:
80+
iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1])
81+
iImgSizeY += len(dictMemType['DefaultPoolBlocks']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
82+
for objBlock in dictMemType['DefaultPoolBlocks']:
83+
iMaxBlockSize = max(iMaxBlockSize, objBlock['Size'])
84+
iImgSizeY += len(dictMemType['CustomPoolBlocks']) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE)
85+
for objBlock in dictMemType['CustomPoolBlocks']:
86+
iMaxBlockSize = max(iMaxBlockSize, objBlock['Size'])
87+
fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize)
88+
return iImgSizeY, fPixelsPerByte
89+
90+
91+
def TypeToColor(sType):
92+
if sType == 'FREE':
93+
return 220, 220, 220, 255
94+
elif sType == 'BUFFER':
95+
return 255, 255, 0, 255
96+
elif sType == 'IMAGE_OPTIMAL':
97+
return 128, 255, 255, 255
98+
elif sType == 'IMAGE_LINEAR':
99+
return 64, 255, 64, 255
100+
assert False
101+
return 0, 0, 0, 255
102+
103+
104+
def DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc):
105+
global fPixelsPerByte
106+
iSizeBytes = tDedicatedAlloc[1]
107+
iSizePixels = int(iSizeBytes * fPixelsPerByte)
108+
draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tDedicatedAlloc[0]), outline=COLOR_OUTLINE)
109+
110+
111+
def DrawBlock(draw, y, objBlock):
112+
global fPixelsPerByte
113+
iSizeBytes = objBlock['Size']
114+
iSizePixels = int(iSizeBytes * fPixelsPerByte)
115+
draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor('FREE'), outline=None)
116+
iByte = 0
117+
iX = 0
118+
iLastHardLineX = -1
119+
for tSuballoc in objBlock['Suballocations']:
120+
sType = tSuballoc[0]
121+
iByteEnd = iByte + tSuballoc[1]
122+
iXEnd = int(iByteEnd * fPixelsPerByte)
123+
if sType != 'FREE':
124+
if iXEnd > iX + 1:
125+
draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType), outline=COLOR_OUTLINE)
126+
# Hard line was been overwritten by rectangle outline: redraw it.
127+
if iLastHardLineX == iX:
128+
draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)
129+
else:
130+
draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)
131+
iLastHardLineX = iX
132+
iByte = iByteEnd
133+
iX = iXEnd
134+
135+
136+
def BytesToStr(iBytes):
137+
if iBytes < 1024:
138+
return "%d B" % iBytes
139+
iBytes /= 1024
140+
if iBytes < 1024:
141+
return "%d KiB" % iBytes
142+
iBytes /= 1024
143+
if iBytes < 1024:
144+
return "%d MiB" % iBytes
145+
iBytes /= 1024
146+
return "%d GiB" % iBytes
147+
148+
149+
jsonSrc = json.load(args.DumpFile)
150+
if 'DedicatedAllocations' in jsonSrc:
151+
for tType in jsonSrc['DedicatedAllocations'].items():
152+
sType = tType[0]
153+
assert sType[:5] == 'Type '
154+
iType = int(sType[5:])
155+
typeData = GetDataForMemoryType(iType)
156+
for objAlloc in tType[1]:
157+
typeData['DedicatedAllocations'].append((objAlloc['Type'], int(objAlloc['Size'])))
158+
if 'DefaultPools' in jsonSrc:
159+
for tType in jsonSrc['DefaultPools'].items():
160+
sType = tType[0]
161+
assert sType[:5] == 'Type '
162+
iType = int(sType[5:])
163+
typeData = GetDataForMemoryType(iType)
164+
for objBlock in tType[1]['Blocks']:
165+
ProcessBlock(typeData['DefaultPoolBlocks'], objBlock)
166+
if 'Pools' in jsonSrc:
167+
arrPools = jsonSrc['Pools']
168+
for objPool in arrPools:
169+
iType = int(objPool['MemoryTypeIndex'])
170+
typeData = GetDataForMemoryType(iType)
171+
arrBlocks = objPool['Blocks']
172+
for objBlock in arrBlocks:
173+
ProcessBlock(typeData['CustomPoolBlocks'], objBlock)
174+
175+
176+
iImgSizeY, fPixelsPerByte = CalcParams()
177+
178+
img = Image.new('RGB', (IMG_SIZE_X, iImgSizeY), 'white')
179+
draw = ImageDraw.Draw(img)
180+
181+
try:
182+
font = ImageFont.truetype('segoeuib.ttf')
183+
except:
184+
font = ImageFont.load_default()
185+
186+
y = IMG_MARGIN
187+
188+
# Draw grid lines
189+
iBytesBetweenGridLines = 32
190+
while iBytesBetweenGridLines * fPixelsPerByte < 64:
191+
iBytesBetweenGridLines *= 2
192+
iByte = 0
193+
while True:
194+
iX = int(iByte * fPixelsPerByte)
195+
if iX > IMG_SIZE_X - 2 * IMG_MARGIN:
196+
break
197+
draw.line([iX + IMG_MARGIN, 0, iX + IMG_MARGIN, iImgSizeY], fill=COLOR_GRID_LINE)
198+
if iX + 32 < IMG_SIZE_X - 2 * IMG_MARGIN:
199+
draw.text((iX + IMG_MARGIN + FONT_SIZE/4, y), BytesToStr(iByte), fill=COLOR_TEXT_H2, font=font)
200+
iByte += iBytesBetweenGridLines
201+
y += FONT_SIZE + IMG_MARGIN
202+
203+
# Draw main content
204+
for iMemTypeIndex in sorted(data.keys()):
205+
dictMemType = data[iMemTypeIndex]
206+
draw.text((IMG_MARGIN, y), "Memory type %d" % iMemTypeIndex, fill=COLOR_TEXT_H1, font=font)
207+
y += FONT_SIZE + IMG_MARGIN
208+
index = 0
209+
for tDedicatedAlloc in dictMemType['DedicatedAllocations']:
210+
draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font)
211+
y += FONT_SIZE + IMG_MARGIN
212+
DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc)
213+
y += MAP_SIZE + IMG_MARGIN
214+
index += 1
215+
index = 0
216+
for objBlock in dictMemType['DefaultPoolBlocks']:
217+
draw.text((IMG_MARGIN, y), "Default pool block %d" % index, fill=COLOR_TEXT_H2, font=font)
218+
y += FONT_SIZE + IMG_MARGIN
219+
DrawBlock(draw, y, objBlock)
220+
y += MAP_SIZE + IMG_MARGIN
221+
index += 1
222+
index = 0
223+
for objBlock in dictMemType['CustomPoolBlocks']:
224+
draw.text((IMG_MARGIN, y), "Custom pool block %d" % index, fill=COLOR_TEXT_H2, font=font)
225+
y += FONT_SIZE + IMG_MARGIN
226+
DrawBlock(draw, y, objBlock)
227+
y += MAP_SIZE + IMG_MARGIN
228+
index += 1
229+
del draw
230+
img.save(args.output)
231+
232+
"""
233+
Main data structure - variable `data` - is a dictionary. Key is integer - memory type index. Value is dictionary of:
234+
- Fixed key 'DedicatedAllocations'. Value is list of tuples, each containing:
235+
- [0]: Type : string
236+
- [1]: Size : integer
237+
- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with:
238+
- Fixed key 'Size'. Value is int.
239+
- Fixed key 'Suballocations'. Value is list of tuples as above.
240+
- Fixed key 'CustomPoolBlocks'. Value is list of objects, each containing dictionary with:
241+
- Fixed key 'Size'. Value is int.
242+
- Fixed key 'Suballocations'. Value is list of tuples as above.
243+
"""

0 commit comments

Comments
 (0)