summaryrefslogtreecommitdiff
path: root/registry/vulkan/scripts/cereal/api_log_decoder.py
blob: 97930f5f080f28c3846077a51c1731dd9b4088bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
import os
from typing import List, Set, Dict, Optional

from . import VulkanType, VulkanCompoundType
from .wrapperdefs import VulkanWrapperGenerator


class ApiLogDecoder(VulkanWrapperGenerator):
    """
    This class generates decoding logic for the graphics API logs captured by
    [GfxApiLogger](http://source/play-internal/battlestar/aosp/device/generic/vulkan-cereal/base/GfxApiLogger.h)

    This allows developers to see a pretty-printed version of the API log data when using
    print_gfx_logs.py
    """

    # List of Vulkan APIs that we will generate decoding logic for
    generated_apis = [
        "vkAcquireImageANDROID",
        "vkAllocateMemory",
        "vkBeginCommandBufferAsyncGOOGLE",
        "vkBindBufferMemory",
        "vkBindImageMemory",
        "vkCmdBeginRenderPass",
        "vkCmdBindDescriptorSets",
        "vkCmdBindIndexBuffer",
        "vkCmdBindPipeline",
        "vkCmdBindVertexBuffers",
        "vkCmdClearAttachments",
        "vkCmdClearColorImage",
        "vkCmdCopyBufferToImage",
        "vkCmdCopyImageToBuffer",
        "vkCmdDraw",
        "vkCmdDrawIndexed",
        "vkCmdEndRenderPass",
        "vkCmdPipelineBarrier",
        "vkCmdSetScissor",
        "vkCmdSetViewport",
        "vkCollectDescriptorPoolIdsGOOGLE",
        "vkCreateBufferWithRequirementsGOOGLE",
        "vkCreateDescriptorPool",
        "vkCreateDescriptorSetLayout",
        "vkCreateFence",
        "vkCreateFramebuffer",
        "vkCreateGraphicsPipelines",
        "vkCreateImageView",
        "vkCreateImageWithRequirementsGOOGLE",
        "vkCreatePipelineCache",
        "vkCreateRenderPass",
        "vkCreateSampler",
        "vkCreateSemaphore",
        "vkCreateShaderModule",
        "vkDestroyBuffer",
        "vkDestroyCommandPool",
        "vkDestroyDescriptorPool",
        "vkDestroyDescriptorSetLayout",
        "vkDestroyDevice",
        "vkDestroyFence",
        "vkDestroyFramebuffer",
        "vkDestroyImage",
        "vkDestroyImageView",
        "vkDestroyInstance",
        "vkDestroyPipeline",
        "vkDestroyPipelineCache",
        "vkDestroyPipelineLayout",
        "vkDestroyRenderPass",
        "vkDestroySemaphore",
        "vkDestroyShaderModule",
        "vkEndCommandBufferAsyncGOOGLE",
        "vkFreeCommandBuffers",
        "vkFreeMemory",
        "vkFreeMemorySyncGOOGLE",
        "vkGetFenceStatus",
        "vkGetMemoryHostAddressInfoGOOGLE",
        "vkGetBlobGOOGLE",
        "vkGetPhysicalDeviceFormatProperties",
        "vkGetPhysicalDeviceProperties2KHR",
        "vkGetPipelineCacheData",
        "vkGetSwapchainGrallocUsageANDROID",
        "vkQueueCommitDescriptorSetUpdatesGOOGLE",
        "vkQueueFlushCommandsGOOGLE",
        "vkQueueSignalReleaseImageANDROIDAsyncGOOGLE",
        "vkQueueSubmitAsyncGOOGLE",
        "vkQueueWaitIdle",
        "vkResetFences",
        "vkWaitForFences",
    ]

    def __init__(self, module, typeInfo):
        VulkanWrapperGenerator.__init__(self, module, typeInfo)
        self.typeInfo = typeInfo

        # Set of Vulkan structs that we need to write decoding logic for
        self.structs: Set[str] = set()

        # Maps enum group names to the list of enums in the group, for all enum groups in the spec
        # E.g.:  "VkResult": ["VK_SUCCESS", "VK_NOT_READY", "VK_TIMEOUT", etc...]
        self.all_enums: Dict[str, List[str]] = {}

        # Set of Vulkan enums that we need to write decoding logic for
        self.needed_enums: Set[str] = {"VkStructureType"}

    def onBegin(self):
        self.module.append("""
#####################################################################################################
# Pretty-printer functions for Vulkan data structures
# THIS FILE IS AUTO-GENERATED - DO NOT EDIT
#
# To re-generate this file, run generate-vulkan-sources.sh
#####################################################################################################

""".lstrip())

    def onGenGroup(self, groupinfo, groupName, alias=None):
        """Called for each enum group in the spec"""
        for enum in groupinfo.elem.findall("enum"):
            self.all_enums[groupName] = self.all_enums.get(groupName, []) + [enum.get('name')]

    def onEnd(self):
        for api_name in sorted(self.generated_apis):
            self.process_api(api_name)
        self.process_structs()
        self.process_enums()

    def process_api(self, api_name):
        """Main entry point to generate decoding logic for each Vulkan API"""
        api = self.typeInfo.apis[api_name]
        self.module.append('def OP_{}(printer, indent: int):\n'.format(api_name))

        # Decode the sequence number. All commands have sequence numbers, except those handled
        # by VkSubdecoder.cpp. The logic here is a bit of a hack since it's based on the command
        # name. Ideally, we would detect whether a particular command is part of a subdecode block
        # in the decoding script.
        if not api_name.startswith("vkCmd") and api_name != "vkBeginCommandBufferAsyncGOOGLE":
            self.module.append('    printer.write_int("seqno: ", 4, indent)\n')

        for param in api.parameters:
            # Add any structs that this API uses to the list of structs to write decoding logic for
            if self.typeInfo.isCompoundType(param.typeName):
                self.structs.add(param.typeName)

            # Don't try to print the pData field of vkQueueFlushCommandsGOOGLE, those are the
            # commands processed as part of the subdecode pass
            if api.name == "vkQueueFlushCommandsGOOGLE" and param.paramName == "pData":
                continue

            # Write out decoding logic for that parameter
            self.process_type(param)

        # Finally, add a return statement. This is needed in case the API has no parameters.
        self.module.append('    return\n\n')

    def process_structs(self):
        """Writes decoding logic for all the structs that we use"""

        # self.structs now contains all the structs used directly by the Vulkan APIs we use.
        # Recursively expand this set to add all the structs used by these structs.
        copy = self.structs.copy()
        self.structs.clear()
        for struct_name in copy:
            self.expand_needed_structs(struct_name)

        # Now we have the full list of structs that we need to write decoding logic for.
        # Write a decoder for each of them
        for struct_name in sorted(self.structs):
            struct = self.typeInfo.structs[struct_name]
            self.module.append('def struct_{}(printer, indent: int):\n'.format(struct_name))
            for member in self.get_members(struct):
                self.process_type(member)
            self.module.append('\n')

    def expand_needed_structs(self, struct_name: str):
        """
        Recursively adds all the structs used by a given struct to the list of structs to process
        """
        if struct_name in self.structs:
            return
        self.structs.add(struct_name)
        struct = self.typeInfo.structs[struct_name]
        for member in self.get_members(struct):
            if self.typeInfo.isCompoundType(member.typeName):
                self.expand_needed_structs(member.typeName)

    def get_members(self, struct: VulkanCompoundType):
        """
        Returns the members of a struct/union that we need to process.
        For structs, returns the list of all members
        For unions, returns a list with just the first member.
        """
        return struct.members[0:1] if struct.isUnion else struct.members

    def process_type(self, type: VulkanType):
        """
        Writes decoding logic for a single Vulkan type. This could be the parameter in a Vulkan API,
        or a struct member.
        """
        if type.typeName == "VkStructureType":
            self.module.append(
                '    printer.write_stype_and_pnext("{}", indent)\n'.format(
                    type.parent.structEnumExpr))
            return

        if type.isNextPointer():
            return

        if type.paramName == "commandBuffer":
            if type.parent.name != "vkQueueFlushCommandsGOOGLE":
                return

        # Enums
        if type.isEnum(self.typeInfo):
            self.needed_enums.add(type.typeName)
            self.module.append(
                '    printer.write_enum("{}", {}, indent)\n'.format(
                    type.paramName, type.typeName))
            return

        # Bitmasks
        if type.isBitmask(self.typeInfo):
            enum_type = self.typeInfo.bitmasks.get(type.typeName)
            if enum_type:
                self.needed_enums.add(enum_type)
                self.module.append(
                    '    printer.write_flags("{}", {}, indent)\n'.format(
                        type.paramName, enum_type))
                return
            # else, fall through and let the primitive type logic handle it

        # Structs or unions
        if self.typeInfo.isCompoundType(type.typeName):
            self.module.append(
                '    printer.write_struct("{name}", struct_{type}, {optional}, {count}, indent)\n'
                    .format(name=type.paramName,
                            type=type.typeName,
                            optional=type.isOptionalPointer(),
                            count=self.get_length_expression(type)))
            return

        # Null-terminated strings
        if type.isString():
            self.module.append('    printer.write_string("{}", None, indent)\n'.format(
                type.paramName))
            return

        # Arrays of primitive types
        if type.staticArrExpr and type.primitiveEncodingSize and type.primitiveEncodingSize <= 8:
            # Array sizes are specified either as a number, or as an enum value
            array_size = int(type.staticArrExpr) if type.staticArrExpr.isdigit() \
                else self.typeInfo.enumValues.get(type.staticArrExpr)
            assert array_size is not None, type.staticArrExpr

            if type.typeName == "char":
                self.module.append(
                    '    printer.write_string("{}", {}, indent)\n'.format(
                        type.paramName, array_size))
            elif type.typeName == "float":
                self.module.append(
                    '    printer.write_float("{}", indent, count={})\n'
                        .format(type.paramName, array_size))
            else:
                self.module.append(
                    '    printer.write_int("{name}", {int_size}, indent, signed={signed}, count={array_size})\n'
                        .format(name=type.paramName,
                                array_size=array_size,
                                int_size=type.primitiveEncodingSize,
                                signed=type.isSigned()))
            return

        # Pointers
        if type.pointerIndirectionLevels > 0:
            # Assume that all uint32* are always serialized directly rather than passed by pointers.
            # This is probably not always true (e.g. out params) - fix this as needed.
            size = 4 if type.primitiveEncodingSize == 4 else 8
            self.module.append(
                '    {name} = printer.write_int("{name}", {size}, indent, optional={opt}, count={count}, big_endian={big_endian})\n'
                    .format(name=type.paramName,
                            size=size,
                            opt=type.isOptionalPointer(),
                            count=self.get_length_expression(type),
                            big_endian=self.using_big_endian(type)))
            return

        # Primitive types (ints, floats)
        if type.isSimpleValueType(self.typeInfo) and type.primitiveEncodingSize:
            if type.typeName == "float":
                self.module.append(
                    '    printer.write_float("{name}", indent)\n'.format(name=type.paramName))
            else:
                self.module.append(
                    '    {name} = printer.write_int("{name}", {size}, indent, signed={signed}, big_endian={big_endian})\n'.format(
                        name=type.paramName,
                        size=type.primitiveEncodingSize,
                        signed=type.isSigned(),
                        big_endian=self.using_big_endian(type))
                )
            return

        raise NotImplementedError(
            "No decoding logic for {} {}".format(type.typeName, type.paramName))

    def using_big_endian(self, type: VulkanType):
        """For some reason gfxstream serializes some types as big endian"""
        return type.typeName == "size_t"

    def get_length_expression(self, type: VulkanType) -> Optional[str]:
        """Returns the length expression for a given type"""
        if type.lenExpr is None:
            return None

        if type.lenExpr.isalpha():
            return type.lenExpr

        # There are a couple of instances in the spec where we use a math expression to express the
        # length (e.g. VkPipelineMultisampleStateCreateInfo). CodeGen().generalLengthAccess() has
        # logic o parse these expressions correctly, but for now,we just use a simple lookup table.
        known_expressions = {
            r"latexmath:[\lceil{\mathit{rasterizationSamples} \over 32}\rceil]":
                "int(rasterizationSamples / 32)",
            r"latexmath:[\textrm{codeSize} \over 4]": "int(codeSize / 4)",
            r"null-terminated": None
        }
        if type.lenExpr in known_expressions:
            return known_expressions[type.lenExpr]

        raise NotImplementedError("Unknown length expression: " + type.lenExpr)

    def process_enums(self):
        """
        For each Vulkan enum that we use, write out a python dictionary mapping the enum values back
        to the enum name as a string
        """
        for enum_name in sorted(self.needed_enums):
            self.module.append('{} = {{\n'.format(enum_name))
            for identifier in self.all_enums[enum_name]:
                value = self.typeInfo.enumValues.get(identifier)
                if value is not None and isinstance(value, int):
                    self.module.append('    {}: "{}",\n'.format(value, identifier))
            self.module.append('}\n\n')