xref: /aosp_15_r20/external/angle/third_party/vulkan_memory_allocator/tools/GpuMemDumpVis/GpuMemDumpVis.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#
2# Copyright (c) 2018-2023 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 = 'Vulkan/D3D12 Memory Allocator Dump Visualization 3.0.2'
29IMG_WIDTH = 1200
30IMG_MARGIN = 8
31TEXT_MARGIN = 4
32FONT_SIZE = 10
33MAP_SIZE = 24
34COLOR_TEXT_H1 = (0, 0, 0, 255)
35COLOR_TEXT_H2 = (150, 150, 150, 255)
36COLOR_OUTLINE = (155, 155, 155, 255)
37COLOR_OUTLINE_HARD = (0, 0, 0, 255)
38COLOR_GRID_LINE = (224, 224, 224, 255)
39
40currentApi = ""
41data = {}
42
43
44def ParseArgs():
45    argParser = argparse.ArgumentParser(description='Visualization of Vulkan/D3D12 Memory Allocator JSON dump.')
46    argParser.add_argument('DumpFile', help='Path to source JSON file with memory dump created by Vulkan/D3D12 Memory Allocator library')
47    argParser.add_argument('-v', '--version', action='version', version=PROGRAM_VERSION)
48    argParser.add_argument('-o', '--output', required=True, help='Path to destination image file (e.g. PNG)')
49    return argParser.parse_args()
50
51def GetDataForMemoryPool(poolTypeName):
52    global data
53    if poolTypeName in data:
54        return data[poolTypeName]
55    else:
56        newPoolData = {'DedicatedAllocations':[], 'Blocks':[], 'CustomPools':{}}
57        data[poolTypeName] = newPoolData
58        return newPoolData
59
60def ProcessBlock(poolData, block):
61    blockInfo = {'ID': block[0], 'Size': int(block[1]['TotalBytes']), 'Suballocations':[]}
62    for alloc in block[1]['Suballocations']:
63        allocData = {'Type': alloc['Type'], 'Size': int(alloc['Size']), 'Usage': int(alloc['Usage']) if 'Usage' in alloc else 0 }
64        blockInfo['Suballocations'].append(allocData)
65    poolData['Blocks'].append(blockInfo)
66
67def IsDataEmpty():
68    global data
69    for poolData in data.values():
70        if len(poolData['DedicatedAllocations']) > 0:
71            return False
72        if len(poolData['Blocks']) > 0:
73            return False
74        for customPool in poolData['CustomPools'].values():
75            if len(customPool['Blocks']) > 0:
76                return False
77            if len(customPool['DedicatedAllocations']) > 0:
78                return False
79    return True
80
81def RemoveEmptyType():
82    global data
83    for poolType in list(data.keys()):
84        pool = data[poolType]
85        if len(pool['DedicatedAllocations']) > 0:
86           continue
87        if len(pool['Blocks']) > 0:
88            continue
89        empty = True
90        for customPool in pool['CustomPools'].values():
91            if len(customPool['Blocks']) > 0:
92                empty = False
93                break
94            if len(customPool['DedicatedAllocations']) > 0:
95                empty = False
96                break
97        if empty:
98            del data[poolType]
99
100# Returns tuple:
101# [0] image height : integer
102# [1] pixels per byte : float
103def CalcParams():
104    global data
105    height = IMG_MARGIN
106    height += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes
107    maxBlockSize = 0
108    # Get height occupied by every memory pool
109    for poolData in data.values():
110        height += FONT_SIZE + IMG_MARGIN # Memory pool title
111        height += len(poolData['Blocks']) * (FONT_SIZE + MAP_SIZE + IMG_MARGIN * 2)
112        height += len(poolData['DedicatedAllocations']) * (FONT_SIZE + MAP_SIZE + IMG_MARGIN * 2)
113        # Get longest block size
114        for dedicatedAlloc in poolData['DedicatedAllocations']:
115            maxBlockSize = max(maxBlockSize, dedicatedAlloc['Size'])
116        for block in poolData['Blocks']:
117            maxBlockSize = max(maxBlockSize, block['Size'])
118        # Same for custom pools
119        for customPoolData in poolData['CustomPools'].values():
120            height += len(customPoolData['Blocks']) * (FONT_SIZE + MAP_SIZE + IMG_MARGIN * 2)
121            height += len(customPoolData['DedicatedAllocations']) * (FONT_SIZE + MAP_SIZE + IMG_MARGIN * 2)
122            # Get longest block size
123            for dedicatedAlloc in customPoolData['DedicatedAllocations']:
124                maxBlockSize = max(maxBlockSize, dedicatedAlloc['Size'])
125            for block in customPoolData['Blocks']:
126                maxBlockSize = max(maxBlockSize, block['Size'])
127
128    return height, (IMG_WIDTH - IMG_MARGIN * 2) / float(maxBlockSize)
129
130def BytesToStr(bytes):
131    if bytes < 1024:
132        return "%d B" % bytes
133    bytes /= 1024
134    if bytes < 1024:
135        return "%d KiB" % bytes
136    bytes /= 1024
137    if bytes < 1024:
138        return "%d MiB" % bytes
139    bytes /= 1024
140    return "%d GiB" % bytes
141
142def TypeToColor(type, usage):
143    global currentApi
144    if type == 'FREE':
145        return 220, 220, 220, 255
146    elif type == 'UNKNOWN':
147        return 175, 175, 175, 255 # Gray
148
149    if currentApi == 'Vulkan':
150        if type == 'BUFFER':
151            if (usage & 0x1C0) != 0: # INDIRECT_BUFFER | VERTEX_BUFFER | INDEX_BUFFER
152                return 255, 148, 148, 255 # Red
153            elif (usage & 0x28) != 0: # STORAGE_BUFFER | STORAGE_TEXEL_BUFFER
154                return 255, 187, 121, 255 # Orange
155            elif (usage & 0x14) != 0: # UNIFORM_BUFFER | UNIFORM_TEXEL_BUFFER
156                return 255, 255, 0, 255 # Yellow
157            else:
158                return 255, 255, 165, 255 # Light yellow
159        elif type == 'IMAGE_OPTIMAL':
160            if (usage & 0x20) != 0: # DEPTH_STENCIL_ATTACHMENT
161                return 246, 128, 255, 255 # Pink
162            elif (usage & 0xD0) != 0: # INPUT_ATTACHMENT | TRANSIENT_ATTACHMENT | COLOR_ATTACHMENT
163                return 179, 179, 255, 255 # Blue
164            elif (usage & 0x4) != 0: # SAMPLED
165                return 0, 255, 255, 255 # Aqua
166            else:
167                return 183, 255, 255, 255 # Light aqua
168        elif type == 'IMAGE_LINEAR' :
169            return 0, 255, 0, 255 # Green
170        elif type == 'IMAGE_UNKNOWN':
171            return 0, 255, 164, 255 # Green/aqua
172    elif currentApi == 'Direct3D 12':
173        if type == 'BUFFER':
174                return 255, 255, 165, 255 # Light yellow
175        elif type == 'TEXTURE1D' or type == 'TEXTURE2D' or type == 'TEXTURE3D':
176            if (usage & 0x2) != 0: # D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
177                return 246, 128, 255, 255 # Pink
178            elif (usage & 0x5) != 0: # D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
179                return 179, 179, 255, 255 # Blue
180            elif (usage & 0x8) == 0: # Not having D3D12_RESOURCE_FLAG_DENY_SHARED_RESOURCE
181                return 0, 255, 255, 255 # Aqua
182            else:
183                return 183, 255, 255, 255 # Light aqua
184    else:
185        print("Unknown graphics API!")
186        exit(1)
187    assert False
188    return 0, 0, 0, 255
189
190def DrawBlock(draw, y, block, pixelsPerByte):
191    sizePixels = int(block['Size'] * pixelsPerByte)
192    draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + sizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0), outline=None)
193    byte = 0
194    x = 0
195    lastHardLineX = -1
196    for alloc in block['Suballocations']:
197        byteEnd = byte + alloc['Size']
198        xEnd = int(byteEnd * pixelsPerByte)
199        if alloc['Type'] != 'FREE':
200            if xEnd > x + 1:
201                draw.rectangle([IMG_MARGIN + x, y, IMG_MARGIN + xEnd, y + MAP_SIZE], fill=TypeToColor(alloc['Type'], alloc['Usage']), outline=COLOR_OUTLINE)
202                # Hard line was been overwritten by rectangle outline: redraw it.
203                if lastHardLineX == x:
204                    draw.line([IMG_MARGIN + x, y, IMG_MARGIN + x, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)
205            else:
206                draw.line([IMG_MARGIN + x, y, IMG_MARGIN + x, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD)
207                lastHardLineX = x
208        byte = byteEnd
209        x = xEnd
210
211def DrawDedicatedAllocationBlock(draw, y, dedicatedAlloc, pixelsPerByte):
212    sizePixels = int(dedicatedAlloc['Size'] * pixelsPerByte)
213    draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + sizePixels, y + MAP_SIZE], fill=TypeToColor(dedicatedAlloc['Type'], dedicatedAlloc['Usage']), outline=COLOR_OUTLINE)
214
215
216if __name__ == '__main__':
217    args = ParseArgs()
218    jsonSrc = json.load(open(args.DumpFile, 'rb'))
219
220    if 'General' in jsonSrc:
221        currentApi = jsonSrc['General']['API']
222    else:
223        print("Wrong JSON format, cannot determine graphics API!")
224        exit(1)
225
226    # Process default pools
227    if 'DefaultPools' in jsonSrc:
228        for memoryPool in jsonSrc['DefaultPools'].items():
229            poolData = GetDataForMemoryPool(memoryPool[0])
230            # Get dedicated allocations
231            for dedicatedAlloc in memoryPool[1]['DedicatedAllocations']:
232                allocData = {'Type': dedicatedAlloc['Type'], 'Size': int(dedicatedAlloc['Size']), 'Usage': int(dedicatedAlloc['Usage'])}
233                poolData['DedicatedAllocations'].append(allocData)
234            # Get allocations in block vectors
235            for block in memoryPool[1]['Blocks'].items():
236                ProcessBlock(poolData, block)
237    # Process custom pools
238    if 'CustomPools' in jsonSrc:
239        for memoryPool in jsonSrc['CustomPools'].items():
240            poolData = GetDataForMemoryPool(memoryPool[0])
241            for pool in memoryPool[1]:
242                poolName = pool['Name']
243                poolData['CustomPools'][poolName] = {'DedicatedAllocations':[], 'Blocks':[]}
244                # Get dedicated allocations
245                for dedicatedAlloc in pool['DedicatedAllocations']:
246                    allocData = {'Type': dedicatedAlloc['Type'], 'Size': int(dedicatedAlloc['Size']), 'Usage': int(dedicatedAlloc['Usage'])}
247                    poolData['CustomPools'][poolName]['DedicatedAllocations'].append(allocData)
248                # Get allocations in block vectors
249                for block in pool['Blocks'].items():
250                    ProcessBlock(poolData['CustomPools'][poolName], block)
251
252    if IsDataEmpty():
253        print("There is nothing to put on the image. Please make sure you generated the stats string with detailed map enabled.")
254        exit(1)
255    RemoveEmptyType()
256    # Calculate dimmensions and create data image
257    imgHeight, pixelsPerByte = CalcParams()
258    img = Image.new('RGB', (IMG_WIDTH, imgHeight), 'white')
259    draw = ImageDraw.Draw(img)
260    try:
261        font = ImageFont.truetype('segoeuib.ttf')
262    except:
263        font = ImageFont.load_default()
264
265    # Draw grid lines
266    bytesBetweenGridLines = 32
267    while bytesBetweenGridLines * pixelsPerByte < 64:
268        bytesBetweenGridLines *= 2
269    byte = 0
270    y = IMG_MARGIN
271    while True:
272        x = int(byte * pixelsPerByte)
273        if x > IMG_WIDTH - 2 * IMG_MARGIN:
274            break
275        draw.line([x + IMG_MARGIN, 0, x + IMG_MARGIN, imgHeight], fill=COLOR_GRID_LINE)
276        if byte == 0:
277            draw.text((x + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font)
278        else:
279            text = BytesToStr(byte)
280            textSize = draw.textsize(text, font=font)
281            draw.text((x + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font)
282        byte += bytesBetweenGridLines
283    y += FONT_SIZE + IMG_MARGIN
284
285    # Draw main content
286    for memType in sorted(data.keys()):
287        memPoolData = data[memType]
288        draw.text((IMG_MARGIN, y), "Memory pool %s" % memType, fill=COLOR_TEXT_H1, font=font)
289        y += FONT_SIZE + IMG_MARGIN
290        # Draw block vectors
291        for block in memPoolData['Blocks']:
292            draw.text((IMG_MARGIN, y), "Default pool block %s" % block['ID'], fill=COLOR_TEXT_H2, font=font)
293            y += FONT_SIZE + IMG_MARGIN
294            DrawBlock(draw, y, block, pixelsPerByte)
295            y += MAP_SIZE + IMG_MARGIN
296        index = 0
297        # Draw dedicated allocations
298        for dedicatedAlloc in memPoolData['DedicatedAllocations']:
299            draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font)
300            y += FONT_SIZE + IMG_MARGIN
301            DrawDedicatedAllocationBlock(draw, y, dedicatedAlloc, pixelsPerByte)
302            y += MAP_SIZE + IMG_MARGIN
303            index += 1
304        for poolName, pool in memPoolData['CustomPools'].items():
305            for block in pool['Blocks']:
306                draw.text((IMG_MARGIN, y), "Custom pool %s block %s" % (poolName, block['ID']), fill=COLOR_TEXT_H2, font=font)
307                y += FONT_SIZE + IMG_MARGIN
308                DrawBlock(draw, y, block, pixelsPerByte)
309                y += MAP_SIZE + IMG_MARGIN
310            index = 0
311            for dedicatedAlloc in pool['DedicatedAllocations']:
312                draw.text((IMG_MARGIN, y), "Custom pool %s dedicated allocation %d" % (poolName, index), fill=COLOR_TEXT_H2, font=font)
313                y += FONT_SIZE + IMG_MARGIN
314                DrawDedicatedAllocationBlock(draw, y, dedicatedAlloc, pixelsPerByte)
315                y += MAP_SIZE + IMG_MARGIN
316                index += 1
317    del draw
318    img.save(args.output)
319
320"""
321Main data structure - variable `data` - is a dictionary. Key is string - memory type name. Value is dictionary of:
322- Fixed key 'DedicatedAllocations'. Value is list of objects, each containing dictionary with:
323    - Fixed key 'Type'. Value is string.
324    - Fixed key 'Size'. Value is int.
325    - Fixed key 'Usage'. Value is int.
326- Fixed key 'Blocks'. Value is list of objects, each containing dictionary with:
327    - Fixed key 'ID'. Value is int.
328    - Fixed key 'Size'. Value is int.
329    - Fixed key 'Suballocations'. Value is list of objects as above.
330- Fixed key 'CustomPools'. Value is dictionary.
331  - Key is string with pool ID/name. Value is a dictionary with:
332    - Fixed key 'DedicatedAllocations'. Value is list of objects as above.
333    - Fixed key 'Blocks'. Value is a list of objects representing memory blocks as above.
334"""
335