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