xref: /aosp_15_r20/external/mesa3d/src/gfxstream/codegen/scripts/cereal/api_log_decoder.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1# Copyright 2023 Google LLC
2# SPDX-License-Identifier: MIT
3
4import os
5from typing import List, Set, Dict, Optional
6
7from . import VulkanType, VulkanCompoundType
8from .wrapperdefs import VulkanWrapperGenerator
9
10
11class ApiLogDecoder(VulkanWrapperGenerator):
12    """
13    This class generates decoding logic for the graphics API logs captured by
14    [GfxApiLogger](http://source/play-internal/battlestar/aosp/device/generic/vulkan-cereal/base/GfxApiLogger.h)
15
16    This allows developers to see a pretty-printed version of the API log data when using
17    print_gfx_logs.py
18    """
19
20    # List of Vulkan APIs that we will generate decoding logic for
21    generated_apis = [
22        "vkAcquireImageANDROID",
23        "vkAllocateMemory",
24        "vkBeginCommandBufferAsyncGOOGLE",
25        "vkBindBufferMemory",
26        "vkBindImageMemory",
27        "vkCmdBeginRenderPass",
28        "vkCmdBindDescriptorSets",
29        "vkCmdBindIndexBuffer",
30        "vkCmdBindPipeline",
31        "vkCmdBindVertexBuffers",
32        "vkCmdClearAttachments",
33        "vkCmdClearColorImage",
34        "vkCmdCopyBufferToImage",
35        "vkCmdCopyImageToBuffer",
36        "vkCmdDraw",
37        "vkCmdDrawIndexed",
38        "vkCmdEndRenderPass",
39        "vkCmdPipelineBarrier",
40        "vkCmdPipelineBarrier2",
41        "vkCmdSetScissor",
42        "vkCmdSetViewport",
43        "vkCollectDescriptorPoolIdsGOOGLE",
44        "vkCreateBufferWithRequirementsGOOGLE",
45        "vkCreateDescriptorPool",
46        "vkCreateDescriptorSetLayout",
47        "vkCreateFence",
48        "vkCreateFramebuffer",
49        "vkCreateGraphicsPipelines",
50        "vkCreateImageView",
51        "vkCreateImageWithRequirementsGOOGLE",
52        "vkCreatePipelineCache",
53        "vkCreateRenderPass",
54        "vkCreateSampler",
55        "vkCreateSemaphore",
56        "vkCreateShaderModule",
57        "vkDestroyBuffer",
58        "vkDestroyCommandPool",
59        "vkDestroyDescriptorPool",
60        "vkDestroyDescriptorSetLayout",
61        "vkDestroyDevice",
62        "vkDestroyFence",
63        "vkDestroyFramebuffer",
64        "vkDestroyImage",
65        "vkDestroyImageView",
66        "vkDestroyInstance",
67        "vkDestroyPipeline",
68        "vkDestroyPipelineCache",
69        "vkDestroyPipelineLayout",
70        "vkDestroyRenderPass",
71        "vkDestroySemaphore",
72        "vkDestroyShaderModule",
73        "vkEndCommandBufferAsyncGOOGLE",
74        "vkFreeCommandBuffers",
75        "vkFreeMemory",
76        "vkFreeMemorySyncGOOGLE",
77        "vkGetFenceStatus",
78        "vkGetMemoryHostAddressInfoGOOGLE",
79        "vkGetBlobGOOGLE",
80        "vkGetPhysicalDeviceFormatProperties",
81        "vkGetPhysicalDeviceProperties2KHR",
82        "vkGetPipelineCacheData",
83        "vkGetSemaphoreGOOGLE",
84        "vkGetSwapchainGrallocUsageANDROID",
85        "vkQueueCommitDescriptorSetUpdatesGOOGLE",
86        "vkQueueFlushCommandsGOOGLE",
87        "vkQueueSignalReleaseImageANDROIDAsyncGOOGLE",
88        "vkQueueSubmitAsyncGOOGLE",
89        "vkQueueWaitIdle",
90        "vkResetFences",
91        "vkWaitForFences",
92    ]
93
94    def __init__(self, module, typeInfo):
95        VulkanWrapperGenerator.__init__(self, module, typeInfo)
96        self.typeInfo = typeInfo
97
98        # Set of Vulkan structs that we need to write decoding logic for
99        self.structs: Set[str] = set()
100
101        # Maps enum group names to the list of enums in the group, for all enum groups in the spec
102        # E.g.:  "VkResult": ["VK_SUCCESS", "VK_NOT_READY", "VK_TIMEOUT", etc...]
103        self.all_enums: Dict[str, List[str]] = {}
104
105        # Set of Vulkan enums that we need to write decoding logic for
106        self.needed_enums: Set[str] = {"VkStructureType"}
107
108    def onBegin(self):
109        self.module.append("""
110#####################################################################################################
111# Pretty-printer functions for Vulkan data structures
112# THIS FILE IS AUTO-GENERATED - DO NOT EDIT
113#
114# To re-generate this file, run generate-vulkan-sources.sh
115#####################################################################################################
116
117""".lstrip())
118
119    def onGenGroup(self, groupinfo, groupName, alias=None):
120        """Called for each enum group in the spec"""
121        for enum in groupinfo.elem.findall("enum"):
122            self.all_enums[groupName] = self.all_enums.get(groupName, []) + [enum.get('name')]
123
124    def onEnd(self):
125        for api_name in sorted(self.generated_apis):
126            self.process_api(api_name)
127        self.process_structs()
128        self.process_enums()
129
130    def process_api(self, api_name):
131        """Main entry point to generate decoding logic for each Vulkan API"""
132        api = self.typeInfo.apis[api_name]
133        self.module.append('def OP_{}(printer, indent: int):\n'.format(api_name))
134
135        # Decode the sequence number. All commands have sequence numbers, except those handled
136        # by VkSubdecoder.cpp. The logic here is a bit of a hack since it's based on the command
137        # name. Ideally, we would detect whether a particular command is part of a subdecode block
138        # in the decoding script.
139        if not api_name.startswith("vkCmd") and api_name != "vkBeginCommandBufferAsyncGOOGLE":
140            self.module.append('    printer.write_int("seqno: ", 4, indent)\n')
141
142        for param in api.parameters:
143            # Add any structs that this API uses to the list of structs to write decoding logic for
144            if self.typeInfo.isCompoundType(param.typeName):
145                self.structs.add(param.typeName)
146
147            # Don't try to print the pData field of vkQueueFlushCommandsGOOGLE, those are the
148            # commands processed as part of the subdecode pass
149            if api.name == "vkQueueFlushCommandsGOOGLE" and param.paramName == "pData":
150                continue
151
152            # Write out decoding logic for that parameter
153            self.process_type(param)
154
155        # Finally, add a return statement. This is needed in case the API has no parameters.
156        self.module.append('    return\n\n')
157
158    def process_structs(self):
159        """Writes decoding logic for all the structs that we use"""
160
161        # self.structs now contains all the structs used directly by the Vulkan APIs we use.
162        # Recursively expand this set to add all the structs used by these structs.
163        copy = self.structs.copy()
164        self.structs.clear()
165        for struct_name in copy:
166            self.expand_needed_structs(struct_name)
167
168        # Now we have the full list of structs that we need to write decoding logic for.
169        # Write a decoder for each of them
170        for struct_name in sorted(self.structs):
171            struct = self.typeInfo.structs[struct_name]
172            self.module.append('def struct_{}(printer, indent: int):\n'.format(struct_name))
173            for member in self.get_members(struct):
174                self.process_type(member)
175            self.module.append('\n')
176
177    def expand_needed_structs(self, struct_name: str):
178        """
179        Recursively adds all the structs used by a given struct to the list of structs to process
180        """
181        if struct_name in self.structs:
182            return
183        self.structs.add(struct_name)
184        struct = self.typeInfo.structs[struct_name]
185        for member in self.get_members(struct):
186            if self.typeInfo.isCompoundType(member.typeName):
187                self.expand_needed_structs(member.typeName)
188
189    def get_members(self, struct: VulkanCompoundType):
190        """
191        Returns the members of a struct/union that we need to process.
192        For structs, returns the list of all members
193        For unions, returns a list with just the first member.
194        """
195        return struct.members[0:1] if struct.isUnion else struct.members
196
197    def process_type(self, type: VulkanType):
198        """
199        Writes decoding logic for a single Vulkan type. This could be the parameter in a Vulkan API,
200        or a struct member.
201        """
202        if type.typeName == "VkStructureType":
203            self.module.append(
204                '    printer.write_stype_and_pnext("{}", indent)\n'.format(
205                    type.parent.structEnumExpr))
206            return
207
208        if type.isNextPointer():
209            return
210
211        if type.paramName == "commandBuffer":
212            if type.parent.name != "vkQueueFlushCommandsGOOGLE":
213                return
214
215        # Enums
216        if type.isEnum(self.typeInfo):
217            self.needed_enums.add(type.typeName)
218            self.module.append(
219                '    printer.write_enum("{}", {}, indent)\n'.format(
220                    type.paramName, type.typeName))
221            return
222
223        # Bitmasks
224        if type.isBitmask(self.typeInfo):
225            enum_type = self.typeInfo.bitmasks.get(type.typeName)
226            if enum_type:
227                self.needed_enums.add(enum_type)
228                self.module.append(
229                    '    printer.write_flags("{}", {}, indent)\n'.format(
230                        type.paramName, enum_type))
231                return
232            # else, fall through and let the primitive type logic handle it
233
234        # Structs or unions
235        if self.typeInfo.isCompoundType(type.typeName):
236            self.module.append(
237                '    printer.write_struct("{name}", struct_{type}, {optional}, {count}, indent)\n'
238                    .format(name=type.paramName,
239                            type=type.typeName,
240                            optional=type.isOptionalPointer(),
241                            count=self.get_length_expression(type)))
242            return
243
244        # Null-terminated strings
245        if type.isString():
246            self.module.append('    printer.write_string("{}", None, indent)\n'.format(
247                type.paramName))
248            return
249
250        # Arrays of primitive types
251        if type.staticArrExpr and type.primitiveEncodingSize and type.primitiveEncodingSize <= 8:
252            # Array sizes are specified either as a number, or as an enum value
253            array_size = int(type.staticArrExpr) if type.staticArrExpr.isdigit() \
254                else self.typeInfo.enumValues.get(type.staticArrExpr)
255            assert array_size is not None, type.staticArrExpr
256
257            if type.typeName == "char":
258                self.module.append(
259                    '    printer.write_string("{}", {}, indent)\n'.format(
260                        type.paramName, array_size))
261            elif type.typeName == "float":
262                self.module.append(
263                    '    printer.write_float("{}", indent, count={})\n'
264                        .format(type.paramName, array_size))
265            else:
266                self.module.append(
267                    '    printer.write_int("{name}", {int_size}, indent, signed={signed}, count={array_size})\n'
268                        .format(name=type.paramName,
269                                array_size=array_size,
270                                int_size=type.primitiveEncodingSize,
271                                signed=type.isSigned()))
272            return
273
274        # Pointers
275        if type.pointerIndirectionLevels > 0:
276            # Assume that all uint32* are always serialized directly rather than passed by pointers.
277            # This is probably not always true (e.g. out params) - fix this as needed.
278            size = 4 if type.primitiveEncodingSize == 4 else 8
279            self.module.append(
280                '    {name} = printer.write_int("{name}", {size}, indent, optional={opt}, count={count}, big_endian={big_endian})\n'
281                    .format(name=type.paramName,
282                            size=size,
283                            opt=type.isOptionalPointer(),
284                            count=self.get_length_expression(type),
285                            big_endian=self.using_big_endian(type)))
286            return
287
288        # Primitive types (ints, floats)
289        if type.isSimpleValueType(self.typeInfo) and type.primitiveEncodingSize:
290            if type.typeName == "float":
291                self.module.append(
292                    '    printer.write_float("{name}", indent)\n'.format(name=type.paramName))
293            else:
294                self.module.append(
295                    '    {name} = printer.write_int("{name}", {size}, indent, signed={signed}, big_endian={big_endian})\n'.format(
296                        name=type.paramName,
297                        size=type.primitiveEncodingSize,
298                        signed=type.isSigned(),
299                        big_endian=self.using_big_endian(type))
300                )
301            return
302
303        raise NotImplementedError(
304            "No decoding logic for {} {}".format(type.typeName, type.paramName))
305
306    def using_big_endian(self, type: VulkanType):
307        """For some reason gfxstream serializes some types as big endian"""
308        return type.typeName == "size_t"
309
310    def get_length_expression(self, type: VulkanType) -> Optional[str]:
311        """Returns the length expression for a given type"""
312        if type.lenExpr is None:
313            return None
314
315        if type.lenExpr.isalpha():
316            return type.lenExpr
317
318        # There are a couple of instances in the spec where we use a math expression to express the
319        # length (e.g. VkPipelineMultisampleStateCreateInfo). CodeGen().generalLengthAccess() has
320        # logic o parse these expressions correctly, but for now,we just use a simple lookup table.
321        known_expressions = {
322            r"latexmath:[\lceil{\mathit{rasterizationSamples} \over 32}\rceil]":
323                "int(rasterizationSamples / 32)",
324            r"latexmath:[\textrm{codeSize} \over 4]": "int(codeSize / 4)",
325            r"null-terminated": None
326        }
327        if type.lenExpr in known_expressions:
328            return known_expressions[type.lenExpr]
329
330        raise NotImplementedError("Unknown length expression: " + type.lenExpr)
331
332    def process_enums(self):
333        """
334        For each Vulkan enum that we use, write out a python dictionary mapping the enum values back
335        to the enum name as a string
336        """
337        for enum_name in sorted(self.needed_enums):
338            self.module.append('{} = {{\n'.format(enum_name))
339            for identifier in self.all_enums[enum_name]:
340                value = self.typeInfo.enumValues.get(identifier)
341                if value is not None and isinstance(value, int):
342                    self.module.append('    {}: "{}",\n'.format(value, identifier))
343            self.module.append('}\n\n')
344