1# Copyright © 2020 Hoe Hao Cheng 2# 3# Permission is hereby granted, free of charge, to any person obtaining a 4# copy of this software and associated documentation files (the "Software"), 5# to deal in the Software without restriction, including without limitation 6# the rights to use, copy, modify, merge, publish, distribute, sublicense, 7# and/or sell copies of the Software, and to permit persons to whom the 8# Software is furnished to do so, subject to the following conditions: 9# 10# The above copyright notice and this permission notice (including the next 11# paragraph) shall be included in all copies or substantial portions of the 12# Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20# IN THE SOFTWARE. 21# 22 23import re 24from xml.etree import ElementTree 25from typing import List,Tuple 26 27class Version: 28 device_version = (1,0,0) 29 struct_version = (1,0) 30 31 def __init__(self, version, struct=()): 32 self.device_version = version 33 34 if not struct: 35 self.struct_version = (version[0], version[1]) 36 else: 37 self.struct_version = struct 38 39 # e.g. "VK_MAKE_VERSION(1,2,0)" 40 def version(self): 41 return ("VK_MAKE_VERSION(" 42 + str(self.device_version[0]) 43 + "," 44 + str(self.device_version[1]) 45 + "," 46 + str(self.device_version[2]) 47 + ")") 48 49 # e.g. "10" 50 def struct(self): 51 return (str(self.struct_version[0])+str(self.struct_version[1])) 52 53 # the sType of the extension's struct 54 # e.g. VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT 55 # for VK_EXT_transform_feedback and struct="FEATURES" 56 def stype(self, struct: str): 57 return ("VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_" 58 + str(self.struct_version[0]) + "_" + str(self.struct_version[1]) 59 + '_' + struct) 60 61class Extension: 62 name = None 63 alias = None 64 is_required = False 65 is_nonstandard = False 66 enable_conds = None 67 core_since = None 68 69 # these are specific to zink_device_info.py: 70 has_properties = False 71 has_features = False 72 guard = False 73 features_promoted = False 74 properties_promoted = False 75 76 77 # these are specific to zink_instance.py: 78 platform_guard = None 79 80 def __init__(self, name, alias="", required=False, nonstandard=False, 81 properties=False, features=False, conditions=None, guard=False): 82 self.name = name 83 self.alias = alias 84 self.is_required = required 85 self.is_nonstandard = nonstandard 86 self.has_properties = properties 87 self.has_features = features 88 self.enable_conds = conditions 89 self.guard = guard 90 91 if alias == "" and (properties == True or features == True): 92 raise RuntimeError("alias must be available when properties and/or features are used") 93 94 # e.g.: "VK_EXT_robustness2" -> "robustness2" 95 def pure_name(self): 96 return '_'.join(self.name.split('_')[2:]) 97 98 # e.g.: "VK_EXT_robustness2" -> "EXT_robustness2" 99 def name_with_vendor(self): 100 return self.name[3:] 101 102 # e.g.: "VK_EXT_robustness2" -> "Robustness2" 103 def name_in_camel_case(self): 104 return "".join([x.title() for x in self.name.split('_')[2:]]) 105 106 # e.g.: "VK_EXT_robustness2" -> "VK_EXT_ROBUSTNESS_2" 107 def name_in_snake_uppercase(self): 108 def replace(original): 109 # we do not split the types into two, e.g. INT_32 110 match_types = re.match(".*(int|float)(8|16|32|64)$", original) 111 112 # do not match win32 113 match_os = re.match(".*win32$", original) 114 115 # try to match extensions with alphanumeric names, like robustness2 116 match_alphanumeric = re.match("([a-z]+)(\d+)", original) 117 118 if match_types is not None or match_os is not None: 119 return original.upper() 120 121 if match_alphanumeric is not None: 122 return (match_alphanumeric[1].upper() 123 + '_' 124 + match_alphanumeric[2]) 125 126 return original.upper() 127 128 replaced = list(map(replace, self.name.split('_'))) 129 return '_'.join(replaced) 130 131 # e.g.: "VK_EXT_robustness2" -> "ROBUSTNESS_2" 132 def pure_name_in_snake_uppercase(self): 133 return '_'.join(self.name_in_snake_uppercase().split('_')[2:]) 134 135 # e.g.: "VK_EXT_robustness2" -> "VK_EXT_ROBUSTNESS_2_EXTENSION_NAME" 136 def extension_name(self): 137 return self.name_in_snake_uppercase() + "_EXTENSION_NAME" 138 139 # generate a C string literal for the extension 140 def extension_name_literal(self): 141 return '"' + self.name + '"' 142 143 # get the field in zink_device_info that refers to the extension's 144 # feature/properties struct 145 # e.g. rb2_<suffix> for VK_EXT_robustness2 146 def field(self, suffix: str): 147 return self.alias + '_' + suffix 148 149 def physical_device_struct(self, struct: str): 150 if self.name_in_camel_case().endswith(struct): 151 struct = "" 152 153 return ("VkPhysicalDevice" 154 + self.name_in_camel_case() 155 + struct 156 + self.vendor()) 157 158 # the sType of the extension's struct 159 # e.g. VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT 160 # for VK_EXT_transform_feedback and struct="FEATURES" 161 def stype(self, struct: str): 162 return ("VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_" 163 + self.pure_name_in_snake_uppercase() 164 + '_' + struct + '_' 165 + self.vendor()) 166 167 # e.g. EXT in VK_EXT_robustness2 168 def vendor(self): 169 return self.name.split('_')[1] 170 171# Type aliases 172Layer = Extension 173 174class ExtensionRegistryEntry: 175 # type of extension - right now it's either "instance" or "device" 176 ext_type = "" 177 # the version in which the extension is promoted to core VK 178 promoted_in = None 179 # functions added by the extension are referred to as "commands" in the registry 180 device_commands = None 181 pdevice_commands = None 182 instance_commands = None 183 constants = None 184 features_struct = None 185 features_fields = None 186 features_promoted = False 187 properties_struct = None 188 properties_fields = None 189 properties_promoted = False 190 # some instance extensions are locked behind certain platforms 191 platform_guard = "" 192 193class ExtensionRegistry: 194 # key = extension name, value = registry entry 195 registry = dict() 196 197 def __init__(self, vkxml_path: str): 198 vkxml = ElementTree.parse(vkxml_path) 199 200 commands_type = dict() 201 command_aliases = dict() 202 platform_guards = dict() 203 struct_aliases = dict() 204 205 for cmd in vkxml.findall("commands/command"): 206 name = cmd.find("./proto/name") 207 208 if name is not None and name.text: 209 commands_type[name.text] = cmd.find("./param/type").text 210 elif cmd.get("name") is not None: 211 command_aliases[cmd.get("name")] = cmd.get("alias") 212 213 for typ in vkxml.findall("types/type"): 214 if typ.get("category") != "struct": 215 continue 216 217 name = typ.get("name") 218 alias = typ.get("alias") 219 220 if name and alias: 221 struct_aliases[name] = alias 222 223 for (cmd, alias) in command_aliases.items(): 224 commands_type[cmd] = commands_type[alias] 225 226 for platform in vkxml.findall("platforms/platform"): 227 name = platform.get("name") 228 guard = platform.get("protect") 229 platform_guards[name] = guard 230 231 for ext in vkxml.findall("extensions/extension"): 232 # Reserved extensions are marked with `supported="disabled"` 233 if ext.get("supported") == "disabled": 234 continue 235 236 name = ext.attrib["name"] 237 238 entry = ExtensionRegistryEntry() 239 entry.ext_type = ext.attrib["type"] 240 entry.promoted_in = self.parse_promotedto(ext.get("promotedto")) 241 242 entry.device_commands = [] 243 entry.pdevice_commands = [] 244 entry.instance_commands = [] 245 entry.features_fields = [] 246 entry.properties_fields = [] 247 248 for cmd in ext.findall("require/command"): 249 cmd_name = cmd.get("name") 250 if cmd_name: 251 if commands_type[cmd_name] in ("VkDevice", "VkCommandBuffer", "VkQueue"): 252 entry.device_commands.append(cmd_name) 253 elif commands_type[cmd_name] in ("VkPhysicalDevice"): 254 entry.pdevice_commands.append(cmd_name) 255 else: 256 entry.instance_commands.append(cmd_name) 257 258 entry.constants = [] 259 for enum in ext.findall("require/enum"): 260 enum_name = enum.get("name") 261 enum_extends = enum.get("extends") 262 # we are only interested in VK_*_EXTENSION_NAME, which does not 263 # have an "extends" attribute 264 if not enum_extends: 265 entry.constants.append(enum_name) 266 267 for ty in ext.findall("require/type"): 268 ty_name = ty.get("name") 269 if (self.is_features_struct(ty_name) and 270 entry.features_struct is None): 271 entry.features_struct = ty_name 272 273 elif (self.is_properties_struct(ty_name) and 274 entry.properties_struct is None): 275 entry.properties_struct = ty_name 276 277 if entry.features_struct: 278 struct_name = entry.features_struct 279 if entry.features_struct in struct_aliases: 280 struct_name = struct_aliases[entry.features_struct] 281 entry.features_promoted = True 282 283 elif entry.promoted_in is not None: 284 # if the extension is promoted but a core-Vulkan alias is not 285 # available for the features, then consider the features struct 286 # non-core-promoted 287 entry.features_promoted = False 288 289 for field in vkxml.findall("./types/type[@name='{}']/member".format(struct_name)): 290 field_name = field.find("name").text 291 292 # we ignore sType and pNext since they are irrelevant 293 if field_name not in ["sType", "pNext"]: 294 entry.features_fields.append(field_name) 295 296 if entry.properties_struct: 297 struct_name = entry.properties_struct 298 if entry.properties_struct in struct_aliases: 299 struct_name = struct_aliases[entry.properties_struct] 300 entry.properties_promoted = True 301 302 elif entry.promoted_in is not None: 303 # if the extension is promoted but a core-Vulkan alias is not 304 # available for the properties, then it is not promoted to core 305 entry.properties_promoted = False 306 307 for field in vkxml.findall("./types/type[@name='{}']/member".format(struct_name)): 308 field_name = field.find("name").text 309 310 # we ignore sType and pNext since they are irrelevant 311 if field_name not in ["sType", "pNext"]: 312 entry.properties_fields.append(field_name) 313 314 if ext.get("platform") is not None: 315 entry.platform_guard = platform_guards[ext.get("platform")] 316 317 self.registry[name] = entry 318 319 def in_registry(self, ext_name: str): 320 return ext_name in self.registry 321 322 def get_registry_entry(self, ext_name: str): 323 if self.in_registry(ext_name): 324 return self.registry[ext_name] 325 326 # Parses e.g. "VK_VERSION_x_y" to integer tuple (x, y) 327 # For any erroneous inputs, None is returned 328 def parse_promotedto(self, promotedto: str): 329 result = None 330 331 if promotedto and promotedto.startswith("VK_VERSION_"): 332 (major, minor) = promotedto.split('_')[-2:] 333 result = (int(major), int(minor)) 334 335 return result 336 337 def is_features_struct(self, struct: str): 338 return re.match(r"VkPhysicalDevice.*Features.*", struct) is not None 339 340 def is_properties_struct(self, struct: str): 341 return re.match(r"VkPhysicalDevice.*Properties.*", struct) is not None 342