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