1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2023 The Khronos Group Inc. 4# Copyright 2023-2024 Google Inc. 5# 6# SPDX-License-Identifier: Apache-2.0 7 8"""Types and classes for manipulating an API registry.""" 9 10import copy 11import re 12import sys 13import xml.etree.ElementTree as etree 14from collections import defaultdict, deque, namedtuple 15 16from generator import GeneratorOptions, OutputGenerator, noneStr, write 17 18def apiNameMatch(str, supported): 19 """Return whether a required api name matches a pattern specified for an 20 XML <feature> 'api' attribute or <extension> 'supported' attribute. 21 22 - str - API name such as 'vulkan' or 'openxr'. May be None, in which 23 case it never matches (this should not happen). 24 - supported - comma-separated list of XML API names. May be None, in 25 which case str always matches (this is the usual case).""" 26 27 if str is not None: 28 return supported is None or str in supported.split(',') 29 30 # Fallthrough case - either str is None or the test failed 31 return False 32 33def matchAPIProfile(api, elem): 34 """Return whether an API and profile 35 like `"gl(core)|gles1(common-lite)"`.""" 36 # Match 'api', if present 37 elem_api = elem.get('api') 38 if elem_api: 39 if api is None: 40 raise UserWarning("No API requested, but 'api' attribute is present with value '" 41 + elem_api + "'") 42 elif api != elem_api: 43 # Requested API does not match attribute 44 return False 45 return True 46 47class BaseInfo: 48 """Base class for information about a registry feature 49 (type/group/enum/command/API/extension). 50 51 Represents the state of a registry feature, used during API generation. 52 """ 53 54 def __init__(self, elem): 55 self.required = False 56 """should this feature be defined during header generation 57 (has it been removed by a profile or version)?""" 58 59 self.declared = False 60 "has this feature been defined already?" 61 62 self.elem = elem 63 "etree Element for this feature" 64 65 def resetState(self): 66 """Reset required/declared to initial values. Used 67 prior to generating a new API interface.""" 68 self.required = False 69 self.declared = False 70 71 def compareKeys(self, info, key, required = False): 72 """Return True if self.elem and info.elem have the same attribute 73 value for key. 74 If 'required' is not True, also returns True if neither element 75 has an attribute value for key.""" 76 77 if required and key not in self.elem.keys(): 78 return False 79 return self.elem.get(key) == info.elem.get(key) 80 81 def compareElem(self, info, infoName): 82 """Return True if self.elem and info.elem have the same definition. 83 info - the other object 84 infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 85 'extension'""" 86 87 if infoName == 'enum': 88 if self.compareKeys(info, 'extends'): 89 # Either both extend the same type, or no type 90 if (self.compareKeys(info, 'value', required = True) or 91 self.compareKeys(info, 'bitpos', required = True)): 92 # If both specify the same value or bit position, 93 # they are equal 94 return True 95 elif (self.compareKeys(info, 'extnumber') and 96 self.compareKeys(info, 'offset') and 97 self.compareKeys(info, 'dir')): 98 # If both specify the same relative offset, they are equal 99 return True 100 elif (self.compareKeys(info, 'alias')): 101 # If both are aliases of the same value 102 return True 103 else: 104 return False 105 else: 106 # The same enum cannot extend two different types 107 return False 108 else: 109 # Non-<enum>s should never be redefined 110 return False 111 112 113class TypeInfo(BaseInfo): 114 """Registry information about a type. No additional state 115 beyond BaseInfo is required.""" 116 117 def __init__(self, elem): 118 BaseInfo.__init__(self, elem) 119 self.additionalValidity = [] 120 self.removedValidity = [] 121 122 def getMembers(self): 123 """Get a collection of all member elements for this type, if any.""" 124 return self.elem.findall('member') 125 126 def resetState(self): 127 BaseInfo.resetState(self) 128 self.additionalValidity = [] 129 self.removedValidity = [] 130 131 132class GroupInfo(BaseInfo): 133 """Registry information about a group of related enums 134 in an <enums> block, generally corresponding to a C "enum" type.""" 135 136 def __init__(self, elem): 137 BaseInfo.__init__(self, elem) 138 139 140class EnumInfo(BaseInfo): 141 """Registry information about an enum""" 142 143 def __init__(self, elem): 144 BaseInfo.__init__(self, elem) 145 self.type = elem.get('type') 146 """numeric type of the value of the <enum> tag 147 ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 )""" 148 if self.type is None: 149 self.type = '' 150 151 152class CmdInfo(BaseInfo): 153 """Registry information about a command""" 154 155 def __init__(self, elem): 156 BaseInfo.__init__(self, elem) 157 self.additionalValidity = [] 158 self.removedValidity = [] 159 160 def getParams(self): 161 """Get a collection of all param elements for this command, if any.""" 162 return self.elem.findall('param') 163 164 def resetState(self): 165 BaseInfo.resetState(self) 166 self.additionalValidity = [] 167 self.removedValidity = [] 168 169 170class FeatureInfo(BaseInfo): 171 """Registry information about an API <feature> 172 or <extension>.""" 173 174 def __init__(self, elem): 175 BaseInfo.__init__(self, elem) 176 self.name = elem.get('name') 177 "feature name string (e.g. 'VK_KHR_surface')" 178 179 self.emit = False 180 "has this feature been defined already?" 181 182 self.sortorder = int(elem.get('sortorder', 0)) 183 """explicit numeric sort key within feature and extension groups. 184 Defaults to 0.""" 185 186 # Determine element category (vendor). Only works 187 # for <extension> elements. 188 if elem.tag == 'feature': 189 # Element category (vendor) is meaningless for <feature> 190 self.category = 'VERSION' 191 """category, e.g. VERSION or khr/vendor tag""" 192 193 self.version = elem.get('name') 194 """feature name string""" 195 196 self.versionNumber = elem.get('number') 197 """versionNumber - API version number, taken from the 'number' 198 attribute of <feature>. Extensions do not have API version 199 numbers and are assigned number 0.""" 200 201 self.number = 0 202 self.supported = None 203 else: 204 # Extract vendor portion of <APIprefix>_<vendor>_<name> 205 self.category = self.name.split('_', 2)[1] 206 self.version = "0" 207 self.versionNumber = "0" 208 209 self.number = int(elem.get('number','0')) 210 """extension number, used for ordering and for assigning 211 enumerant offsets. <feature> features do not have extension 212 numbers and are assigned number 0, as are extensions without 213 numbers, so sorting works.""" 214 215 self.supported = elem.get('supported', 'disabled') 216 217class Registry: 218 """Object representing an API registry, loaded from an XML file.""" 219 220 def __init__(self, gen=None, genOpts=None): 221 if gen is None: 222 # If not specified, give a default object so messaging will work 223 self.gen = OutputGenerator() 224 else: 225 self.gen = gen 226 "Output generator used to write headers / messages" 227 228 if genOpts is None: 229 # If no generator is provided, we may still need the XML API name 230 # (for example, in genRef.py). 231 self.genOpts = GeneratorOptions(apiname = 'vulkan') 232 else: 233 self.genOpts = genOpts 234 "Options controlling features to write and how to format them" 235 236 self.gen.registry = self 237 self.gen.genOpts = self.genOpts 238 self.gen.genOpts.registry = self 239 240 self.tree = None 241 "ElementTree containing the root `<registry>`" 242 243 self.typedict = {} 244 "dictionary of TypeInfo objects keyed by type name" 245 246 self.groupdict = {} 247 "dictionary of GroupInfo objects keyed by group name" 248 249 self.enumdict = {} 250 "dictionary of EnumInfo objects keyed by enum name" 251 252 self.cmddict = {} 253 "dictionary of CmdInfo objects keyed by command name" 254 255 self.apidict = {} 256 "dictionary of FeatureInfo objects for `<feature>` elements keyed by API name" 257 258 self.extensions = [] 259 "list of `<extension>` Elements" 260 261 self.extdict = {} 262 "dictionary of FeatureInfo objects for `<extension>` elements keyed by extension name" 263 264 self.emitFeatures = False 265 """True to actually emit features for a version / extension, 266 or False to just treat them as emitted""" 267 268 self.filename = None 269 270 def loadElementTree(self, tree): 271 """Load ElementTree into a Registry object and parse it.""" 272 self.tree = tree 273 self.parseTree() 274 275 def loadFile(self, file): 276 """Load an API registry XML file into a Registry object and parse it""" 277 self.filename = file 278 self.tree = etree.parse(file) 279 self.parseTree() 280 281 def setGenerator(self, gen): 282 """Specify output generator object. 283 284 `None` restores the default generator.""" 285 self.gen = gen 286 self.gen.setRegistry(self) 287 288 def addElementInfo(self, elem, info, infoName, dictionary): 289 """Add information about an element to the corresponding dictionary. 290 291 Intended for internal use only. 292 293 - elem - `<type>`/`<enums>`/`<enum>`/`<command>`/`<feature>`/`<extension>`/`<spirvextension>`/`<spirvcapability>`/`<format>`/`<syncstage>`/`<syncaccess>`/`<syncpipeline>` Element 294 - info - corresponding {Type|Group|Enum|Cmd|Feature|Spirv|Format|SyncStage|SyncAccess|SyncPipeline}Info object 295 - infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' / 'spirvextension' / 'spirvcapability' / 'format' / 'syncstage' / 'syncaccess' / 'syncpipeline' 296 - dictionary - self.{type|group|enum|cmd|api|ext|format|spirvext|spirvcap|sync}dict 297 298 The dictionary key is the element 'name' attribute.""" 299 300 key = elem.get('name') 301 if key in dictionary: 302 if not dictionary[key].compareElem(info, infoName): 303 return 304 else: 305 dictionary[key] = info 306 307 def lookupElementInfo(self, fname, dictionary): 308 """Find a {Type|Enum|Cmd}Info object by name. 309 310 Intended for internal use only. 311 312 If an object qualified by API name exists, use that. 313 314 - fname - name of type / enum / command 315 - dictionary - self.{type|enum|cmd}dict""" 316 key = (fname, 'vulkan') 317 if key in dictionary: 318 return dictionary[key] 319 if fname in dictionary: 320 return dictionary[fname] 321 322 return None 323 324 def parseTree(self): 325 """Parse the registry Element, once created""" 326 # This must be the Element for the root <registry> 327 if self.tree is None: 328 raise RuntimeError("Tree not initialized!") 329 self.reg = self.tree.getroot() 330 331 # There is usually one <types> block; more are OK 332 # Required <type> attributes: 'name' or nested <name> tag contents 333 self.typedict = {} 334 for type_elem in self.reg.findall('types/type'): 335 # If the <type> does not already have a 'name' attribute, set 336 # it from contents of its <name> tag. 337 if type_elem.get('name') is None: 338 name_elem = type_elem.find('name') 339 if name_elem is None or not name_elem.text: 340 raise RuntimeError("Type without a name!") 341 type_elem.set('name', name_elem.text) 342 self.addElementInfo(type_elem, TypeInfo(type_elem), 'type', self.typedict) 343 344 # Create dictionary of registry enum groups from <enums> tags. 345 # 346 # Required <enums> attributes: 'name'. If no name is given, one is 347 # generated, but that group cannot be identified and turned into an 348 # enum type definition - it is just a container for <enum> tags. 349 self.groupdict = {} 350 for group in self.reg.findall('enums'): 351 self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) 352 353 # Create dictionary of registry enums from <enum> tags 354 # 355 # <enums> tags usually define different namespaces for the values 356 # defined in those tags, but the actual names all share the 357 # same dictionary. 358 # Required <enum> attributes: 'name', 'value' 359 # For containing <enums> which have type="enum" or type="bitmask", 360 # tag all contained <enum>s are required. This is a stopgap until 361 # a better scheme for tagging core and extension enums is created. 362 self.enumdict = {} 363 for enums in self.reg.findall('enums'): 364 required = (enums.get('type') is not None) 365 for enum in enums.findall('enum'): 366 enumInfo = EnumInfo(enum) 367 enumInfo.required = required 368 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 369 370 # Create dictionary of registry commands from <command> tags 371 # and add 'name' attribute to each <command> tag (where missing) 372 # based on its <proto><name> element. 373 # 374 # There is usually only one <commands> block; more are OK. 375 # Required <command> attributes: 'name' or <proto><name> tag contents 376 self.cmddict = {} 377 # List of commands which alias others. Contains 378 # [ aliasName, element ] 379 # for each alias 380 cmdAlias = [] 381 for cmd in self.reg.findall('commands/command'): 382 # If the <command> does not already have a 'name' attribute, set 383 # it from contents of its <proto><name> tag. 384 name = cmd.get('name') 385 if name is None: 386 name_elem = cmd.find('proto/name') 387 if name_elem is None or not name_elem.text: 388 raise RuntimeError("Command without a name!") 389 name = cmd.set('name', name_elem.text) 390 ci = CmdInfo(cmd) 391 self.addElementInfo(cmd, ci, 'command', self.cmddict) 392 alias = cmd.get('alias') 393 if alias: 394 cmdAlias.append([name, alias, cmd]) 395 396 # Now loop over aliases, injecting a copy of the aliased command's 397 # Element with the aliased prototype name replaced with the command 398 # name - if it exists. 399 for (name, alias, cmd) in cmdAlias: 400 if alias in self.cmddict: 401 aliasInfo = self.cmddict[alias] 402 cmdElem = copy.deepcopy(aliasInfo.elem) 403 cmdElem.find('proto/name').text = name 404 cmdElem.set('name', name) 405 cmdElem.set('alias', alias) 406 ci = CmdInfo(cmdElem) 407 # Replace the dictionary entry for the CmdInfo element 408 self.cmddict[name] = ci 409 410 # Create dictionaries of API and extension interfaces 411 # from toplevel <api> and <extension> tags. 412 self.apidict = {} 413 for feature in self.reg.findall('feature'): 414 featureInfo = FeatureInfo(feature) 415 self.addElementInfo(feature, featureInfo, 'feature', self.apidict) 416 417 # Add additional enums defined only in <feature> tags 418 # to the corresponding enumerated type. 419 # When seen here, the <enum> element, processed to contain the 420 # numeric enum value, is added to the corresponding <enums> 421 # element, as well as adding to the enum dictionary. It is no 422 # longer removed from the <require> element it is introduced in. 423 # Instead, generateRequiredInterface ignores <enum> elements 424 # that extend enumerated types. 425 # 426 # For <enum> tags which are actually just constants, if there is 427 # no 'extends' tag but there is a 'value' or 'bitpos' tag, just 428 # add an EnumInfo record to the dictionary. That works because 429 # output generation of constants is purely dependency-based, and 430 # does not need to iterate through the XML tags. 431 for elem in feature.findall('require'): 432 for enum in elem.findall('enum'): 433 addEnumInfo = False 434 groupName = enum.get('extends') 435 if groupName is not None: 436 # Add version number attribute to the <enum> element 437 enum.set('version', featureInfo.version) 438 # Look up the GroupInfo with matching groupName 439 if groupName in self.groupdict: 440 gi = self.groupdict[groupName] 441 gi.elem.append(copy.deepcopy(enum)) 442 addEnumInfo = True 443 elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): 444 addEnumInfo = True 445 if addEnumInfo: 446 enumInfo = EnumInfo(enum) 447 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 448 449 self.extensions = self.reg.findall('extensions/extension') 450 self.extdict = {} 451 for feature in self.extensions: 452 featureInfo = FeatureInfo(feature) 453 self.addElementInfo(feature, featureInfo, 'extension', self.extdict) 454 455 # Add additional enums defined only in <extension> tags 456 # to the corresponding core type. 457 # Algorithm matches that of enums in a "feature" tag as above. 458 # 459 # This code also adds a 'extnumber' attribute containing the 460 # extension number, used for enumerant value calculation. 461 for elem in feature.findall('require'): 462 for enum in elem.findall('enum'): 463 addEnumInfo = False 464 groupName = enum.get('extends') 465 if groupName is not None: 466 467 # Add <extension> block's extension number attribute to 468 # the <enum> element unless specified explicitly, such 469 # as when redefining an enum in another extension. 470 extnumber = enum.get('extnumber') 471 if not extnumber: 472 enum.set('extnumber', str(featureInfo.number)) 473 474 enum.set('extname', featureInfo.name) 475 enum.set('supported', noneStr(featureInfo.supported)) 476 # Look up the GroupInfo with matching groupName 477 if groupName in self.groupdict: 478 gi = self.groupdict[groupName] 479 gi.elem.append(copy.deepcopy(enum)) 480 481 addEnumInfo = True 482 elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): 483 addEnumInfo = True 484 if addEnumInfo: 485 enumInfo = EnumInfo(enum) 486 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 487 488 def markTypeRequired(self, typename, required): 489 """Require (along with its dependencies) or remove (but not its dependencies) a type. 490 491 - typename - name of type 492 - required - boolean (to tag features as required or not) 493 """ 494 # Get TypeInfo object for <type> tag corresponding to typename 495 typeinfo = self.lookupElementInfo(typename, self.typedict) 496 if typeinfo is not None: 497 if required: 498 # Tag type dependencies in 'alias' and 'required' attributes as 499 # required. This does not un-tag dependencies in a <remove> 500 # tag. See comments in markRequired() below for the reason. 501 for attrib_name in ['requires', 'alias']: 502 depname = typeinfo.elem.get(attrib_name) 503 if depname: 504 # Do not recurse on self-referential structures. 505 if typename != depname: 506 self.markTypeRequired(depname, required) 507 # Tag types used in defining this type (e.g. in nested 508 # <type> tags) 509 # Look for <type> in entire <command> tree, 510 # not just immediate children 511 for subtype in typeinfo.elem.findall('.//type'): 512 if typename != subtype.text: 513 self.markTypeRequired(subtype.text, required) 514 # Tag enums used in defining this type, for example in 515 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 516 for subenum in typeinfo.elem.findall('.//enum'): 517 self.markEnumRequired(subenum.text, required) 518 # Tag type dependency in 'bitvalues' attributes as 519 # required. This ensures that the bit values for a flag 520 # are emitted 521 depType = typeinfo.elem.get('bitvalues') 522 if depType: 523 self.markTypeRequired(depType, required) 524 group = self.lookupElementInfo(depType, self.groupdict) 525 if group is not None: 526 group.flagType = typeinfo 527 528 typeinfo.required = required 529 530 def markEnumRequired(self, enumname, required): 531 """Mark an enum as required or not. 532 533 - enumname - name of enum 534 - required - boolean (to tag features as required or not)""" 535 536 enum = self.lookupElementInfo(enumname, self.enumdict) 537 if enum is not None: 538 # If the enum is part of a group, and is being removed, then 539 # look it up in that <enums> tag and remove the Element there, 540 # so that it is not visible to generators (which traverse the 541 # <enums> tag elements rather than using the dictionaries). 542 if not required: 543 groupName = enum.elem.get('extends') 544 if groupName is not None: 545 546 # Look up the Info with matching groupName 547 if groupName in self.groupdict: 548 gi = self.groupdict[groupName] 549 gienum = gi.elem.find("enum[@name='" + enumname + "']") 550 if gienum is not None: 551 # Remove copy of this enum from the group 552 gi.elem.remove(gienum) 553 else: 554 # This enum is not an extending enum. 555 # The XML tree must be searched for all <enums> that 556 # might have it, so we know the parent to delete from. 557 558 enumName = enum.elem.get('name') 559 count = 0 560 for enums in self.reg.findall('enums'): 561 for thisEnum in enums.findall('enum'): 562 if thisEnum.get('name') == enumName: 563 # Actually remove it 564 count = count + 1 565 enums.remove(thisEnum) 566 567 enum.required = required 568 # Tag enum dependencies in 'alias' attribute as required 569 depname = enum.elem.get('alias') 570 if depname: 571 self.markEnumRequired(depname, required) 572 573 def markCmdRequired(self, cmdname, required): 574 """Mark a command as required or not. 575 576 - cmdname - name of command 577 - required - boolean (to tag features as required or not)""" 578 cmd = self.lookupElementInfo(cmdname, self.cmddict) 579 if cmd is not None: 580 cmd.required = required 581 # Tag all parameter types of this command as required. 582 # This does not remove types of commands in a <remove> 583 # tag, because many other commands may use the same type. 584 # We could be more clever and reference count types, 585 # instead of using a boolean. 586 if required: 587 # Look for <type> in entire <command> tree, 588 # not just immediate children 589 for type_elem in cmd.elem.findall('.//type'): 590 self.markTypeRequired(type_elem.text, required) 591 592 def markRequired(self, featurename, feature, required): 593 """Require or remove features specified in the Element. 594 595 - featurename - name of the feature 596 - feature - Element for `<require>` or `<remove>` tag 597 - required - boolean (to tag features as required or not)""" 598 # Loop over types, enums, and commands in the tag 599 # @@ It would be possible to respect 'api' and 'profile' attributes 600 # in individual features, but that is not done yet. 601 for typeElem in feature.findall('type'): 602 self.markTypeRequired(typeElem.get('name'), required) 603 for enumElem in feature.findall('enum'): 604 self.markEnumRequired(enumElem.get('name'), required) 605 606 for cmdElem in feature.findall('command'): 607 self.markCmdRequired(cmdElem.get('name'), required) 608 609 def fillFeatureDictionary(self, interface, featurename, api): 610 """Capture added interfaces for a `<version>` or `<extension>`. 611 612 - interface - Element for `<version>` or `<extension>`, containing 613 `<require>` and `<remove>` tags 614 - featurename - name of the feature 615 - api - string specifying API name being generated 616 """ 617 618 # Explicitly initialize known types - errors for unhandled categories 619 self.gen.featureDictionary[featurename] = { 620 "enumconstant": {}, 621 "command": {}, 622 "enum": {}, 623 "struct": {}, 624 "handle": {}, 625 "basetype": {}, 626 "include": {}, 627 "define": {}, 628 "bitmask": {}, 629 "union": {}, 630 "funcpointer": {}, 631 } 632 633 def requireFeatures(self, interface, featurename, api): 634 """Process `<require>` tags for a `<version>` or `<extension>`. 635 636 - interface - Element for `<version>` or `<extension>`, containing 637 `<require>` tags 638 - featurename - name of the feature 639 - api - string specifying API name being generated 640 - profile - string specifying API profile being generated""" 641 642 # <require> marks things that are required by this version/profile 643 for feature in interface.findall('require'): 644 if matchAPIProfile(api, feature): 645 self.markRequired(featurename, feature, True) 646 647 def generateFeature(self, fname, ftype, dictionary, explicit=False): 648 """Generate a single type / enum group / enum / command, 649 and all its dependencies as needed. 650 651 - fname - name of feature (`<type>`/`<enum>`/`<command>`) 652 - ftype - type of feature, 'type' | 'enum' | 'command' 653 - dictionary - of *Info objects - self.{type|enum|cmd}dict 654 - explicit - True if this is explicitly required by the top-level 655 XML <require> tag, False if it is a dependency of an explicit 656 requirement.""" 657 658 f = self.lookupElementInfo(fname, dictionary) 659 if f is None: 660 return 661 662 if not f.required: 663 return 664 665 # If feature is not required, or has already been declared, return 666 if f.declared: 667 return 668 # Always mark feature declared, as though actually emitted 669 f.declared = True 670 671 # Determine if this is an alias, and of what, if so 672 alias = f.elem.get('alias') 673 # Pull in dependent declaration(s) of the feature. 674 # For types, there may be one type in the 'requires' attribute of 675 # the element, one in the 'alias' attribute, and many in 676 # embedded <type> and <enum> tags within the element. 677 # For commands, there may be many in <type> tags within the element. 678 # For enums, no dependencies are allowed (though perhaps if you 679 # have a uint64 enum, it should require that type). 680 genProc = None 681 followupFeature = None 682 if ftype == 'type': 683 genProc = self.gen.genType 684 685 # Generate type dependencies in 'alias' and 'requires' attributes 686 if alias: 687 self.generateFeature(alias, 'type', self.typedict) 688 requires = f.elem.get('requires') 689 if requires: 690 self.generateFeature(requires, 'type', self.typedict) 691 692 # Generate types used in defining this type (e.g. in nested 693 # <type> tags) 694 # Look for <type> in entire <command> tree, 695 # not just immediate children 696 for subtype in f.elem.findall('.//type'): 697 self.generateFeature(subtype.text, 'type', self.typedict) 698 699 # Generate enums used in defining this type, for example in 700 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 701 for subtype in f.elem.findall('.//enum'): 702 self.generateFeature(subtype.text, 'enum', self.enumdict) 703 704 # If the type is an enum group, look up the corresponding 705 # group in the group dictionary and generate that instead. 706 if f.elem.get('category') == 'enum': 707 group = self.lookupElementInfo(fname, self.groupdict) 708 if alias is not None: 709 # Now, pass the *aliased* GroupInfo to the genGroup, but 710 # with an additional parameter which is the alias name. 711 genProc = self.gen.genGroup 712 f = self.lookupElementInfo(alias, self.groupdict) 713 elif group is None: 714 return 715 else: 716 genProc = self.gen.genGroup 717 f = group 718 719 # @ The enum group is not ready for generation. At this 720 # @ point, it contains all <enum> tags injected by 721 # @ <extension> tags without any verification of whether 722 # @ they are required or not. It may also contain 723 # @ duplicates injected by multiple consistent 724 # @ definitions of an <enum>. 725 726 # @ Pass over each enum, marking its enumdict[] entry as 727 # @ required or not. Mark aliases of enums as required, 728 # @ too. 729 730 enums = group.elem.findall('enum') 731 # Check for required enums, including aliases 732 # LATER - Check for, report, and remove duplicates? 733 enumAliases = [] 734 for elem in enums: 735 name = elem.get('name') 736 737 required = False 738 739 extname = elem.get('extname') 740 version = elem.get('version') 741 if extname is not None: 742 # 'supported' attribute was injected when the <enum> element was 743 # moved into the <enums> group in Registry.parseTree() 744 supported_list = elem.get('supported').split(",") 745 if 'vulkan' in supported_list: 746 required = True 747 elif re.match(self.genOpts.addExtensions, extname) is not None: 748 required = True 749 elif version is not None: 750 required = re.match(self.genOpts.emitversions, version) is not None 751 else: 752 required = True 753 754 if required: 755 # Mark this element as required (in the element, not the EnumInfo) 756 elem.set('required', 'true') 757 # If it is an alias, track that for later use 758 enumAlias = elem.get('alias') 759 if enumAlias: 760 enumAliases.append(enumAlias) 761 for elem in enums: 762 name = elem.get('name') 763 if name in enumAliases: 764 elem.set('required', 'true') 765 if f is None: 766 raise RuntimeError("Should not get here") 767 if f.elem.get('category') == 'bitmask': 768 followupFeature = f.elem.get('bitvalues') 769 elif ftype == 'command': 770 # Generate command dependencies in 'alias' attribute 771 if alias: 772 self.generateFeature(alias, 'command', self.cmddict) 773 774 genProc = self.gen.genCmd 775 for type_elem in f.elem.findall('.//type'): 776 depname = type_elem.text 777 self.generateFeature(depname, 'type', self.typedict) 778 elif ftype == 'enum': 779 # Generate enum dependencies in 'alias' attribute 780 if alias: 781 self.generateFeature(alias, 'enum', self.enumdict) 782 genProc = self.gen.genEnum 783 784 # Actually generate the type only if emitting declarations 785 if self.emitFeatures: 786 if genProc is None: 787 raise RuntimeError("genProc is None when we should be emitting") 788 genProc(f, fname, alias) 789 790 if followupFeature: 791 self.generateFeature(followupFeature, "type", self.typedict) 792 793 def generateRequiredInterface(self, interface): 794 """Generate all interfaces required by an API version or extension. 795 796 - interface - Element for `<version>` or `<extension>`""" 797 798 # Loop over all features inside all <require> tags. 799 for features in interface.findall('require'): 800 for t in features.findall('type'): 801 self.generateFeature(t.get('name'), 'type', self.typedict, explicit=True) 802 for e in features.findall('enum'): 803 # If this is an enum extending an enumerated type, do not 804 # generate it - this has already been done in reg.parseTree, 805 # by copying this element into the enumerated type. 806 enumextends = e.get('extends') 807 if not enumextends: 808 self.generateFeature(e.get('name'), 'enum', self.enumdict, explicit=True) 809 for c in features.findall('command'): 810 self.generateFeature(c.get('name'), 'command', self.cmddict, explicit=True) 811 812 def apiGen(self): 813 """Generate interface for specified versions using the current 814 generator and generator options""" 815 816 # Could reset required/declared flags for all features here. 817 # This has been removed as never used. The initial motivation was 818 # the idea of calling apiGen() repeatedly for different targets, but 819 # this has never been done. The 20% or so build-time speedup that 820 # might result is not worth the effort to make it actually work. 821 # 822 # self.apiReset() 823 824 # Compile regexps used to select versions & extensions 825 regVersions = re.compile(self.genOpts.versions) 826 regEmitVersions = re.compile(self.genOpts.emitversions) 827 regAddExtensions = re.compile(self.genOpts.addExtensions) 828 regEmitExtensions = re.compile(self.genOpts.emitExtensions) 829 830 # Get all matching API feature names & add to list of FeatureInfo 831 # Note we used to select on feature version attributes, not names. 832 features = [] 833 apiMatch = False 834 for key in self.apidict: 835 fi = self.apidict[key] 836 api = fi.elem.get('api') 837 if apiNameMatch('vulkan', api): 838 apiMatch = True 839 if regVersions.match(fi.name): 840 # Matches API & version #s being generated. Mark for 841 # emission and add to the features[] list . 842 # @@ Could use 'declared' instead of 'emit'? 843 fi.emit = (regEmitVersions.match(fi.name) is not None) 844 features.append(fi) 845 846 # Get all matching extensions, in order by their extension number, 847 # and add to the list of features. 848 # Start with extensions whose 'supported' attributes match the API 849 # being generated. Add extensions matching the pattern specified in 850 # regExtensions, then remove extensions matching the pattern 851 # specified in regRemoveExtensions 852 for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'): 853 extName = ei.name 854 include = False 855 856 # Include extension if defaultExtensions is not None and is 857 # exactly matched by the 'supported' attribute. 858 if apiNameMatch('vulkan', ei.elem.get('supported')): 859 include = True 860 861 # Include additional extensions if the extension name matches 862 # the regexp specified in the generator options. This allows 863 # forcing extensions into an interface even if they are not 864 # tagged appropriately in the registry. 865 # However, we still respect the 'supported' attribute. 866 if regAddExtensions.match(extName) is not None: 867 if not apiNameMatch('vulkan', ei.elem.get('supported')): 868 include = False 869 else: 870 include = True 871 # If the extension is to be included, add it to the 872 # extension features list. 873 if include: 874 ei.emit = (regEmitExtensions.match(extName) is not None) 875 features.append(ei) 876 877 # Sort the features list, if a sort procedure is defined 878 if self.genOpts.sortProcedure: 879 self.genOpts.sortProcedure(features) 880 881 # Passes 1+2: loop over requested API versions and extensions tagging 882 # types/commands/features as required (in an <require> block) or no 883 # longer required (in an <remove> block). <remove>s are processed 884 # after all <require>s, so removals win. 885 # If a profile other than 'None' is being generated, it must 886 # match the profile attribute (if any) of the <require> and 887 # <remove> tags. 888 for f in features: 889 self.fillFeatureDictionary(f.elem, f.name, 'vulkan') 890 self.requireFeatures(f.elem, f.name, 'vulkan') 891 892 # @@May need to strip <spirvcapability> / <spirvextension> <enable> 893 # tags of these forms: 894 # <enable version="VK_API_VERSION_1_0"/> 895 # <enable struct="VkPhysicalDeviceFeatures" feature="geometryShader" requires="VK_VERSION_1_0"/> 896 # <enable extension="VK_KHR_shader_draw_parameters"/> 897 # <enable property="VkPhysicalDeviceVulkan12Properties" member="shaderDenormPreserveFloat16" value="VK_TRUE" requires="VK_VERSION_1_2,VK_KHR_shader_float_controls"/> 898 899 # Pass 3: loop over specified API versions and extensions printing 900 # declarations for required things which have not already been 901 # generated. 902 self.gen.beginFile(self.genOpts) 903 for f in features: 904 emit = self.emitFeatures = f.emit 905 # Generate the interface (or just tag its elements as having been 906 # emitted, if they have not been). 907 self.gen.beginFeature(f.elem, emit) 908 self.generateRequiredInterface(f.elem) 909 self.gen.endFeature() 910 self.gen.endFile() 911