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.
63 KB
Loading
196 Bytes
Loading
214 Bytes
Loading
194 Bytes
Loading
214 Bytes
Loading
215 Bytes
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)