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