xref: /aosp_15_r20/external/mesa3d/src/gfxstream/codegen/scripts/reg.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
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