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