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