1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2021 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7"""Types and classes for manipulating an API registry.""" 8 9import copy 10import re 11import sys 12import xml.etree.ElementTree as etree 13from collections import defaultdict, deque, namedtuple 14from generator import OutputGenerator, GeneratorOptions, write 15from apiconventions import APIConventions 16 17def apiNameMatch(str, supported): 18 """Return whether a required api name matches a pattern specified for an 19 XML <feature> 'api' attribute or <extension> 'supported' attribute. 20 21 - str - API name such as 'vulkan' or 'openxr'. May be None, in which 22 case it never matches (this should not happen). 23 - supported - comma-separated list of XML API names. May be None, in 24 which case str always matches (this is the usual case).""" 25 26 if str is not None: 27 return supported is None or str in supported.split(',') 28 29 # Fallthrough case - either str is None or the test failed 30 return False 31 32def matchAPIProfile(api, profile, elem): 33 """Return whether an API and profile 34 being generated matches an element's profile 35 36 - api - string naming the API to match 37 - profile - string naming the profile to match 38 - elem - Element which (may) have 'api' and 'profile' 39 attributes to match to. 40 41 If a tag is not present in the Element, the corresponding API 42 or profile always matches. 43 44 Otherwise, the tag must exactly match the API or profile. 45 46 Thus, if 'profile' = core: 47 48 - `<remove>` with no attribute will match 49 - `<remove profile="core">` will match 50 - `<remove profile="compatibility">` will not match 51 52 Possible match conditions: 53 54 ``` 55 Requested Element 56 Profile Profile 57 --------- -------- 58 None None Always matches 59 'string' None Always matches 60 None 'string' Does not match. Cannot generate multiple APIs 61 or profiles, so if an API/profile constraint 62 is present, it must be asked for explicitly. 63 'string' 'string' Strings must match 64 ``` 65 66 ** In the future, we will allow regexes for the attributes, 67 not just strings, so that `api="^(gl|gles2)"` will match. Even 68 this is not really quite enough, we might prefer something 69 like `"gl(core)|gles1(common-lite)"`.""" 70 # Match 'api', if present 71 elem_api = elem.get('api') 72 if elem_api: 73 if api is None: 74 raise UserWarning("No API requested, but 'api' attribute is present with value '" 75 + elem_api + "'") 76 elif api != elem_api: 77 # Requested API does not match attribute 78 return False 79 elem_profile = elem.get('profile') 80 if elem_profile: 81 if profile is None: 82 raise UserWarning("No profile requested, but 'profile' attribute is present with value '" 83 + elem_profile + "'") 84 elif profile != elem_profile: 85 # Requested profile does not match attribute 86 return False 87 return True 88 89 90def stripNonmatchingAPIs(tree, apiName, actuallyDelete = True): 91 """Remove tree Elements with 'api' attributes matching apiName. 92 93 tree - Element at the root of the hierarchy to strip. Only its 94 children can actually be removed, not the tree itself. 95 apiName - string which much match a command-separated component of 96 the 'api' attribute. 97 actuallyDelete - only delete matching elements if True.""" 98 99 stack = deque() 100 stack.append(tree) 101 102 while len(stack) > 0: 103 parent = stack.pop() 104 105 for child in parent.findall('*'): 106 api = child.get('api') 107 if api: 108 if not apiNameMatch(apiName, api): 109 if actuallyDelete: 110 parent.remove(child) 111 else: 112 # Add child to the queue 113 stack.append(child) 114 115 116class BaseInfo: 117 """Base class for information about a registry feature 118 (type/group/enum/command/API/extension). 119 120 Represents the state of a registry feature, used during API generation. 121 """ 122 123 def __init__(self, elem): 124 self.required = False 125 """should this feature be defined during header generation 126 (has it been removed by a profile or version)?""" 127 128 self.declared = False 129 "has this feature been defined already?" 130 131 self.elem = elem 132 "etree Element for this feature" 133 134 def resetState(self): 135 """Reset required/declared to initial values. Used 136 prior to generating a new API interface.""" 137 self.required = False 138 self.declared = False 139 140 def compareKeys(self, info, key, required = False): 141 """Return True if self.elem and info.elem have the same attribute 142 value for key. 143 If 'required' is not True, also returns True if neither element 144 has an attribute value for key.""" 145 146 if required and key not in self.elem.keys(): 147 return False 148 return self.elem.get(key) == info.elem.get(key) 149 150 def compareElem(self, info, infoName): 151 """Return True if self.elem and info.elem have the same definition. 152 info - the other object 153 infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 154 'extension'""" 155 156 if infoName == 'enum': 157 if self.compareKeys(info, 'extends'): 158 # Either both extend the same type, or no type 159 if (self.compareKeys(info, 'value', required = True) or 160 self.compareKeys(info, 'bitpos', required = True)): 161 # If both specify the same value or bit position, 162 # they are equal 163 return True 164 elif (self.compareKeys(info, 'extnumber') and 165 self.compareKeys(info, 'offset') and 166 self.compareKeys(info, 'dir')): 167 # If both specify the same relative offset, they are equal 168 return True 169 elif (self.compareKeys(info, 'alias')): 170 # If both are aliases of the same value 171 return True 172 else: 173 return False 174 else: 175 # The same enum cannot extend two different types 176 return False 177 else: 178 # Non-<enum>s should never be redefined 179 return False 180 181 182class TypeInfo(BaseInfo): 183 """Registry information about a type. No additional state 184 beyond BaseInfo is required.""" 185 186 def __init__(self, elem): 187 BaseInfo.__init__(self, elem) 188 self.additionalValidity = [] 189 self.removedValidity = [] 190 191 def getMembers(self): 192 """Get a collection of all member elements for this type, if any.""" 193 return self.elem.findall('member') 194 195 def resetState(self): 196 BaseInfo.resetState(self) 197 self.additionalValidity = [] 198 self.removedValidity = [] 199 200 201class GroupInfo(BaseInfo): 202 """Registry information about a group of related enums 203 in an <enums> block, generally corresponding to a C "enum" type.""" 204 205 def __init__(self, elem): 206 BaseInfo.__init__(self, elem) 207 208 209class EnumInfo(BaseInfo): 210 """Registry information about an enum""" 211 212 def __init__(self, elem): 213 BaseInfo.__init__(self, elem) 214 self.type = elem.get('type') 215 """numeric type of the value of the <enum> tag 216 ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 )""" 217 if self.type is None: 218 self.type = '' 219 220 221class CmdInfo(BaseInfo): 222 """Registry information about a command""" 223 224 def __init__(self, elem): 225 BaseInfo.__init__(self, elem) 226 self.additionalValidity = [] 227 self.removedValidity = [] 228 229 def getParams(self): 230 """Get a collection of all param elements for this command, if any.""" 231 return self.elem.findall('param') 232 233 def resetState(self): 234 BaseInfo.resetState(self) 235 self.additionalValidity = [] 236 self.removedValidity = [] 237 238 239class FeatureInfo(BaseInfo): 240 """Registry information about an API <feature> 241 or <extension>.""" 242 243 def __init__(self, elem): 244 BaseInfo.__init__(self, elem) 245 self.name = elem.get('name') 246 "feature name string (e.g. 'VK_KHR_surface')" 247 248 self.emit = False 249 "has this feature been defined already?" 250 251 self.sortorder = int(elem.get('sortorder', 0)) 252 """explicit numeric sort key within feature and extension groups. 253 Defaults to 0.""" 254 255 # Determine element category (vendor). Only works 256 # for <extension> elements. 257 if elem.tag == 'feature': 258 # Element category (vendor) is meaningless for <feature> 259 self.category = 'VERSION' 260 """category, e.g. VERSION or khr/vendor tag""" 261 262 self.version = elem.get('name') 263 """feature name string""" 264 265 self.versionNumber = elem.get('number') 266 """versionNumber - API version number, taken from the 'number' 267 attribute of <feature>. Extensions do not have API version 268 numbers and are assigned number 0.""" 269 270 self.number = "0" 271 self.supported = None 272 else: 273 # Extract vendor portion of <APIprefix>_<vendor>_<name> 274 self.category = self.name.split('_', 2)[1] 275 self.version = "0" 276 self.versionNumber = "0" 277 self.number = elem.get('number') 278 """extension number, used for ordering and for assigning 279 enumerant offsets. <feature> features do not have extension 280 numbers and are assigned number 0.""" 281 282 # If there is no 'number' attribute, use 0, so sorting works 283 if self.number is None: 284 self.number = 0 285 self.supported = elem.get('supported') 286 287class SpirvInfo(BaseInfo): 288 """Registry information about an API <spirvextensions> 289 or <spirvcapability>.""" 290 291 def __init__(self, elem): 292 BaseInfo.__init__(self, elem) 293 294class FormatInfo(BaseInfo): 295 """Registry information about an API <format>.""" 296 297 def __init__(self, elem): 298 BaseInfo.__init__(self, elem) 299 300class Registry: 301 """Object representing an API registry, loaded from an XML file.""" 302 303 def __init__(self, gen=None, genOpts=None): 304 if gen is None: 305 # If not specified, give a default object so messaging will work 306 self.gen = OutputGenerator() 307 else: 308 self.gen = gen 309 "Output generator used to write headers / messages" 310 311 if genOpts is None: 312 # If no generator is provided, we may still need the XML API name 313 # (for example, in genRef.py). 314 self.genOpts = GeneratorOptions(apiname = APIConventions().xml_api_name) 315 else: 316 self.genOpts = genOpts 317 "Options controlling features to write and how to format them" 318 319 self.gen.registry = self 320 self.gen.genOpts = self.genOpts 321 self.gen.genOpts.registry = self 322 323 self.tree = None 324 "ElementTree containing the root `<registry>`" 325 326 self.typedict = {} 327 "dictionary of TypeInfo objects keyed by type name" 328 329 self.groupdict = {} 330 "dictionary of GroupInfo objects keyed by group name" 331 332 self.enumdict = {} 333 "dictionary of EnumInfo objects keyed by enum name" 334 335 self.cmddict = {} 336 "dictionary of CmdInfo objects keyed by command name" 337 338 self.apidict = {} 339 "dictionary of FeatureInfo objects for `<feature>` elements keyed by API name" 340 341 self.extensions = [] 342 "list of `<extension>` Elements" 343 344 self.extdict = {} 345 "dictionary of FeatureInfo objects for `<extension>` elements keyed by extension name" 346 347 self.spirvextdict = {} 348 "dictionary of FeatureInfo objects for `<spirvextension>` elements keyed by spirv extension name" 349 350 self.spirvcapdict = {} 351 "dictionary of FeatureInfo objects for `<spirvcapability>` elements keyed by spirv capability name" 352 353 self.formatsdict = {} 354 "dictionary of FeatureInfo objects for `<format>` elements keyed by VkFormat name" 355 356 self.emitFeatures = False 357 """True to actually emit features for a version / extension, 358 or False to just treat them as emitted""" 359 360 self.breakPat = None 361 "regexp pattern to break on when generating names" 362 # self.breakPat = re.compile('VkFenceImportFlagBits.*') 363 364 self.requiredextensions = [] # Hack - can remove it after validity generator goes away 365 366 # ** Global types for automatic source generation ** 367 # Length Member data 368 self.commandextensiontuple = namedtuple('commandextensiontuple', 369 ['command', # The name of the command being modified 370 'value', # The value to append to the command 371 'extension']) # The name of the extension that added it 372 self.validextensionstructs = defaultdict(list) 373 self.commandextensionsuccesses = [] 374 self.commandextensionerrors = [] 375 376 self.filename = None 377 378 def loadElementTree(self, tree): 379 """Load ElementTree into a Registry object and parse it.""" 380 self.tree = tree 381 self.parseTree() 382 383 def loadFile(self, file): 384 """Load an API registry XML file into a Registry object and parse it""" 385 self.filename = file 386 self.tree = etree.parse(file) 387 self.parseTree() 388 389 def setGenerator(self, gen): 390 """Specify output generator object. 391 392 `None` restores the default generator.""" 393 self.gen = gen 394 self.gen.setRegistry(self) 395 396 def addElementInfo(self, elem, info, infoName, dictionary): 397 """Add information about an element to the corresponding dictionary. 398 399 Intended for internal use only. 400 401 - elem - `<type>`/`<enums>`/`<enum>`/`<command>`/`<feature>`/`<extension>`/`<spirvextension>`/`<spirvcapability>`/`<format>` Element 402 - info - corresponding {Type|Group|Enum|Cmd|Feature|Spirv}Info object 403 - infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' / 'spirvextension' / 'spirvcapability' / 'format' 404 - dictionary - self.{type|group|enum|cmd|api|ext|format|spirvext|spirvcap}dict 405 406 If the Element has an 'api' attribute, the dictionary key is the 407 tuple (name,api). If not, the key is the name. 'name' is an 408 attribute of the Element""" 409 # self.gen.logMsg('diag', 'Adding ElementInfo.required =', 410 # info.required, 'name =', elem.get('name')) 411 api = elem.get('api') 412 if api: 413 key = (elem.get('name'), api) 414 else: 415 key = elem.get('name') 416 if key in dictionary: 417 if not dictionary[key].compareElem(info, infoName): 418 self.gen.logMsg('warn', 'Attempt to redefine', key, 419 '(this should not happen)') 420 else: 421 True 422 else: 423 dictionary[key] = info 424 425 def lookupElementInfo(self, fname, dictionary): 426 """Find a {Type|Enum|Cmd}Info object by name. 427 428 Intended for internal use only. 429 430 If an object qualified by API name exists, use that. 431 432 - fname - name of type / enum / command 433 - dictionary - self.{type|enum|cmd}dict""" 434 key = (fname, self.genOpts.apiname) 435 if key in dictionary: 436 # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) 437 return dictionary[key] 438 if fname in dictionary: 439 # self.gen.logMsg('diag', 'Found generic element for feature', fname) 440 return dictionary[fname] 441 442 return None 443 444 def breakOnName(self, regexp): 445 """Specify a feature name regexp to break on when generating features.""" 446 self.breakPat = re.compile(regexp) 447 448 def parseTree(self): 449 """Parse the registry Element, once created""" 450 # This must be the Element for the root <registry> 451 self.reg = self.tree.getroot() 452 453 # Preprocess the tree by removing all elements with non-matching 454 # 'api' attributes by breadth-first tree traversal. 455 # This is a blunt hammer, but eliminates the need to track and test 456 # the apis deeper in processing to select the correct elements and 457 # avoid duplicates. 458 # Schema validation should prevent duplicate elements with 459 # overlapping api attributes, or where one element has an api 460 # attribute and the other does not. 461 462 stripNonmatchingAPIs(self.reg, self.genOpts.apiname) 463 464 # Create dictionary of registry types from toplevel <types> tags 465 # and add 'name' attribute to each <type> tag (where missing) 466 # based on its <name> element. 467 # 468 # There is usually one <types> block; more are OK 469 # Required <type> attributes: 'name' or nested <name> tag contents 470 self.typedict = {} 471 for type_elem in self.reg.findall('types/type'): 472 # If the <type> does not already have a 'name' attribute, set 473 # it from contents of its <name> tag. 474 if type_elem.get('name') is None: 475 type_elem.set('name', type_elem.find('name').text) 476 self.addElementInfo(type_elem, TypeInfo(type_elem), 'type', self.typedict) 477 478 # Create dictionary of registry enum groups from <enums> tags. 479 # 480 # Required <enums> attributes: 'name'. If no name is given, one is 481 # generated, but that group cannot be identified and turned into an 482 # enum type definition - it is just a container for <enum> tags. 483 self.groupdict = {} 484 for group in self.reg.findall('enums'): 485 self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) 486 487 # Create dictionary of registry enums from <enum> tags 488 # 489 # <enums> tags usually define different namespaces for the values 490 # defined in those tags, but the actual names all share the 491 # same dictionary. 492 # Required <enum> attributes: 'name', 'value' 493 # For containing <enums> which have type="enum" or type="bitmask", 494 # tag all contained <enum>s are required. This is a stopgap until 495 # a better scheme for tagging core and extension enums is created. 496 self.enumdict = {} 497 for enums in self.reg.findall('enums'): 498 required = (enums.get('type') is not None) 499 for enum in enums.findall('enum'): 500 enumInfo = EnumInfo(enum) 501 enumInfo.required = required 502 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 503 504 # Create dictionary of registry commands from <command> tags 505 # and add 'name' attribute to each <command> tag (where missing) 506 # based on its <proto><name> element. 507 # 508 # There is usually only one <commands> block; more are OK. 509 # Required <command> attributes: 'name' or <proto><name> tag contents 510 self.cmddict = {} 511 # List of commands which alias others. Contains 512 # [ aliasName, element ] 513 # for each alias 514 cmdAlias = [] 515 for cmd in self.reg.findall('commands/command'): 516 # If the <command> does not already have a 'name' attribute, set 517 # it from contents of its <proto><name> tag. 518 name = cmd.get('name') 519 if name is None: 520 name = cmd.set('name', cmd.find('proto/name').text) 521 ci = CmdInfo(cmd) 522 self.addElementInfo(cmd, ci, 'command', self.cmddict) 523 alias = cmd.get('alias') 524 if alias: 525 cmdAlias.append([name, alias, cmd]) 526 527 # Now loop over aliases, injecting a copy of the aliased command's 528 # Element with the aliased prototype name replaced with the command 529 # name - if it exists. 530 for (name, alias, cmd) in cmdAlias: 531 if alias in self.cmddict: 532 aliasInfo = self.cmddict[alias] 533 cmdElem = copy.deepcopy(aliasInfo.elem) 534 cmdElem.find('proto/name').text = name 535 cmdElem.set('name', name) 536 cmdElem.set('alias', alias) 537 ci = CmdInfo(cmdElem) 538 # Replace the dictionary entry for the CmdInfo element 539 self.cmddict[name] = ci 540 541 # @ newString = etree.tostring(base, encoding="unicode").replace(aliasValue, aliasName) 542 # @elem.append(etree.fromstring(replacement)) 543 else: 544 self.gen.logMsg('warn', 'No matching <command> found for command', 545 cmd.get('name'), 'alias', alias) 546 547 # Create dictionaries of API and extension interfaces 548 # from toplevel <api> and <extension> tags. 549 self.apidict = {} 550 for feature in self.reg.findall('feature'): 551 featureInfo = FeatureInfo(feature) 552 self.addElementInfo(feature, featureInfo, 'feature', self.apidict) 553 554 # Add additional enums defined only in <feature> tags 555 # to the corresponding enumerated type. 556 # When seen here, the <enum> element, processed to contain the 557 # numeric enum value, is added to the corresponding <enums> 558 # element, as well as adding to the enum dictionary. It is no 559 # longer removed from the <require> element it is introduced in. 560 # Instead, generateRequiredInterface ignores <enum> elements 561 # that extend enumerated types. 562 # 563 # For <enum> tags which are actually just constants, if there is 564 # no 'extends' tag but there is a 'value' or 'bitpos' tag, just 565 # add an EnumInfo record to the dictionary. That works because 566 # output generation of constants is purely dependency-based, and 567 # does not need to iterate through the XML tags. 568 for elem in feature.findall('require'): 569 for enum in elem.findall('enum'): 570 addEnumInfo = False 571 groupName = enum.get('extends') 572 if groupName is not None: 573 # self.gen.logMsg('diag', 'Found extension enum', 574 # enum.get('name')) 575 # Add version number attribute to the <enum> element 576 enum.set('version', featureInfo.version) 577 # Look up the GroupInfo with matching groupName 578 if groupName in self.groupdict: 579 # self.gen.logMsg('diag', 'Matching group', 580 # groupName, 'found, adding element...') 581 gi = self.groupdict[groupName] 582 gi.elem.append(copy.deepcopy(enum)) 583 else: 584 self.gen.logMsg('warn', 'NO matching group', 585 groupName, 'for enum', enum.get('name'), 'found.') 586 addEnumInfo = True 587 elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): 588 # self.gen.logMsg('diag', 'Adding extension constant "enum"', 589 # enum.get('name')) 590 addEnumInfo = True 591 if addEnumInfo: 592 enumInfo = EnumInfo(enum) 593 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 594 595 self.extensions = self.reg.findall('extensions/extension') 596 self.extdict = {} 597 for feature in self.extensions: 598 featureInfo = FeatureInfo(feature) 599 self.addElementInfo(feature, featureInfo, 'extension', self.extdict) 600 601 # Add additional enums defined only in <extension> tags 602 # to the corresponding core type. 603 # Algorithm matches that of enums in a "feature" tag as above. 604 # 605 # This code also adds a 'extnumber' attribute containing the 606 # extension number, used for enumerant value calculation. 607 for elem in feature.findall('require'): 608 for enum in elem.findall('enum'): 609 addEnumInfo = False 610 groupName = enum.get('extends') 611 if groupName is not None: 612 # self.gen.logMsg('diag', 'Found extension enum', 613 # enum.get('name')) 614 615 # Add <extension> block's extension number attribute to 616 # the <enum> element unless specified explicitly, such 617 # as when redefining an enum in another extension. 618 extnumber = enum.get('extnumber') 619 if not extnumber: 620 enum.set('extnumber', featureInfo.number) 621 622 enum.set('extname', featureInfo.name) 623 enum.set('supported', featureInfo.supported) 624 # Look up the GroupInfo with matching groupName 625 if groupName in self.groupdict: 626 # self.gen.logMsg('diag', 'Matching group', 627 # groupName, 'found, adding element...') 628 gi = self.groupdict[groupName] 629 gi.elem.append(copy.deepcopy(enum)) 630 else: 631 self.gen.logMsg('warn', 'NO matching group', 632 groupName, 'for enum', enum.get('name'), 'found.') 633 addEnumInfo = True 634 elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): 635 # self.gen.logMsg('diag', 'Adding extension constant "enum"', 636 # enum.get('name')) 637 addEnumInfo = True 638 if addEnumInfo: 639 enumInfo = EnumInfo(enum) 640 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 641 642 # Construct a "validextensionstructs" list for parent structures 643 # based on "structextends" tags in child structures 644 disabled_types = [] 645 for disabled_ext in self.reg.findall('extensions/extension[@supported="disabled"]'): 646 for type_elem in disabled_ext.findall("*/type"): 647 disabled_types.append(type_elem.get('name')) 648 for type_elem in self.reg.findall('types/type'): 649 if type_elem.get('name') not in disabled_types: 650 parentStructs = type_elem.get('structextends') 651 if parentStructs is not None: 652 for parent in parentStructs.split(','): 653 # self.gen.logMsg('diag', type.get('name'), 'extends', parent) 654 self.validextensionstructs[parent].append(type_elem.get('name')) 655 # Sort the lists so they do not depend on the XML order 656 for parent in self.validextensionstructs: 657 self.validextensionstructs[parent].sort() 658 659 # Parse out all spirv tags in dictionaries 660 # Use addElementInfo to catch duplicates 661 for spirv in self.reg.findall('spirvextensions/spirvextension'): 662 spirvInfo = SpirvInfo(spirv) 663 self.addElementInfo(spirv, spirvInfo, 'spirvextension', self.spirvextdict) 664 for spirv in self.reg.findall('spirvcapabilities/spirvcapability'): 665 spirvInfo = SpirvInfo(spirv) 666 self.addElementInfo(spirv, spirvInfo, 'spirvcapability', self.spirvcapdict) 667 668 for format in self.reg.findall('formats/format'): 669 formatInfo = FormatInfo(format) 670 self.addElementInfo(format, formatInfo, 'format', self.formatsdict) 671 672 def dumpReg(self, maxlen=120, filehandle=sys.stdout): 673 """Dump all the dictionaries constructed from the Registry object. 674 675 Diagnostic to dump the dictionaries to specified file handle (default stdout). 676 Truncates type / enum / command elements to maxlen characters (default 120)""" 677 write('***************************************', file=filehandle) 678 write(' ** Dumping Registry contents **', file=filehandle) 679 write('***************************************', file=filehandle) 680 write('// Types', file=filehandle) 681 for name in self.typedict: 682 tobj = self.typedict[name] 683 write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) 684 write('// Groups', file=filehandle) 685 for name in self.groupdict: 686 gobj = self.groupdict[name] 687 write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) 688 write('// Enums', file=filehandle) 689 for name in self.enumdict: 690 eobj = self.enumdict[name] 691 write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) 692 write('// Commands', file=filehandle) 693 for name in self.cmddict: 694 cobj = self.cmddict[name] 695 write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) 696 write('// APIs', file=filehandle) 697 for key in self.apidict: 698 write(' API Version ', key, '->', 699 etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) 700 write('// Extensions', file=filehandle) 701 for key in self.extdict: 702 write(' Extension', key, '->', 703 etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) 704 write('// SPIR-V', file=filehandle) 705 for key in self.spirvextdict: 706 write(' SPIR-V Extension', key, '->', 707 etree.tostring(self.spirvextdict[key].elem)[0:maxlen], file=filehandle) 708 for key in self.spirvcapdict: 709 write(' SPIR-V Capability', key, '->', 710 etree.tostring(self.spirvcapdict[key].elem)[0:maxlen], file=filehandle) 711 write('// VkFormat', file=filehandle) 712 for key in self.formatsdict: 713 write(' VkFormat', key, '->', 714 etree.tostring(self.formatsdict[key].elem)[0:maxlen], file=filehandle) 715 716 def markTypeRequired(self, typename, required): 717 """Require (along with its dependencies) or remove (but not its dependencies) a type. 718 719 - typename - name of type 720 - required - boolean (to tag features as required or not) 721 """ 722 self.gen.logMsg('diag', 'tagging type:', typename, '-> required =', required) 723 # Get TypeInfo object for <type> tag corresponding to typename 724 typeinfo = self.lookupElementInfo(typename, self.typedict) 725 if typeinfo is not None: 726 if required: 727 # Tag type dependencies in 'alias' and 'required' attributes as 728 # required. This does not un-tag dependencies in a <remove> 729 # tag. See comments in markRequired() below for the reason. 730 for attrib_name in ['requires', 'alias']: 731 depname = typeinfo.elem.get(attrib_name) 732 if depname: 733 self.gen.logMsg('diag', 'Generating dependent type', 734 depname, 'for', attrib_name, 'type', typename) 735 # Do not recurse on self-referential structures. 736 if typename != depname: 737 self.markTypeRequired(depname, required) 738 else: 739 self.gen.logMsg('diag', 'type', typename, 'is self-referential') 740 # Tag types used in defining this type (e.g. in nested 741 # <type> tags) 742 # Look for <type> in entire <command> tree, 743 # not just immediate children 744 for subtype in typeinfo.elem.findall('.//type'): 745 self.gen.logMsg('diag', 'markRequired: type requires dependent <type>', subtype.text) 746 if typename != subtype.text: 747 self.markTypeRequired(subtype.text, required) 748 else: 749 self.gen.logMsg('diag', 'type', typename, 'is self-referential') 750 # Tag enums used in defining this type, for example in 751 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 752 for subenum in typeinfo.elem.findall('.//enum'): 753 self.gen.logMsg('diag', 'markRequired: type requires dependent <enum>', subenum.text) 754 self.markEnumRequired(subenum.text, required) 755 # Tag type dependency in 'bitvalues' attributes as 756 # required. This ensures that the bit values for a flag 757 # are emitted 758 depType = typeinfo.elem.get('bitvalues') 759 if depType: 760 self.gen.logMsg('diag', 'Generating bitflag type', 761 depType, 'for type', typename) 762 self.markTypeRequired(depType, required) 763 group = self.lookupElementInfo(depType, self.groupdict) 764 if group is not None: 765 group.flagType = typeinfo 766 767 typeinfo.required = required 768 elif '.h' not in typename: 769 self.gen.logMsg('warn', 'type:', typename, 'IS NOT DEFINED') 770 771 def markEnumRequired(self, enumname, required): 772 """Mark an enum as required or not. 773 774 - enumname - name of enum 775 - required - boolean (to tag features as required or not)""" 776 777 self.gen.logMsg('diag', 'tagging enum:', enumname, '-> required =', required) 778 enum = self.lookupElementInfo(enumname, self.enumdict) 779 if enum is not None: 780 # If the enum is part of a group, and is being removed, then 781 # look it up in that <group> tag and remove it there, so that it 782 # is not visible to generators (which traverse the <group> tag 783 # elements themselves). 784 # This is not the most robust way of doing this, since a removed 785 # enum that is later required again will no longer have a group 786 # element, but it makes the change non-intrusive on generator 787 # code. 788 if required is False: 789 groupName = enum.elem.get('extends') 790 if groupName is not None: 791 # Look up the Info with matching groupName 792 if groupName in self.groupdict: 793 gi = self.groupdict[groupName] 794 gienum = gi.elem.find("enum[@name='" + enumname + "']") 795 if gienum is not None: 796 # Remove copy of this enum from the group 797 gi.elem.remove(gienum) 798 else: 799 self.gen.logMsg('warn', 'Cannot remove enum', 800 enumname, 'not found in group', 801 groupName) 802 else: 803 self.gen.logMsg('warn', 'Cannot remove enum', 804 enumname, 'from nonexistent group', 805 groupName) 806 807 enum.required = required 808 # Tag enum dependencies in 'alias' attribute as required 809 depname = enum.elem.get('alias') 810 if depname: 811 self.gen.logMsg('diag', 'Generating dependent enum', 812 depname, 'for alias', enumname, 'required =', enum.required) 813 self.markEnumRequired(depname, required) 814 else: 815 self.gen.logMsg('warn', 'enum:', enumname, 'IS NOT DEFINED') 816 817 def markCmdRequired(self, cmdname, required): 818 """Mark a command as required or not. 819 820 - cmdname - name of command 821 - required - boolean (to tag features as required or not)""" 822 self.gen.logMsg('diag', 'tagging command:', cmdname, '-> required =', required) 823 cmd = self.lookupElementInfo(cmdname, self.cmddict) 824 if cmd is not None: 825 cmd.required = required 826 827 # Tag command dependencies in 'alias' attribute as required 828 # 829 # This is usually not done, because command 'aliases' are not 830 # actual C language aliases like type and enum aliases. Instead 831 # they are just duplicates of the function signature of the 832 # alias. This means that there is no dependency of a command 833 # alias on what it aliases. One exception is validity includes, 834 # where the spec markup needs the promoted-to validity include 835 # even if only the promoted-from command is being built. 836 if self.genOpts.requireCommandAliases: 837 depname = cmd.elem.get('alias') 838 if depname: 839 self.gen.logMsg('diag', 'Generating dependent command', 840 depname, 'for alias', cmdname) 841 self.markCmdRequired(depname, required) 842 843 # Tag all parameter types of this command as required. 844 # This DOES NOT remove types of commands in a <remove> 845 # tag, because many other commands may use the same type. 846 # We could be more clever and reference count types, 847 # instead of using a boolean. 848 if required: 849 # Look for <type> in entire <command> tree, 850 # not just immediate children 851 for type_elem in cmd.elem.findall('.//type'): 852 self.gen.logMsg('diag', 'markRequired: command implicitly requires dependent type', type_elem.text) 853 self.markTypeRequired(type_elem.text, required) 854 else: 855 self.gen.logMsg('warn', 'command:', cmdname, 'IS NOT DEFINED') 856 857 def markRequired(self, featurename, feature, required): 858 """Require or remove features specified in the Element. 859 860 - featurename - name of the feature 861 - feature - Element for `<require>` or `<remove>` tag 862 - required - boolean (to tag features as required or not)""" 863 self.gen.logMsg('diag', 'markRequired (feature = <too long to print>, required =', required, ')') 864 865 # Loop over types, enums, and commands in the tag 866 # @@ It would be possible to respect 'api' and 'profile' attributes 867 # in individual features, but that is not done yet. 868 for typeElem in feature.findall('type'): 869 self.markTypeRequired(typeElem.get('name'), required) 870 for enumElem in feature.findall('enum'): 871 self.markEnumRequired(enumElem.get('name'), required) 872 for cmdElem in feature.findall('command'): 873 self.markCmdRequired(cmdElem.get('name'), required) 874 875 # Extensions may need to extend existing commands or other items in the future. 876 # So, look for extend tags. 877 for extendElem in feature.findall('extend'): 878 extendType = extendElem.get('type') 879 if extendType == 'command': 880 commandName = extendElem.get('name') 881 successExtends = extendElem.get('successcodes') 882 if successExtends is not None: 883 for success in successExtends.split(','): 884 self.commandextensionsuccesses.append(self.commandextensiontuple(command=commandName, 885 value=success, 886 extension=featurename)) 887 errorExtends = extendElem.get('errorcodes') 888 if errorExtends is not None: 889 for error in errorExtends.split(','): 890 self.commandextensionerrors.append(self.commandextensiontuple(command=commandName, 891 value=error, 892 extension=featurename)) 893 else: 894 self.gen.logMsg('warn', 'extend type:', extendType, 'IS NOT SUPPORTED') 895 896 def getAlias(self, elem, dict): 897 """Check for an alias in the same require block. 898 899 - elem - Element to check for an alias""" 900 901 # Try to find an alias 902 alias = elem.get('alias') 903 if alias is None: 904 name = elem.get('name') 905 typeinfo = self.lookupElementInfo(name, dict) 906 alias = typeinfo.elem.get('alias') 907 908 return alias 909 910 def checkForCorrectionAliases(self, alias, require, tag): 911 """Check for an alias in the same require block. 912 913 - alias - String name of the alias 914 - require - `<require>` block from the registry 915 - tag - tag to look for in the require block""" 916 917 # For the time being, the code below is bypassed. It has the effect 918 # of excluding "spelling aliases" created to comply with the style 919 # guide, but this leaves references out of the specification and 920 # causes broken internal links. 921 # 922 # if alias and require.findall(tag + "[@name='" + alias + "']"): 923 # return True 924 925 return False 926 927 def fillFeatureDictionary(self, interface, featurename, api, profile): 928 """Capture added interfaces for a `<version>` or `<extension>`. 929 930 - interface - Element for `<version>` or `<extension>`, containing 931 `<require>` and `<remove>` tags 932 - featurename - name of the feature 933 - api - string specifying API name being generated 934 - profile - string specifying API profile being generated""" 935 936 # Explicitly initialize known types - errors for unhandled categories 937 self.gen.featureDictionary[featurename] = { 938 "enumconstant": {}, 939 "command": {}, 940 "enum": {}, 941 "struct": {}, 942 "handle": {}, 943 "basetype": {}, 944 "include": {}, 945 "define": {}, 946 "bitmask": {}, 947 "union": {}, 948 "funcpointer": {}, 949 } 950 951 # <require> marks things that are required by this version/profile 952 for require in interface.findall('require'): 953 if matchAPIProfile(api, profile, require): 954 955 # Determine the required extension or version needed for a require block 956 # Assumes that only one of these is specified 957 required_key = require.get('feature') 958 if required_key is None: 959 required_key = require.get('extension') 960 961 # Loop over types, enums, and commands in the tag 962 for typeElem in require.findall('type'): 963 typename = typeElem.get('name') 964 typeinfo = self.lookupElementInfo(typename, self.typedict) 965 966 if typeinfo: 967 # Remove aliases in the same extension/feature; these are always added as a correction. Do not need the original to be visible. 968 alias = self.getAlias(typeElem, self.typedict) 969 if not self.checkForCorrectionAliases(alias, require, 'type'): 970 # Resolve the type info to the actual type, so we get an accurate read for 'structextends' 971 while alias: 972 typeinfo = self.lookupElementInfo(alias, self.typedict) 973 alias = typeinfo.elem.get('alias') 974 975 typecat = typeinfo.elem.get('category') 976 typeextends = typeinfo.elem.get('structextends') 977 if not required_key in self.gen.featureDictionary[featurename][typecat]: 978 self.gen.featureDictionary[featurename][typecat][required_key] = {} 979 if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]: 980 self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = [] 981 self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename) 982 else: 983 self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) 984 985 986 for enumElem in require.findall('enum'): 987 enumname = enumElem.get('name') 988 typeinfo = self.lookupElementInfo(enumname, self.enumdict) 989 990 # Remove aliases in the same extension/feature; these are always added as a correction. Do not need the original to be visible. 991 alias = self.getAlias(enumElem, self.enumdict) 992 if not self.checkForCorrectionAliases(alias, require, 'enum'): 993 enumextends = enumElem.get('extends') 994 if not required_key in self.gen.featureDictionary[featurename]['enumconstant']: 995 self.gen.featureDictionary[featurename]['enumconstant'][required_key] = {} 996 if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]: 997 self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = [] 998 self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname) 999 else: 1000 self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) 1001 1002 for cmdElem in require.findall('command'): 1003 # Remove aliases in the same extension/feature; these are always added as a correction. Do not need the original to be visible. 1004 alias = self.getAlias(cmdElem, self.cmddict) 1005 if not self.checkForCorrectionAliases(alias, require, 'command'): 1006 if not required_key in self.gen.featureDictionary[featurename]['command']: 1007 self.gen.featureDictionary[featurename]['command'][required_key] = [] 1008 self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name')) 1009 else: 1010 self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) 1011 1012 def requireAndRemoveFeatures(self, interface, featurename, api, profile): 1013 """Process `<require>` and `<remove>` tags for a `<version>` or `<extension>`. 1014 1015 - interface - Element for `<version>` or `<extension>`, containing 1016 `<require>` and `<remove>` tags 1017 - featurename - name of the feature 1018 - api - string specifying API name being generated 1019 - profile - string specifying API profile being generated""" 1020 1021 # <require> marks things that are required by this version/profile 1022 for feature in interface.findall('require'): 1023 if matchAPIProfile(api, profile, feature): 1024 self.markRequired(featurename, feature, True) 1025 1026 # <remove> marks things that are removed by this version/profile 1027 for feature in interface.findall('remove'): 1028 if matchAPIProfile(api, profile, feature): 1029 self.markRequired(featurename, feature, False) 1030 1031 def assignAdditionalValidity(self, interface, api, profile): 1032 # Loop over all usage inside all <require> tags. 1033 for feature in interface.findall('require'): 1034 if matchAPIProfile(api, profile, feature): 1035 for v in feature.findall('usage'): 1036 if v.get('command'): 1037 self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v)) 1038 if v.get('struct'): 1039 self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v)) 1040 1041 # Loop over all usage inside all <remove> tags. 1042 for feature in interface.findall('remove'): 1043 if matchAPIProfile(api, profile, feature): 1044 for v in feature.findall('usage'): 1045 if v.get('command'): 1046 self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v)) 1047 if v.get('struct'): 1048 self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v)) 1049 1050 def generateFeature(self, fname, ftype, dictionary): 1051 """Generate a single type / enum group / enum / command, 1052 and all its dependencies as needed. 1053 1054 - fname - name of feature (`<type>`/`<enum>`/`<command>`) 1055 - ftype - type of feature, 'type' | 'enum' | 'command' 1056 - dictionary - of *Info objects - self.{type|enum|cmd}dict""" 1057 1058 self.gen.logMsg('diag', 'generateFeature: generating', ftype, fname) 1059 f = self.lookupElementInfo(fname, dictionary) 1060 if f is None: 1061 # No such feature. This is an error, but reported earlier 1062 self.gen.logMsg('diag', 'No entry found for feature', fname, 1063 'returning!') 1064 return 1065 1066 # If feature is not required, or has already been declared, return 1067 if not f.required: 1068 self.gen.logMsg('diag', 'Skipping', ftype, fname, '(not required)') 1069 return 1070 if f.declared: 1071 self.gen.logMsg('diag', 'Skipping', ftype, fname, '(already declared)') 1072 return 1073 # Always mark feature declared, as though actually emitted 1074 f.declared = True 1075 1076 # Determine if this is an alias, and of what, if so 1077 alias = f.elem.get('alias') 1078 if alias: 1079 self.gen.logMsg('diag', fname, 'is an alias of', alias) 1080 1081 # Pull in dependent declaration(s) of the feature. 1082 # For types, there may be one type in the 'requires' attribute of 1083 # the element, one in the 'alias' attribute, and many in 1084 # embedded <type> and <enum> tags within the element. 1085 # For commands, there may be many in <type> tags within the element. 1086 # For enums, no dependencies are allowed (though perhaps if you 1087 # have a uint64 enum, it should require that type). 1088 genProc = None 1089 followupFeature = None 1090 if ftype == 'type': 1091 genProc = self.gen.genType 1092 1093 # Generate type dependencies in 'alias' and 'requires' attributes 1094 if alias: 1095 self.generateFeature(alias, 'type', self.typedict) 1096 requires = f.elem.get('requires') 1097 if requires: 1098 self.gen.logMsg('diag', 'Generating required dependent type', 1099 requires) 1100 self.generateFeature(requires, 'type', self.typedict) 1101 1102 # Generate types used in defining this type (e.g. in nested 1103 # <type> tags) 1104 # Look for <type> in entire <command> tree, 1105 # not just immediate children 1106 for subtype in f.elem.findall('.//type'): 1107 self.gen.logMsg('diag', 'Generating required dependent <type>', 1108 subtype.text) 1109 self.generateFeature(subtype.text, 'type', self.typedict) 1110 1111 # Generate enums used in defining this type, for example in 1112 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 1113 for subtype in f.elem.findall('.//enum'): 1114 self.gen.logMsg('diag', 'Generating required dependent <enum>', 1115 subtype.text) 1116 self.generateFeature(subtype.text, 'enum', self.enumdict) 1117 1118 # If the type is an enum group, look up the corresponding 1119 # group in the group dictionary and generate that instead. 1120 if f.elem.get('category') == 'enum': 1121 self.gen.logMsg('diag', 'Type', fname, 'is an enum group, so generate that instead') 1122 group = self.lookupElementInfo(fname, self.groupdict) 1123 if alias is not None: 1124 # An alias of another group name. 1125 # Pass to genGroup with 'alias' parameter = aliased name 1126 self.gen.logMsg('diag', 'Generating alias', fname, 1127 'for enumerated type', alias) 1128 # Now, pass the *aliased* GroupInfo to the genGroup, but 1129 # with an additional parameter which is the alias name. 1130 genProc = self.gen.genGroup 1131 f = self.lookupElementInfo(alias, self.groupdict) 1132 elif group is None: 1133 self.gen.logMsg('warn', 'Skipping enum type', fname, 1134 ': No matching enumerant group') 1135 return 1136 else: 1137 genProc = self.gen.genGroup 1138 f = group 1139 1140 # @ The enum group is not ready for generation. At this 1141 # @ point, it contains all <enum> tags injected by 1142 # @ <extension> tags without any verification of whether 1143 # @ they are required or not. It may also contain 1144 # @ duplicates injected by multiple consistent 1145 # @ definitions of an <enum>. 1146 1147 # @ Pass over each enum, marking its enumdict[] entry as 1148 # @ required or not. Mark aliases of enums as required, 1149 # @ too. 1150 1151 enums = group.elem.findall('enum') 1152 1153 self.gen.logMsg('diag', 'generateFeature: checking enums for group', fname) 1154 1155 # Check for required enums, including aliases 1156 # LATER - Check for, report, and remove duplicates? 1157 enumAliases = [] 1158 for elem in enums: 1159 name = elem.get('name') 1160 1161 required = False 1162 1163 extname = elem.get('extname') 1164 version = elem.get('version') 1165 if extname is not None: 1166 # 'supported' attribute was injected when the <enum> element was 1167 # moved into the <enums> group in Registry.parseTree() 1168 if self.genOpts.defaultExtensions == elem.get('supported'): 1169 required = True 1170 elif re.match(self.genOpts.addExtensions, extname) is not None: 1171 required = True 1172 elif version is not None: 1173 required = re.match(self.genOpts.emitversions, version) is not None 1174 else: 1175 required = True 1176 1177 self.gen.logMsg('diag', '* required =', required, 'for', name) 1178 if required: 1179 # Mark this element as required (in the element, not the EnumInfo) 1180 elem.set('required', 'true') 1181 # If it is an alias, track that for later use 1182 enumAlias = elem.get('alias') 1183 if enumAlias: 1184 enumAliases.append(enumAlias) 1185 for elem in enums: 1186 name = elem.get('name') 1187 if name in enumAliases: 1188 elem.set('required', 'true') 1189 self.gen.logMsg('diag', '* also need to require alias', name) 1190 if f.elem.get('category') == 'bitmask': 1191 followupFeature = f.elem.get('bitvalues') 1192 elif ftype == 'command': 1193 # Generate command dependencies in 'alias' attribute 1194 if alias: 1195 self.generateFeature(alias, 'command', self.cmddict) 1196 1197 genProc = self.gen.genCmd 1198 for type_elem in f.elem.findall('.//type'): 1199 depname = type_elem.text 1200 self.gen.logMsg('diag', 'Generating required parameter type', 1201 depname) 1202 self.generateFeature(depname, 'type', self.typedict) 1203 elif ftype == 'enum': 1204 # Generate enum dependencies in 'alias' attribute 1205 if alias: 1206 self.generateFeature(alias, 'enum', self.enumdict) 1207 genProc = self.gen.genEnum 1208 1209 # Actually generate the type only if emitting declarations 1210 if self.emitFeatures: 1211 self.gen.logMsg('diag', 'Emitting', ftype, 'decl for', fname) 1212 genProc(f, fname, alias) 1213 else: 1214 self.gen.logMsg('diag', 'Skipping', ftype, fname, 1215 '(should not be emitted)') 1216 1217 if followupFeature: 1218 self.gen.logMsg('diag', 'Generating required bitvalues <enum>', 1219 followupFeature) 1220 self.generateFeature(followupFeature, "type", self.typedict) 1221 1222 def generateRequiredInterface(self, interface): 1223 """Generate all interfaces required by an API version or extension. 1224 1225 - interface - Element for `<version>` or `<extension>`""" 1226 1227 # Loop over all features inside all <require> tags. 1228 for features in interface.findall('require'): 1229 for t in features.findall('type'): 1230 self.generateFeature(t.get('name'), 'type', self.typedict) 1231 for e in features.findall('enum'): 1232 # If this is an enum extending an enumerated type, do not 1233 # generate it - this has already been done in reg.parseTree, 1234 # by copying this element into the enumerated type. 1235 enumextends = e.get('extends') 1236 if not enumextends: 1237 self.generateFeature(e.get('name'), 'enum', self.enumdict) 1238 for c in features.findall('command'): 1239 self.generateFeature(c.get('name'), 'command', self.cmddict) 1240 1241 def generateSpirv(self, spirv, dictionary): 1242 if spirv is None: 1243 self.gen.logMsg('diag', 'No entry found for element', name, 1244 'returning!') 1245 return 1246 1247 name = spirv.elem.get('name') 1248 # No known alias for spirv elements 1249 alias = None 1250 if spirv.emit: 1251 genProc = self.gen.genSpirv 1252 genProc(spirv, name, alias) 1253 1254 def stripUnsupportedAPIs(self, dictionary, attribute, supportedDictionary): 1255 """Strip unsupported APIs from attributes of APIs. 1256 dictionary - *Info dictionary of APIs to be updated 1257 attribute - attribute name to look for in each API 1258 supportedDictionary - dictionary in which to look for supported 1259 API elements in the attribute""" 1260 1261 for key in dictionary: 1262 eleminfo = dictionary[key] 1263 attribstring = eleminfo.elem.get(attribute) 1264 if attribstring is not None: 1265 apis = [] 1266 stripped = False 1267 for api in attribstring.split(','): 1268 ##print('Checking API {} referenced by {}'.format(api, key)) 1269 if supportedDictionary[api].required: 1270 apis.append(api) 1271 else: 1272 stripped = True 1273 ##print('\t**STRIPPING API {} from {}'.format(api, key)) 1274 1275 # Update the attribute after stripping stuff. 1276 # Could sort apis before joining, but it is not a clear win 1277 if stripped: 1278 eleminfo.elem.set(attribute, ','.join(apis)) 1279 1280 def generateFormat(self, format, dictionary): 1281 if format is None: 1282 self.gen.logMsg('diag', 'No entry found for format element', 1283 'returning!') 1284 return 1285 1286 name = format.elem.get('name') 1287 # No known alias for VkFormat elements 1288 alias = None 1289 if format.emit: 1290 genProc = self.gen.genFormat 1291 genProc(format, name, alias) 1292 1293 def apiGen(self): 1294 """Generate interface for specified versions using the current 1295 generator and generator options""" 1296 1297 self.gen.logMsg('diag', '*******************************************') 1298 self.gen.logMsg('diag', ' Registry.apiGen file:', self.genOpts.filename, 1299 'api:', self.genOpts.apiname, 1300 'profile:', self.genOpts.profile) 1301 self.gen.logMsg('diag', '*******************************************') 1302 1303 # Could reset required/declared flags for all features here. 1304 # This has been removed as never used. The initial motivation was 1305 # the idea of calling apiGen() repeatedly for different targets, but 1306 # this has never been done. The 20% or so build-time speedup that 1307 # might result is not worth the effort to make it actually work. 1308 # 1309 # self.apiReset() 1310 1311 # Compile regexps used to select versions & extensions 1312 regVersions = re.compile(self.genOpts.versions) 1313 regEmitVersions = re.compile(self.genOpts.emitversions) 1314 regAddExtensions = re.compile(self.genOpts.addExtensions) 1315 regRemoveExtensions = re.compile(self.genOpts.removeExtensions) 1316 regEmitExtensions = re.compile(self.genOpts.emitExtensions) 1317 regEmitSpirv = re.compile(self.genOpts.emitSpirv) 1318 regEmitFormats = re.compile(self.genOpts.emitFormats) 1319 1320 # Get all matching API feature names & add to list of FeatureInfo 1321 # Note we used to select on feature version attributes, not names. 1322 features = [] 1323 apiMatch = False 1324 for key in self.apidict: 1325 fi = self.apidict[key] 1326 api = fi.elem.get('api') 1327 if apiNameMatch(self.genOpts.apiname, api): 1328 apiMatch = True 1329 if regVersions.match(fi.name): 1330 # Matches API & version #s being generated. Mark for 1331 # emission and add to the features[] list . 1332 # @@ Could use 'declared' instead of 'emit'? 1333 fi.emit = (regEmitVersions.match(fi.name) is not None) 1334 features.append(fi) 1335 if not fi.emit: 1336 self.gen.logMsg('diag', 'NOT tagging feature api =', api, 1337 'name =', fi.name, 'version =', fi.version, 1338 'for emission (does not match emitversions pattern)') 1339 else: 1340 self.gen.logMsg('diag', 'Including feature api =', api, 1341 'name =', fi.name, 'version =', fi.version, 1342 'for emission (matches emitversions pattern)') 1343 else: 1344 self.gen.logMsg('diag', 'NOT including feature api =', api, 1345 'name =', fi.name, 'version =', fi.version, 1346 '(does not match requested versions)') 1347 else: 1348 self.gen.logMsg('diag', 'NOT including feature api =', api, 1349 'name =', fi.name, 1350 '(does not match requested API)') 1351 if not apiMatch: 1352 self.gen.logMsg('warn', 'No matching API versions found!') 1353 1354 # Get all matching extensions, in order by their extension number, 1355 # and add to the list of features. 1356 # Start with extensions tagged with 'api' pattern matching the API 1357 # being generated. Add extensions matching the pattern specified in 1358 # regExtensions, then remove extensions matching the pattern 1359 # specified in regRemoveExtensions 1360 for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'): 1361 extName = ei.name 1362 include = False 1363 1364 # Include extension if defaultExtensions is not None and is 1365 # exactly matched by the 'supported' attribute. 1366 if apiNameMatch(self.genOpts.defaultExtensions, 1367 ei.elem.get('supported')): 1368 self.gen.logMsg('diag', 'Including extension', 1369 extName, "(defaultExtensions matches the 'supported' attribute)") 1370 include = True 1371 1372 # Include additional extensions if the extension name matches 1373 # the regexp specified in the generator options. This allows 1374 # forcing extensions into an interface even if they are not 1375 # tagged appropriately in the registry. 1376 # However we still respect the 'supported' attribute. 1377 if regAddExtensions.match(extName) is not None: 1378 if not apiNameMatch(self.genOpts.apiname, ei.elem.get('supported')): 1379 self.gen.logMsg('diag', 'NOT including extension', 1380 extName, '(matches explicitly requested, but does not match the \'supported\' attribute)') 1381 include = False 1382 else: 1383 self.gen.logMsg('diag', 'Including extension', 1384 extName, '(matches explicitly requested extensions to add)') 1385 include = True 1386 # Remove extensions if the name matches the regexp specified 1387 # in generator options. This allows forcing removal of 1388 # extensions from an interface even if they are tagged that 1389 # way in the registry. 1390 if regRemoveExtensions.match(extName) is not None: 1391 self.gen.logMsg('diag', 'Removing extension', 1392 extName, '(matches explicitly requested extensions to remove)') 1393 include = False 1394 1395 # If the extension is to be included, add it to the 1396 # extension features list. 1397 if include: 1398 ei.emit = (regEmitExtensions.match(extName) is not None) 1399 features.append(ei) 1400 if not ei.emit: 1401 self.gen.logMsg('diag', 'NOT tagging extension', 1402 extName, 1403 'for emission (does not match emitextensions pattern)') 1404 1405 # Hack - can be removed when validity generator goes away 1406 # (Jon) I am not sure what this does, or if it should 1407 # respect the ei.emit flag above. 1408 self.requiredextensions.append(extName) 1409 else: 1410 self.gen.logMsg('diag', 'NOT including extension', 1411 extName, '(does not match api attribute or explicitly requested extensions)') 1412 1413 # Add all spirv elements to list 1414 # generators decide to emit them all or not 1415 # Currently no filtering as no client of these elements needs filtering 1416 spirvexts = [] 1417 for key in self.spirvextdict: 1418 si = self.spirvextdict[key] 1419 si.emit = (regEmitSpirv.match(key) is not None) 1420 spirvexts.append(si) 1421 spirvcaps = [] 1422 for key in self.spirvcapdict: 1423 si = self.spirvcapdict[key] 1424 si.emit = (regEmitSpirv.match(key) is not None) 1425 spirvcaps.append(si) 1426 1427 formats = [] 1428 for key in self.formatsdict: 1429 si = self.formatsdict[key] 1430 si.emit = (regEmitFormats.match(key) is not None) 1431 formats.append(si) 1432 1433 # Sort the features list, if a sort procedure is defined 1434 if self.genOpts.sortProcedure: 1435 self.genOpts.sortProcedure(features) 1436 # print('sortProcedure ->', [f.name for f in features]) 1437 1438 # Pass 1: loop over requested API versions and extensions tagging 1439 # types/commands/features as required (in an <require> block) or no 1440 # longer required (in an <remove> block). It is possible to remove 1441 # a feature in one version and restore it later by requiring it in 1442 # a later version. 1443 # If a profile other than 'None' is being generated, it must 1444 # match the profile attribute (if any) of the <require> and 1445 # <remove> tags. 1446 self.gen.logMsg('diag', 'PASS 1: TAG FEATURES') 1447 for f in features: 1448 self.gen.logMsg('diag', 'PASS 1: Tagging required and removed features for', 1449 f.name) 1450 self.fillFeatureDictionary(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) 1451 self.requireAndRemoveFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) 1452 self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile) 1453 1454 # Now, strip references to APIs that are not required. 1455 # At present such references may occur in: 1456 # Structs in <type category="struct"> 'structextends' attributes 1457 # Enums in <command> 'successcodes' and 'errorcodes' attributes 1458 self.stripUnsupportedAPIs(self.typedict, 'structextends', self.typedict) 1459 self.stripUnsupportedAPIs(self.cmddict, 'successcodes', self.enumdict) 1460 self.stripUnsupportedAPIs(self.cmddict, 'errorcodes', self.enumdict) 1461 1462 # @@May need to strip <spirvcapability> / <spirvextension> <enable> 1463 # tags of these forms: 1464 # <enable version="VK_API_VERSION_1_0"/> 1465 # <enable struct="VkPhysicalDeviceFeatures" feature="geometryShader" requires="VK_VERSION_1_0"/> 1466 # <enable extension="VK_KHR_shader_draw_parameters"/> 1467 # <enable property="VkPhysicalDeviceVulkan12Properties" member="shaderDenormPreserveFloat16" value="VK_TRUE" requires="VK_VERSION_1_2,VK_KHR_shader_float_controls"/> 1468 1469 # Pass 2: loop over specified API versions and extensions printing 1470 # declarations for required things which have not already been 1471 # generated. 1472 self.gen.logMsg('diag', 'PASS 2: GENERATE INTERFACES FOR FEATURES') 1473 self.gen.beginFile(self.genOpts) 1474 for f in features: 1475 self.gen.logMsg('diag', 'PASS 2: Generating interface for', 1476 f.name) 1477 emit = self.emitFeatures = f.emit 1478 if not emit: 1479 self.gen.logMsg('diag', 'PASS 2: NOT declaring feature', 1480 f.elem.get('name'), 'because it is not tagged for emission') 1481 # Generate the interface (or just tag its elements as having been 1482 # emitted, if they have not been). 1483 self.gen.beginFeature(f.elem, emit) 1484 self.generateRequiredInterface(f.elem) 1485 self.gen.endFeature() 1486 # Generate spirv elements 1487 for s in spirvexts: 1488 self.generateSpirv(s, self.spirvextdict) 1489 for s in spirvcaps: 1490 self.generateSpirv(s, self.spirvcapdict) 1491 for s in formats: 1492 self.generateFormat(s, self.formatsdict) 1493 self.gen.endFile() 1494 1495 def apiReset(self): 1496 """Reset type/enum/command dictionaries before generating another API. 1497 1498 Use between apiGen() calls to reset internal state.""" 1499 for datatype in self.typedict: 1500 self.typedict[datatype].resetState() 1501 for enum in self.enumdict: 1502 self.enumdict[enum].resetState() 1503 for cmd in self.cmddict: 1504 self.cmddict[cmd].resetState() 1505 for cmd in self.apidict: 1506 self.apidict[cmd].resetState() 1507 1508 def __validateStructLimittypes(self, struct): 1509 """Validate 'limittype' attributes for a single struct.""" 1510 limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid']) 1511 badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[])) 1512 validLimittypes = { 'min', 'max', 'bitmask', 'range', 'struct', 'noauto' } 1513 for member in struct.getMembers(): 1514 memberName = member.findtext('name') 1515 if memberName in ['sType', 'pNext']: 1516 continue 1517 limittype = member.get('limittype') 1518 if not limittype: 1519 badFields[struct.elem.get('name')].missing.append(memberName) 1520 elif limittype == 'struct': 1521 typeName = member.findtext('type') 1522 memberType = self.typedict[typeName] 1523 badFields.update(self.__validateStructLimittypes(memberType)) 1524 elif limittype not in validLimittypes: 1525 badFields[struct.elem.get('name')].invalid.append(memberName) 1526 return badFields 1527 1528 def __validateLimittype(self): 1529 """Validate 'limittype' attributes.""" 1530 badFields = self.__validateStructLimittypes(self.typedict['VkPhysicalDeviceProperties2']) 1531 for featStructName in self.validextensionstructs['VkPhysicalDeviceProperties2']: 1532 featStruct = self.typedict[featStructName] 1533 badFields.update(self.__validateStructLimittypes(featStruct)) 1534 1535 if badFields: 1536 self.gen.logMsg('diag', 'SUMMARY OF FIELDS WITH INCORRECT LIMITTYPES') 1537 for key in sorted(badFields.keys()): 1538 diags = badFields[key] 1539 if diags.missing: 1540 self.gen.logMsg('diag', ' ', key, 'missing limittype:', ', '.join(badFields[key].missing)) 1541 if diags.invalid: 1542 self.gen.logMsg('diag', ' ', key, 'invalid limittype:', ', '.join(badFields[key].invalid)) 1543 return False 1544 return True 1545 1546 def validateRegistry(self): 1547 """Validate properties of the registry.""" 1548 return self.__validateLimittype() 1549