From c10d78e61e9c50faf2c433d3954601780fd0a486 Mon Sep 17 00:00:00 2001 From: Gurchetan Singh Date: Mon, 6 Nov 2023 10:19:32 -0800 Subject: [PATCH] gfxstream: import Vulkan-docs from 463f8c This is based on 463f8c ("Change log for October 20, 2023 Vulkan 1.3.269 spec update"). Right now, the current version of vulkan-docs seems to be 2021. Need to update. Import here without modifications so we can track the changes that were needed to support cereal. Reviewed-by: Aaron Ruby Acked-by: Yonggang Luo Acked-by: Adam Jackson Part-of: --- src/gfxstream/codegen/scripts/cgenerator.py | 513 ++++++ src/gfxstream/codegen/scripts/generator.py | 1389 +++++++++++++++ src/gfxstream/codegen/scripts/genvk.py | 1117 ++++++++++++ src/gfxstream/codegen/scripts/reg.py | 1779 +++++++++++++++++++ 4 files changed, 4798 insertions(+) create mode 100644 src/gfxstream/codegen/scripts/cgenerator.py create mode 100644 src/gfxstream/codegen/scripts/generator.py create mode 100755 src/gfxstream/codegen/scripts/genvk.py create mode 100644 src/gfxstream/codegen/scripts/reg.py diff --git a/src/gfxstream/codegen/scripts/cgenerator.py b/src/gfxstream/codegen/scripts/cgenerator.py new file mode 100644 index 00000000000..ef8d68137c0 --- /dev/null +++ b/src/gfxstream/codegen/scripts/cgenerator.py @@ -0,0 +1,513 @@ +#!/usr/bin/python3 -i +# +# Copyright 2013-2023 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import re + +from generator import (GeneratorOptions, + MissingGeneratorOptionsConventionsError, + MissingGeneratorOptionsError, MissingRegistryError, + OutputGenerator, noneStr, regSortFeatures, write) + +class CGeneratorOptions(GeneratorOptions): + """CGeneratorOptions - subclass of GeneratorOptions. + + Adds options used by COutputGenerator objects during C language header + generation.""" + + def __init__(self, + prefixText='', + genFuncPointers=True, + protectFile=True, + protectFeature=True, + protectProto=None, + protectProtoStr=None, + protectExtensionProto=None, + protectExtensionProtoStr=None, + apicall='', + apientry='', + apientryp='', + indentFuncProto=True, + indentFuncPointer=False, + alignFuncParam=0, + genEnumBeginEndRange=False, + genAliasMacro=False, + genStructExtendsComment=False, + aliasMacro='', + misracstyle=False, + misracppstyle=False, + **kwargs + ): + """Constructor. + Additional parameters beyond parent class: + + - prefixText - list of strings to prefix generated header with + (usually a copyright statement + calling convention macros) + - protectFile - True if multiple inclusion protection should be + generated (based on the filename) around the entire header + - protectFeature - True if #ifndef..#endif protection should be + generated around a feature interface in the header file + - genFuncPointers - True if function pointer typedefs should be + generated + - protectProto - If conditional protection should be generated + around prototype declarations, set to either '#ifdef' + to require opt-in (#ifdef protectProtoStr) or '#ifndef' + to require opt-out (#ifndef protectProtoStr). Otherwise + set to None. + - protectProtoStr - #ifdef/#ifndef symbol to use around prototype + declarations, if protectProto is set + - protectExtensionProto - If conditional protection should be generated + around extension prototype declarations, set to either '#ifdef' + to require opt-in (#ifdef protectExtensionProtoStr) or '#ifndef' + to require opt-out (#ifndef protectExtensionProtoStr). Otherwise + set to None + - protectExtensionProtoStr - #ifdef/#ifndef symbol to use around + extension prototype declarations, if protectExtensionProto is set + - apicall - string to use for the function declaration prefix, + such as APICALL on Windows + - apientry - string to use for the calling convention macro, + in typedefs, such as APIENTRY + - apientryp - string to use for the calling convention macro + in function pointer typedefs, such as APIENTRYP + - indentFuncProto - True if prototype declarations should put each + parameter on a separate line + - indentFuncPointer - True if typedefed function pointers should put each + parameter on a separate line + - alignFuncParam - if nonzero and parameters are being put on a + separate line, align parameter names at the specified column + - genEnumBeginEndRange - True if BEGIN_RANGE / END_RANGE macros should + be generated for enumerated types + - genAliasMacro - True if the OpenXR alias macro should be generated + for aliased types (unclear what other circumstances this is useful) + - genStructExtendsComment - True if comments showing the structures + whose pNext chain a structure extends are included before its + definition + - aliasMacro - alias macro to inject when genAliasMacro is True + - misracstyle - generate MISRA C-friendly headers + - misracppstyle - generate MISRA C++-friendly headers""" + + GeneratorOptions.__init__(self, **kwargs) + + self.prefixText = prefixText + """list of strings to prefix generated header with (usually a copyright statement + calling convention macros).""" + + self.genFuncPointers = genFuncPointers + """True if function pointer typedefs should be generated""" + + self.protectFile = protectFile + """True if multiple inclusion protection should be generated (based on the filename) around the entire header.""" + + self.protectFeature = protectFeature + """True if #ifndef..#endif protection should be generated around a feature interface in the header file.""" + + self.protectProto = protectProto + """If conditional protection should be generated around prototype declarations, set to either '#ifdef' to require opt-in (#ifdef protectProtoStr) or '#ifndef' to require opt-out (#ifndef protectProtoStr). Otherwise set to None.""" + + self.protectProtoStr = protectProtoStr + """#ifdef/#ifndef symbol to use around prototype declarations, if protectProto is set""" + + self.protectExtensionProto = protectExtensionProto + """If conditional protection should be generated around extension prototype declarations, set to either '#ifdef' to require opt-in (#ifdef protectExtensionProtoStr) or '#ifndef' to require opt-out (#ifndef protectExtensionProtoStr). Otherwise set to None.""" + + self.protectExtensionProtoStr = protectExtensionProtoStr + """#ifdef/#ifndef symbol to use around extension prototype declarations, if protectExtensionProto is set""" + + self.apicall = apicall + """string to use for the function declaration prefix, such as APICALL on Windows.""" + + self.apientry = apientry + """string to use for the calling convention macro, in typedefs, such as APIENTRY.""" + + self.apientryp = apientryp + """string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP.""" + + self.indentFuncProto = indentFuncProto + """True if prototype declarations should put each parameter on a separate line""" + + self.indentFuncPointer = indentFuncPointer + """True if typedefed function pointers should put each parameter on a separate line""" + + self.alignFuncParam = alignFuncParam + """if nonzero and parameters are being put on a separate line, align parameter names at the specified column""" + + self.genEnumBeginEndRange = genEnumBeginEndRange + """True if BEGIN_RANGE / END_RANGE macros should be generated for enumerated types""" + + self.genAliasMacro = genAliasMacro + """True if the OpenXR alias macro should be generated for aliased types (unclear what other circumstances this is useful)""" + + self.genStructExtendsComment = genStructExtendsComment + """True if comments showing the structures whose pNext chain a structure extends are included before its definition""" + + self.aliasMacro = aliasMacro + """alias macro to inject when genAliasMacro is True""" + + self.misracstyle = misracstyle + """generate MISRA C-friendly headers""" + + self.misracppstyle = misracppstyle + """generate MISRA C++-friendly headers""" + + self.codeGenerator = True + """True if this generator makes compilable code""" + + +class COutputGenerator(OutputGenerator): + """Generates C-language API interfaces.""" + + # This is an ordered list of sections in the header file. + TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', + 'group', 'bitmask', 'funcpointer', 'struct'] + ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Internal state - accumulators for different inner block text + self.sections = {section: [] for section in self.ALL_SECTIONS} + self.feature_not_empty = False + self.may_alias = None + + def beginFile(self, genOpts): + OutputGenerator.beginFile(self, genOpts) + if self.genOpts is None: + raise MissingGeneratorOptionsError() + # C-specific + # + # Multiple inclusion protection & C++ wrappers. + if self.genOpts.protectFile and self.genOpts.filename: + headerSym = re.sub(r'\.h', '_h_', + os.path.basename(self.genOpts.filename)).upper() + write('#ifndef', headerSym, file=self.outFile) + write('#define', headerSym, '1', file=self.outFile) + self.newline() + + # User-supplied prefix text, if any (list of strings) + if genOpts.prefixText: + for s in genOpts.prefixText: + write(s, file=self.outFile) + + # C++ extern wrapper - after prefix lines so they can add includes. + self.newline() + write('#ifdef __cplusplus', file=self.outFile) + write('extern "C" {', file=self.outFile) + write('#endif', file=self.outFile) + self.newline() + + def endFile(self): + # C-specific + # Finish C++ wrapper and multiple inclusion protection + if self.genOpts is None: + raise MissingGeneratorOptionsError() + self.newline() + write('#ifdef __cplusplus', file=self.outFile) + write('}', file=self.outFile) + write('#endif', file=self.outFile) + if self.genOpts.protectFile and self.genOpts.filename: + self.newline() + write('#endif', file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFile(self) + + def beginFeature(self, interface, emit): + # Start processing in superclass + OutputGenerator.beginFeature(self, interface, emit) + # C-specific + # Accumulate includes, defines, types, enums, function pointer typedefs, + # end function prototypes separately for this feature. They are only + # printed in endFeature(). + self.sections = {section: [] for section in self.ALL_SECTIONS} + self.feature_not_empty = False + + def _endProtectComment(self, protect_str, protect_directive='#ifdef'): + if protect_directive is None or protect_str is None: + raise RuntimeError('Should not call in here without something to protect') + + # Do not put comments after #endif closing blocks if this is not set + if not self.genOpts.conventions.protectProtoComment: + return '' + elif 'ifdef' in protect_directive: + return f' /* {protect_str} */' + else: + return f' /* !{protect_str} */' + + def endFeature(self): + "Actually write the interface to the output file." + # C-specific + if self.emit: + if self.feature_not_empty: + if self.genOpts is None: + raise MissingGeneratorOptionsError() + if self.genOpts.conventions is None: + raise MissingGeneratorOptionsConventionsError() + is_core = self.featureName and self.featureName.startswith(self.conventions.api_prefix + 'VERSION_') + if self.genOpts.conventions.writeFeature(self.featureExtraProtect, self.genOpts.filename): + self.newline() + if self.genOpts.protectFeature: + write('#ifndef', self.featureName, file=self.outFile) + + # If type declarations are needed by other features based on + # this one, it may be necessary to suppress the ExtraProtect, + # or move it below the 'for section...' loop. + if self.featureExtraProtect is not None: + write('#ifdef', self.featureExtraProtect, file=self.outFile) + self.newline() + + # Generate warning of possible use in IDEs + write(f'// {self.featureName} is a preprocessor guard. Do not pass it to API calls.', file=self.outFile) + write('#define', self.featureName, '1', file=self.outFile) + for section in self.TYPE_SECTIONS: + contents = self.sections[section] + if contents: + write('\n'.join(contents), file=self.outFile) + + if self.genOpts.genFuncPointers and self.sections['commandPointer']: + write('\n'.join(self.sections['commandPointer']), file=self.outFile) + self.newline() + + if self.sections['command']: + if self.genOpts.protectProto: + write(self.genOpts.protectProto, + self.genOpts.protectProtoStr, file=self.outFile) + if self.genOpts.protectExtensionProto and not is_core: + write(self.genOpts.protectExtensionProto, + self.genOpts.protectExtensionProtoStr, file=self.outFile) + write('\n'.join(self.sections['command']), end='', file=self.outFile) + if self.genOpts.protectExtensionProto and not is_core: + write('#endif' + + self._endProtectComment(protect_directive=self.genOpts.protectExtensionProto, + protect_str=self.genOpts.protectExtensionProtoStr), + file=self.outFile) + if self.genOpts.protectProto: + write('#endif' + + self._endProtectComment(protect_directive=self.genOpts.protectProto, + protect_str=self.genOpts.protectProtoStr), + file=self.outFile) + else: + self.newline() + + if self.featureExtraProtect is not None: + write('#endif' + + self._endProtectComment(protect_str=self.featureExtraProtect), + file=self.outFile) + + if self.genOpts.protectFeature: + write('#endif' + + self._endProtectComment(protect_str=self.featureName), + file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFeature(self) + + def appendSection(self, section, text): + "Append a definition to the specified section" + + if section is None: + self.logMsg('error', 'Missing section in appendSection (probably a element missing its \'category\' attribute. Text:', text) + exit(1) + + self.sections[section].append(text) + self.feature_not_empty = True + + def genType(self, typeinfo, name, alias): + "Generate type." + OutputGenerator.genType(self, typeinfo, name, alias) + typeElem = typeinfo.elem + + # Vulkan: + # Determine the category of the type, and the type section to add + # its definition to. + # 'funcpointer' is added to the 'struct' section as a workaround for + # internal issue #877, since structures and function pointer types + # can have cross-dependencies. + category = typeElem.get('category') + if category == 'funcpointer': + section = 'struct' + else: + section = category + + if category in ('struct', 'union'): + # If the type is a struct type, generate it using the + # special-purpose generator. + self.genStruct(typeinfo, name, alias) + else: + if self.genOpts is None: + raise MissingGeneratorOptionsError() + # OpenXR: this section was not under 'else:' previously, just fell through + if alias: + # If the type is an alias, just emit a typedef declaration + body = 'typedef ' + alias + ' ' + name + ';\n' + else: + # Replace tags with an APIENTRY-style string + # (from self.genOpts). Copy other text through unchanged. + # If the resulting text is an empty string, do not emit it. + body = noneStr(typeElem.text) + for elem in typeElem: + if elem.tag == 'apientry': + body += self.genOpts.apientry + noneStr(elem.tail) + else: + body += noneStr(elem.text) + noneStr(elem.tail) + if category == 'define' and self.misracppstyle(): + body = body.replace("(uint32_t)", "static_cast") + if body: + # Add extra newline after multi-line entries. + if '\n' in body[0:-1]: + body += '\n' + self.appendSection(section, body) + + def genProtectString(self, protect_str): + """Generate protection string. + + Protection strings are the strings defining the OS/Platform/Graphics + requirements for a given API command. When generating the + language header files, we need to make sure the items specific to a + graphics API or OS platform are properly wrapped in #ifs.""" + protect_if_str = '' + protect_end_str = '' + if not protect_str: + return (protect_if_str, protect_end_str) + + if ',' in protect_str: + protect_list = protect_str.split(',') + protect_defs = ('defined(%s)' % d for d in protect_list) + protect_def_str = ' && '.join(protect_defs) + protect_if_str = '#if %s\n' % protect_def_str + protect_end_str = '#endif // %s\n' % protect_def_str + else: + protect_if_str = '#ifdef %s\n' % protect_str + protect_end_str = '#endif // %s\n' % protect_str + + return (protect_if_str, protect_end_str) + + def typeMayAlias(self, typeName): + if not self.may_alias: + if self.registry is None: + raise MissingRegistryError() + # First time we have asked if a type may alias. + # So, populate the set of all names of types that may. + + # Everyone with an explicit mayalias="true" + self.may_alias = set(typeName + for typeName, data in self.registry.typedict.items() + if data.elem.get('mayalias') == 'true') + + # Every type mentioned in some other type's parentstruct attribute. + polymorphic_bases = (otherType.elem.get('parentstruct') + for otherType in self.registry.typedict.values()) + self.may_alias.update(set(x for x in polymorphic_bases + if x is not None)) + return typeName in self.may_alias + + def genStruct(self, typeinfo, typeName, alias): + """Generate struct (e.g. C "struct" type). + + This is a special case of the tag where the contents are + interpreted as a set of tags instead of freeform C + C type declarations. The tags are just like + tags - they are a declaration of a struct or union member. + Only simple member declarations are supported (no nested + structs etc.) + + If alias is not None, then this struct aliases another; just + generate a typedef of that alias.""" + OutputGenerator.genStruct(self, typeinfo, typeName, alias) + + if self.genOpts is None: + raise MissingGeneratorOptionsError() + + typeElem = typeinfo.elem + + if alias: + body = 'typedef ' + alias + ' ' + typeName + ';\n' + else: + body = '' + (protect_begin, protect_end) = self.genProtectString(typeElem.get('protect')) + if protect_begin: + body += protect_begin + + if self.genOpts.genStructExtendsComment: + structextends = typeElem.get('structextends') + body += '// ' + typeName + ' extends ' + structextends + '\n' if structextends else '' + + body += 'typedef ' + typeElem.get('category') + + # This is an OpenXR-specific alternative where aliasing refers + # to an inheritance hierarchy of types rather than C-level type + # aliases. + if self.genOpts.genAliasMacro and self.typeMayAlias(typeName): + body += ' ' + self.genOpts.aliasMacro + + body += ' ' + typeName + ' {\n' + + targetLen = self.getMaxCParamTypeLength(typeinfo) + for member in typeElem.findall('.//member'): + body += self.makeCParamDecl(member, targetLen + 4) + body += ';\n' + body += '} ' + typeName + ';\n' + if protect_end: + body += protect_end + + self.appendSection('struct', body) + + def genGroup(self, groupinfo, groupName, alias=None): + """Generate groups (e.g. C "enum" type). + + These are concatenated together with other types. + + If alias is not None, it is the name of another group type + which aliases this type; just generate that alias.""" + OutputGenerator.genGroup(self, groupinfo, groupName, alias) + groupElem = groupinfo.elem + + # After either enumerated type or alias paths, add the declaration + # to the appropriate section for the group being defined. + if groupElem.get('type') == 'bitmask': + section = 'bitmask' + else: + section = 'group' + + if alias: + # If the group name is aliased, just emit a typedef declaration + # for the alias. + body = 'typedef ' + alias + ' ' + groupName + ';\n' + self.appendSection(section, body) + else: + if self.genOpts is None: + raise MissingGeneratorOptionsError() + (section, body) = self.buildEnumCDecl(self.genOpts.genEnumBeginEndRange, groupinfo, groupName) + self.appendSection(section, '\n' + body) + + def genEnum(self, enuminfo, name, alias): + """Generate the C declaration for a constant (a single value). + + tags may specify their values in several ways, but are usually + just integers.""" + + OutputGenerator.genEnum(self, enuminfo, name, alias) + + body = self.buildConstantCDecl(enuminfo, name, alias) + self.appendSection('enum', body) + + def genCmd(self, cmdinfo, name, alias): + "Command generation" + OutputGenerator.genCmd(self, cmdinfo, name, alias) + + # if alias: + # prefix = '// ' + name + ' is an alias of command ' + alias + '\n' + # else: + # prefix = '' + if self.genOpts is None: + raise MissingGeneratorOptionsError() + + prefix = '' + decls = self.makeCDecls(cmdinfo.elem) + self.appendSection('command', prefix + decls[0] + '\n') + if self.genOpts.genFuncPointers: + self.appendSection('commandPointer', decls[1]) + + def misracstyle(self): + return self.genOpts.misracstyle; + + def misracppstyle(self): + return self.genOpts.misracppstyle; diff --git a/src/gfxstream/codegen/scripts/generator.py b/src/gfxstream/codegen/scripts/generator.py new file mode 100644 index 00000000000..669e5d689e8 --- /dev/null +++ b/src/gfxstream/codegen/scripts/generator.py @@ -0,0 +1,1389 @@ +#!/usr/bin/python3 -i +# +# Copyright 2013-2023 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 +"""Base class for source/header/doc generators, as well as some utility functions.""" + +from __future__ import unicode_literals + +import io +import os +import pdb +import re +import shutil +import sys +import tempfile +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path # type: ignore + +from spec_tools.util import getElemName, getElemType + + +def write(*args, **kwargs): + file = kwargs.pop('file', sys.stdout) + end = kwargs.pop('end', '\n') + file.write(' '.join(str(arg) for arg in args)) + file.write(end) + + +def noneStr(s): + """Return string argument, or "" if argument is None. + + Used in converting etree Elements into text. + s - string to convert""" + if s: + return s + return "" + + +def enquote(s): + """Return string argument with surrounding quotes, + for serialization into Python code.""" + if s: + if isinstance(s, str): + return f"'{s}'" + else: + return s + return None + + +def regSortCategoryKey(feature): + """Sort key for regSortFeatures. + Sorts by category of the feature name string: + + - Core API features (those defined with a `` tag) + - (sort VKSC after VK - this is Vulkan-specific) + - ARB/KHR/OES (Khronos extensions) + - other (EXT/vendor extensions)""" + + if feature.elem.tag == 'feature': + if feature.name.startswith('VKSC'): + return 0.5 + else: + return 0 + if (feature.category == 'ARB' + or feature.category == 'KHR' + or feature.category == 'OES'): + return 1 + + return 2 + + +def regSortOrderKey(feature): + """Sort key for regSortFeatures - key is the sortorder attribute.""" + + return feature.sortorder + + +def regSortNameKey(feature): + """Sort key for regSortFeatures - key is the extension name.""" + + return feature.name + + +def regSortFeatureVersionKey(feature): + """Sort key for regSortFeatures - key is the feature version. + `` elements all have version number 0.""" + + return float(feature.versionNumber) + + +def regSortExtensionNumberKey(feature): + """Sort key for regSortFeatures - key is the extension number. + `` elements all have extension number 0.""" + + return int(feature.number) + + +def regSortFeatures(featureList): + """Default sort procedure for features. + + - Sorts by explicit sort order (default 0) relative to other features + - then by feature category ('feature' or 'extension'), + - then by version number (for features) + - then by extension number (for extensions)""" + featureList.sort(key=regSortExtensionNumberKey) + featureList.sort(key=regSortFeatureVersionKey) + featureList.sort(key=regSortCategoryKey) + featureList.sort(key=regSortOrderKey) + + +class MissingGeneratorOptionsError(RuntimeError): + """Error raised when a Generator tries to do something that requires GeneratorOptions but it is None.""" + + def __init__(self, msg=None): + full_msg = 'Missing generator options object self.genOpts' + if msg: + full_msg += ': ' + msg + super().__init__(full_msg) + + +class MissingRegistryError(RuntimeError): + """Error raised when a Generator tries to do something that requires a Registry object but it is None.""" + + def __init__(self, msg=None): + full_msg = 'Missing Registry object self.registry' + if msg: + full_msg += ': ' + msg + super().__init__(full_msg) + + +class MissingGeneratorOptionsConventionsError(RuntimeError): + """Error raised when a Generator tries to do something that requires a Conventions object but it is None.""" + + def __init__(self, msg=None): + full_msg = 'Missing Conventions object self.genOpts.conventions' + if msg: + full_msg += ': ' + msg + super().__init__(full_msg) + + +class GeneratorOptions: + """Base class for options used during header/documentation production. + + These options are target language independent, and used by + Registry.apiGen() and by base OutputGenerator objects.""" + + def __init__(self, + conventions=None, + filename=None, + directory='.', + genpath=None, + apiname=None, + mergeApiNames=None, + profile=None, + versions='.*', + emitversions='.*', + defaultExtensions=None, + addExtensions=None, + removeExtensions=None, + emitExtensions=None, + emitSpirv=None, + emitFormats=None, + reparentEnums=True, + sortProcedure=regSortFeatures, + requireCommandAliases=False, + requireDepends=True, + ): + """Constructor. + + Arguments: + + - conventions - may be mandatory for some generators: + an object that implements ConventionsBase + - filename - basename of file to generate, or None to write to stdout. + - directory - directory in which to generate filename + - genpath - path to previously generated files, such as apimap.py + - apiname - string matching `` 'apiname' attribute, e.g. 'gl'. + - mergeApiNames - If not None, a comma separated list of API names + to merge into the API specified by 'apiname' + - profile - string specifying API profile , e.g. 'core', or None. + - versions - regex matching API versions to process interfaces for. + Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions. + - emitversions - regex matching API versions to actually emit + interfaces for (though all requested versions are considered + when deciding which interfaces to generate). For GL 4.3 glext.h, + this might be `'1[.][2-5]|[2-4][.][0-9]'`. + - defaultExtensions - If not None, a string which must in its + entirety match the pattern in the "supported" attribute of + the ``. Defaults to None. Usually the same as apiname. + - addExtensions - regex matching names of additional extensions + to include. Defaults to None. + - removeExtensions - regex matching names of extensions to + remove (after defaultExtensions and addExtensions). Defaults + to None. + - emitExtensions - regex matching names of extensions to actually emit + interfaces for (though all requested versions are considered when + deciding which interfaces to generate). Defaults to None. + - emitSpirv - regex matching names of extensions and capabilities + to actually emit interfaces for. + - emitFormats - regex matching names of formats to actually emit + interfaces for. + - reparentEnums - move elements which extend an enumerated + type from or elements to the target + element. This is required for almost all purposes, but the + InterfaceGenerator relies on the list of interfaces in the + or being complete. Defaults to True. + - sortProcedure - takes a list of FeatureInfo objects and sorts + them in place to a preferred order in the generated output. + - requireCommandAliases - if True, treat command aliases + as required dependencies. + - requireDepends - whether to follow API dependencies when emitting + APIs. + + Default is + - core API versions + - Khronos (ARB/KHR/OES) extensions + - All other extensions + - By core API version number or extension number in each group. + + The regex patterns can be None or empty, in which case they match + nothing.""" + self.conventions = conventions + """may be mandatory for some generators: + an object that implements ConventionsBase""" + + self.filename = filename + "basename of file to generate, or None to write to stdout." + + self.genpath = genpath + """path to previously generated files, such as apimap.py""" + + self.directory = directory + "directory in which to generate filename" + + self.apiname = apiname + "string matching `` 'apiname' attribute, e.g. 'gl'." + + self.mergeApiNames = mergeApiNames + "comma separated list of API names to merge into the API specified by 'apiname'" + + self.profile = profile + "string specifying API profile , e.g. 'core', or None." + + self.versions = self.emptyRegex(versions) + """regex matching API versions to process interfaces for. + Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.""" + + self.emitversions = self.emptyRegex(emitversions) + """regex matching API versions to actually emit + interfaces for (though all requested versions are considered + when deciding which interfaces to generate). For GL 4.3 glext.h, + this might be `'1[.][2-5]|[2-4][.][0-9]'`.""" + + self.defaultExtensions = defaultExtensions + """If not None, a string which must in its + entirety match the pattern in the "supported" attribute of + the ``. Defaults to None. Usually the same as apiname.""" + + self.addExtensions = self.emptyRegex(addExtensions) + """regex matching names of additional extensions + to include. Defaults to None.""" + + self.removeExtensions = self.emptyRegex(removeExtensions) + """regex matching names of extensions to + remove (after defaultExtensions and addExtensions). Defaults + to None.""" + + self.emitExtensions = self.emptyRegex(emitExtensions) + """regex matching names of extensions to actually emit + interfaces for (though all requested versions are considered when + deciding which interfaces to generate).""" + + self.emitSpirv = self.emptyRegex(emitSpirv) + """regex matching names of extensions and capabilities + to actually emit interfaces for.""" + + self.emitFormats = self.emptyRegex(emitFormats) + """regex matching names of formats + to actually emit interfaces for.""" + + self.reparentEnums = reparentEnums + """boolean specifying whether to remove elements from + or when extending an type.""" + + self.sortProcedure = sortProcedure + """takes a list of FeatureInfo objects and sorts + them in place to a preferred order in the generated output. + Default is core API versions, ARB/KHR/OES extensions, all + other extensions, alphabetically within each group.""" + + self.codeGenerator = False + """True if this generator makes compilable code""" + + self.registry = None + """Populated later with the registry object.""" + + self.requireCommandAliases = requireCommandAliases + """True if alias= attributes of tags are transitively + required.""" + + self.requireDepends = requireDepends + """True if dependencies of API tags are transitively required.""" + + def emptyRegex(self, pat): + """Substitute a regular expression which matches no version + or extension names for None or the empty string.""" + if not pat: + return '_nomatch_^' + + return pat + + +class OutputGenerator: + """Generate specified API interfaces in a specific style, such as a C header. + + Base class for generating API interfaces. + Manages basic logic, logging, and output file control. + Derived classes actually generate formatted output. + """ + + # categoryToPath - map XML 'category' to include file directory name + categoryToPath = { + 'bitmask': 'flags', + 'enum': 'enums', + 'funcpointer': 'funcpointers', + 'handle': 'handles', + 'define': 'defines', + 'basetype': 'basetypes', + } + + def breakName(self, name, msg): + """Break into debugger if this is a special name""" + + # List of string names to break on + bad = ( + ) + + if name in bad and True: + print('breakName {}: {}'.format(name, msg)) + pdb.set_trace() + + def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout): + """Constructor + + - errFile, warnFile, diagFile - file handles to write errors, + warnings, diagnostics to. May be None to not write.""" + self.outFile = None + self.errFile = errFile + self.warnFile = warnFile + self.diagFile = diagFile + # Internal state + self.featureName = None + """The current feature name being generated.""" + + self.genOpts = None + """The GeneratorOptions subclass instance.""" + + self.registry = None + """The specification registry object.""" + + self.featureDictionary = {} + """The dictionary of dictionaries of API features.""" + + # Used for extension enum value generation + self.extBase = 1000000000 + self.extBlockSize = 1000 + self.madeDirs = {} + + # API dictionary, which may be loaded by the beginFile method of + # derived generators. + self.apidict = None + + # File suffix for generated files, set in beginFile below. + self.file_suffix = '' + + def logMsg(self, level, *args): + """Write a message of different categories to different + destinations. + + - `level` + - 'diag' (diagnostic, voluminous) + - 'warn' (warning) + - 'error' (fatal error - raises exception after logging) + + - `*args` - print()-style arguments to direct to corresponding log""" + if level == 'error': + strfile = io.StringIO() + write('ERROR:', *args, file=strfile) + if self.errFile is not None: + write(strfile.getvalue(), file=self.errFile) + raise UserWarning(strfile.getvalue()) + elif level == 'warn': + if self.warnFile is not None: + write('WARNING:', *args, file=self.warnFile) + elif level == 'diag': + if self.diagFile is not None: + write('DIAG:', *args, file=self.diagFile) + else: + raise UserWarning( + '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) + + def enumToValue(self, elem, needsNum, bitwidth = 32, + forceSuffix = False, parent_for_alias_dereference=None): + """Parse and convert an `` tag into a value. + + - elem - Element + - needsNum - generate a numeric representation of the element value + - bitwidth - size of the numeric representation in bits (32 or 64) + - forceSuffix - if True, always use a 'U' / 'ULL' suffix on integers + - parent_for_alias_dereference - if not None, an Element containing + the parent of elem, used to look for elements this is an alias of + + Returns a list: + + - first element - integer representation of the value, or None + if needsNum is False. The value must be a legal number + if needsNum is True. + - second element - string representation of the value + + There are several possible representations of values. + + - A 'value' attribute simply contains the value. + - A 'bitpos' attribute defines a value by specifying the bit + position which is set in that value. + - An 'offset','extbase','extends' triplet specifies a value + as an offset to a base value defined by the specified + 'extbase' extension name, which is then cast to the + typename specified by 'extends'. This requires probing + the registry database, and imbeds knowledge of the + API extension enum scheme in this function. + - An 'alias' attribute contains the name of another enum + which this is an alias of. The other enum must be + declared first when emitting this enum.""" + if self.genOpts is None: + raise MissingGeneratorOptionsError() + if self.genOpts.conventions is None: + raise MissingGeneratorOptionsConventionsError() + + name = elem.get('name') + numVal = None + if 'value' in elem.keys(): + value = elem.get('value') + # print('About to translate value =', value, 'type =', type(value)) + if needsNum: + numVal = int(value, 0) + # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or + # 'ull'), append it to the string value. + # t = enuminfo.elem.get('type') + # if t is not None and t != '' and t != 'i' and t != 's': + # value += enuminfo.type + if forceSuffix: + if bitwidth == 64: + value = value + 'ULL' + else: + value = value + 'U' + self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') + return [numVal, value] + if 'bitpos' in elem.keys(): + value = elem.get('bitpos') + bitpos = int(value, 0) + numVal = 1 << bitpos + value = '0x%08x' % numVal + if bitwidth == 64 or bitpos >= 32: + value = value + 'ULL' + elif forceSuffix: + value = value + 'U' + self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') + return [numVal, value] + if 'offset' in elem.keys(): + # Obtain values in the mapping from the attributes + enumNegative = False + offset = int(elem.get('offset'), 0) + extnumber = int(elem.get('extnumber'), 0) + extends = elem.get('extends') + if 'dir' in elem.keys(): + enumNegative = True + self.logMsg('diag', 'Enum', name, 'offset =', offset, + 'extnumber =', extnumber, 'extends =', extends, + 'enumNegative =', enumNegative) + # Now determine the actual enumerant value, as defined + # in the "Layers and Extensions" appendix of the spec. + numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset + if enumNegative: + numVal *= -1 + value = '%d' % numVal + # More logic needed! + self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') + return [numVal, value] + if 'alias' in elem.keys(): + alias_of = elem.get('alias') + if parent_for_alias_dereference is None: + return (None, alias_of) + siblings = parent_for_alias_dereference.findall('enum') + for sib in siblings: + sib_name = sib.get('name') + if sib_name == alias_of: + return self.enumToValue(sib, needsNum) + raise RuntimeError("Could not find the aliased enum value") + return [None, None] + + def checkDuplicateEnums(self, enums): + """Check enumerated values for duplicates. + + - enums - list of `` Elements + + returns the list with duplicates stripped""" + # Dictionaries indexed by name and numeric value. + # Entries are [ Element, numVal, strVal ] matching name or value + + nameMap = {} + valueMap = {} + + stripped = [] + for elem in enums: + name = elem.get('name') + (numVal, strVal) = self.enumToValue(elem, True) + + if name in nameMap: + # Duplicate name found; check values + (name2, numVal2, strVal2) = nameMap[name] + + # Duplicate enum values for the same name are benign. This + # happens when defining the same enum conditionally in + # several extension blocks. + if (strVal2 == strVal or (numVal is not None + and numVal == numVal2)): + True + # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name + + # ') found with the same value:' + strVal) + else: + self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name + + ') found with different values:' + strVal + + ' and ' + strVal2) + + # Do not add the duplicate to the returned list + continue + elif numVal in valueMap: + # Duplicate value found (such as an alias); report it, but + # still add this enum to the list. + (name2, numVal2, strVal2) = valueMap[numVal] + + msg = 'Two enums found with the same value: {} = {} = {}'.format( + name, name2.get('name'), strVal) + self.logMsg('error', msg) + + # Track this enum to detect followon duplicates + nameMap[name] = [elem, numVal, strVal] + if numVal is not None: + valueMap[numVal] = [elem, numVal, strVal] + + # Add this enum to the list + stripped.append(elem) + + # Return the list + return stripped + + def misracstyle(self): + return False; + + def misracppstyle(self): + return False; + + def buildEnumCDecl(self, expand, groupinfo, groupName): + """Generate the C declaration for an enum""" + if self.genOpts is None: + raise MissingGeneratorOptionsError() + if self.genOpts.conventions is None: + raise MissingGeneratorOptionsConventionsError() + + groupElem = groupinfo.elem + + # Determine the required bit width for the enum group. + # 32 is the default, which generates C enum types for the values. + bitwidth = 32 + + # If the constFlagBits preference is set, 64 is the default for bitmasks + if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask': + bitwidth = 64 + + # Check for an explicitly defined bitwidth, which will override any defaults. + if groupElem.get('bitwidth'): + try: + bitwidth = int(groupElem.get('bitwidth')) + except ValueError as ve: + self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n') + exit(1) + + usebitmask = False + usedefine = False + + # Bitmask flags can be generated as either "static const uint{32,64}_t" values, + # or as 32-bit C enums. 64-bit types must use uint64_t values. + if groupElem.get('type') == 'bitmask': + if bitwidth > 32 or self.misracppstyle(): + usebitmask = True + if self.misracstyle(): + usedefine = True + + if usedefine or usebitmask: + # Validate the bitwidth and generate values appropriately + if bitwidth > 64: + self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n') + exit(1) + else: + return self.buildEnumCDecl_BitmaskOrDefine(groupinfo, groupName, bitwidth, usedefine) + else: + # Validate the bitwidth and generate values appropriately + if bitwidth > 32: + self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n') + exit(1) + else: + return self.buildEnumCDecl_Enum(expand, groupinfo, groupName) + + def buildEnumCDecl_BitmaskOrDefine(self, groupinfo, groupName, bitwidth, usedefine): + """Generate the C declaration for an "enum" that is actually a + set of flag bits""" + groupElem = groupinfo.elem + flagTypeName = groupElem.get('name') + + # Prefix + body = "// Flag bits for " + flagTypeName + "\n" + + if bitwidth == 64: + body += "typedef VkFlags64 %s;\n" % flagTypeName; + else: + body += "typedef VkFlags %s;\n" % flagTypeName; + + # Maximum allowable value for a flag (unsigned 64-bit integer) + maxValidValue = 2**(64) - 1 + minValidValue = 0 + + # Get a list of nested 'enum' tags. + enums = groupElem.findall('enum') + + # Check for and report duplicates, and return a list with them + # removed. + enums = self.checkDuplicateEnums(enums) + + # Accumulate non-numeric enumerant values separately and append + # them following the numeric values, to allow for aliases. + # NOTE: this does not do a topological sort yet, so aliases of + # aliases can still get in the wrong order. + aliasText = '' + + # Loop over the nested 'enum' tags. + for elem in enums: + # Convert the value to an integer and use that to track min/max. + # Values of form -(number) are accepted but nothing more complex. + # Should catch exceptions here for more complex constructs. Not yet. + (numVal, strVal) = self.enumToValue(elem, True, bitwidth, True) + name = elem.get('name') + + # Range check for the enum value + if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): + self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n') + exit(1) + + decl = self.genRequirements(name, mustBeFound = False) + + if self.isEnumRequired(elem): + protect = elem.get('protect') + if protect is not None: + body += '#ifdef {}\n'.format(protect) + + if usedefine: + decl += "#define {} {}\n".format(name, strVal) + elif self.misracppstyle(): + decl += "static constexpr {} {} {{{}}};\n".format(flagTypeName, name, strVal) + else: + # Some C compilers only allow initializing a 'static const' variable with a literal value. + # So initializing an alias from another 'static const' value would fail to compile. + # Work around this by chasing the aliases to get the actual value. + while numVal is None: + alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']") + if alias is not None: + (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True) + else: + self.logMsg('error', 'No such alias {} for enum {}'.format(strVal, name)) + decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal) + + if numVal is not None: + body += decl + else: + aliasText += decl + + if protect is not None: + body += '#endif\n' + + # Now append the non-numeric enumerant values + body += aliasText + + # Postfix + + return ("bitmask", body) + + def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): + """Generate the C declaration for an enumerated type""" + groupElem = groupinfo.elem + + # Break the group name into prefix and suffix portions for range + # enum generation + expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper() + expandPrefix = expandName + expandSuffix = '' + expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName) + if expandSuffixMatch: + expandSuffix = '_' + expandSuffixMatch.group() + # Strip off the suffix from the prefix + expandPrefix = expandName.rsplit(expandSuffix, 1)[0] + + # Prefix + body = ["typedef enum %s {" % groupName] + + # @@ Should use the type="bitmask" attribute instead + isEnum = ('FLAG_BITS' not in expandPrefix) + + # Allowable range for a C enum - which is that of a signed 32-bit integer + maxValidValue = 2**(32 - 1) - 1 + minValidValue = (maxValidValue * -1) - 1 + + # Get a list of nested 'enum' tags. + enums = groupElem.findall('enum') + + # Check for and report duplicates, and return a list with them + # removed. + enums = self.checkDuplicateEnums(enums) + + # Loop over the nested 'enum' tags. Keep track of the minimum and + # maximum numeric values, if they can be determined; but only for + # core API enumerants, not extension enumerants. This is inferred + # by looking for 'extends' attributes. + minName = None + + # Accumulate non-numeric enumerant values separately and append + # them following the numeric values, to allow for aliases. + # NOTE: this does not do a topological sort yet, so aliases of + # aliases can still get in the wrong order. + aliasText = [] + + maxName = None + minValue = None + maxValue = None + for elem in enums: + # Convert the value to an integer and use that to track min/max. + # Values of form -(number) are accepted but nothing more complex. + # Should catch exceptions here for more complex constructs. Not yet. + (numVal, strVal) = self.enumToValue(elem, True) + name = elem.get('name') + + # Extension enumerants are only included if they are required + if self.isEnumRequired(elem): + decl = '' + + protect = elem.get('protect') + if protect is not None: + decl += '#ifdef {}\n'.format(protect) + + # Indent requirements comment, if there is one + requirements = self.genRequirements(name, mustBeFound = False) + if requirements != '': + requirements = ' ' + requirements + decl += requirements + decl += ' {} = {},'.format(name, strVal) + + if protect is not None: + decl += '\n#endif' + + if numVal is not None: + body.append(decl) + else: + aliasText.append(decl) + + # Range check for the enum value + if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): + self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n') + exit(1) + + # Do not track min/max for non-numbers (numVal is None) + if isEnum and numVal is not None and elem.get('extends') is None: + if minName is None: + minName = maxName = name + minValue = maxValue = numVal + elif minValue is None or numVal < minValue: + minName = name + minValue = numVal + elif maxValue is None or numVal > maxValue: + maxName = name + maxValue = numVal + + # Now append the non-numeric enumerant values + body.extend(aliasText) + + # Generate min/max value tokens - legacy use case. + if isEnum and expand: + body.extend((f' {expandPrefix}_BEGIN_RANGE{expandSuffix} = {minName},', + f' {expandPrefix}_END_RANGE{expandSuffix} = {maxName},', + f' {expandPrefix}_RANGE_SIZE{expandSuffix} = ({maxName} - {minName} + 1),')) + + # Generate a range-padding value to ensure the enum is 32 bits, but + # only in code generators, so it does not appear in documentation + if (self.genOpts.codeGenerator or + self.conventions.generate_max_enum_in_docs): + body.append(f' {expandPrefix}_MAX_ENUM{expandSuffix} = 0x7FFFFFFF') + + # Postfix + body.append("} %s;" % groupName) + + # Determine appropriate section for this declaration + if groupElem.get('type') == 'bitmask': + section = 'bitmask' + else: + section = 'group' + + return (section, '\n'.join(body)) + + def buildConstantCDecl(self, enuminfo, name, alias): + """Generate the C declaration for a constant (a single + value). + + tags may specify their values in several ways, but are + usually just integers or floating-point numbers.""" + + (_, strVal) = self.enumToValue(enuminfo.elem, False) + + if self.misracppstyle() and enuminfo.elem.get('type') and not alias: + # Generate e.g.: static constexpr uint32_t x = ~static_cast(1U); + # This appeases MISRA "underlying type" rules. + typeStr = enuminfo.elem.get('type'); + invert = '~' in strVal + number = strVal.strip("()~UL") + if typeStr != "float": + number += 'U' + strVal = "~" if invert else "" + strVal += "static_cast<" + typeStr + ">(" + number + ")" + body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};' + elif enuminfo.elem.get('type') and not alias: + # Generate e.g.: #define x (~0ULL) + typeStr = enuminfo.elem.get('type'); + invert = '~' in strVal + paren = '(' in strVal + number = strVal.strip("()~UL") + if typeStr != "float": + if typeStr == "uint64_t": + number += 'ULL' + else: + number += 'U' + strVal = "~" if invert else "" + strVal += number + if paren: + strVal = "(" + strVal + ")"; + body = '#define ' + name.ljust(33) + ' ' + strVal; + else: + body = '#define ' + name.ljust(33) + ' ' + strVal + + return body + + def makeDir(self, path): + """Create a directory, if not already done. + + Generally called from derived generators creating hierarchies.""" + self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') + if path not in self.madeDirs: + # This can get race conditions with multiple writers, see + # https://stackoverflow.com/questions/273192/ + if not os.path.exists(path): + os.makedirs(path) + self.madeDirs[path] = None + + def beginFile(self, genOpts): + """Start a new interface file + + - genOpts - GeneratorOptions controlling what is generated and how""" + + self.genOpts = genOpts + if self.genOpts is None: + raise MissingGeneratorOptionsError() + if self.genOpts.conventions is None: + raise MissingGeneratorOptionsConventionsError() + self.should_insert_may_alias_macro = \ + self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts) + self.file_suffix = self.genOpts.conventions.file_suffix + + # Try to import the API dictionary, apimap.py, if it exists. Nothing + # in apimap.py cannot be extracted directly from the XML, and in the + # future we should do that. + if self.genOpts.genpath is not None: + try: + sys.path.insert(0, self.genOpts.genpath) + import apimap + self.apidict = apimap + except ImportError: + self.apidict = None + + self.conventions = genOpts.conventions + + # Open a temporary file for accumulating output. + if self.genOpts.filename is not None: + self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False) + else: + self.outFile = sys.stdout + + def endFile(self): + if self.errFile: + self.errFile.flush() + if self.warnFile: + self.warnFile.flush() + if self.diagFile: + self.diagFile.flush() + if self.outFile: + self.outFile.flush() + if self.outFile != sys.stdout and self.outFile != sys.stderr: + self.outFile.close() + + if self.genOpts is None: + raise MissingGeneratorOptionsError() + + # On successfully generating output, move the temporary file to the + # target file. + if self.genOpts.filename is not None: + if sys.platform == 'win32': + directory = Path(self.genOpts.directory) + if not Path.exists(directory): + os.makedirs(directory) + shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename) + os.remove(self.outFile.name) + self.genOpts = None + + def beginFeature(self, interface, emit): + """Write interface for a feature and tag generated features as having been done. + + - interface - element for the `` / `` to generate + - emit - actually write to the header only when True""" + self.emit = emit + self.featureName = interface.get('name') + # If there is an additional 'protect' attribute in the feature, save it + self.featureExtraProtect = interface.get('protect') + + def endFeature(self): + """Finish an interface file, closing it when done. + + Derived classes responsible for emitting feature""" + self.featureName = None + self.featureExtraProtect = None + + def genRequirements(self, name, mustBeFound = True): + """Generate text showing what core versions and extensions introduce + an API. This exists in the base Generator class because it is used by + the shared enumerant-generating interfaces (buildEnumCDecl, etc.). + Here it returns an empty string for most generators, but can be + overridden by e.g. DocGenerator. + + - name - name of the API + - mustBeFound - If True, when requirements for 'name' cannot be + determined, a warning comment is generated. + """ + + return '' + + def validateFeature(self, featureType, featureName): + """Validate we are generating something only inside a `` tag""" + if self.featureName is None: + raise UserWarning('Attempt to generate', featureType, + featureName, 'when not in feature') + + def genType(self, typeinfo, name, alias): + """Generate interface for a type + + - typeinfo - TypeInfo for a type + + Extend to generate as desired in your derived class.""" + self.validateFeature('type', name) + + def genStruct(self, typeinfo, typeName, alias): + """Generate interface for a C "struct" type. + + - typeinfo - TypeInfo for a type interpreted as a struct + + Extend to generate as desired in your derived class.""" + self.validateFeature('struct', typeName) + + # The mixed-mode tags may contain no-op tags. + # It is convenient to remove them here where all output generators + # will benefit. + for member in typeinfo.elem.findall('.//member'): + for comment in member.findall('comment'): + member.remove(comment) + + def genGroup(self, groupinfo, groupName, alias): + """Generate interface for a group of enums (C "enum") + + - groupinfo - GroupInfo for a group. + + Extend to generate as desired in your derived class.""" + + self.validateFeature('group', groupName) + + def genEnum(self, enuminfo, typeName, alias): + """Generate interface for an enum (constant). + + - enuminfo - EnumInfo for an enum + - name - enum name + + Extend to generate as desired in your derived class.""" + self.validateFeature('enum', typeName) + + def genCmd(self, cmd, cmdinfo, alias): + """Generate interface for a command. + + - cmdinfo - CmdInfo for a command + + Extend to generate as desired in your derived class.""" + self.validateFeature('command', cmdinfo) + + def genSpirv(self, spirv, spirvinfo, alias): + """Generate interface for a spirv element. + + - spirvinfo - SpirvInfo for a command + + Extend to generate as desired in your derived class.""" + return + + def genFormat(self, format, formatinfo, alias): + """Generate interface for a format element. + + - formatinfo - FormatInfo + + Extend to generate as desired in your derived class.""" + return + + def genSyncStage(self, stageinfo): + """Generate interface for a sync stage element. + + - stageinfo - SyncStageInfo + + Extend to generate as desired in your derived class.""" + return + + def genSyncAccess(self, accessinfo): + """Generate interface for a sync stage element. + + - accessinfo - AccessInfo + + Extend to generate as desired in your derived class.""" + return + + def genSyncPipeline(self, pipelineinfo): + """Generate interface for a sync stage element. + + - pipelineinfo - SyncPipelineInfo + + Extend to generate as desired in your derived class.""" + return + + def makeProtoName(self, name, tail): + """Turn a `` `` into C-language prototype + and typedef declarations for that name. + + - name - contents of `` tag + - tail - whatever text follows that tag in the Element""" + if self.genOpts is None: + raise MissingGeneratorOptionsError() + return self.genOpts.apientry + name + tail + + def makeTypedefName(self, name, tail): + """Make the function-pointer typedef name for a command.""" + if self.genOpts is None: + raise MissingGeneratorOptionsError() + return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' + + def makeCParamDecl(self, param, aligncol): + """Return a string which is an indented, formatted + declaration for a `` or `` block (e.g. function parameter + or structure/union member). + + - param - Element (`` or ``) to format + - aligncol - if non-zero, attempt to align the nested `` element + at this column""" + if self.genOpts is None: + raise MissingGeneratorOptionsError() + if self.genOpts.conventions is None: + raise MissingGeneratorOptionsConventionsError() + indent = ' ' + paramdecl = indent + prefix = noneStr(param.text) + + for elem in param: + text = noneStr(elem.text) + tail = noneStr(elem.tail) + + if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): + # OpenXR-specific macro insertion - but not in apiinc for the spec + tail = self.genOpts.conventions.make_voidpointer_alias(tail) + if elem.tag == 'name' and aligncol > 0: + self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) + # Align at specified column, if possible + paramdecl = paramdecl.rstrip() + oldLen = len(paramdecl) + # This works around a problem where very long type names - + # longer than the alignment column - would run into the tail + # text. + paramdecl = paramdecl.ljust(aligncol - 1) + ' ' + newLen = len(paramdecl) + self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) + + if (self.misracppstyle() and prefix.find('const ') != -1): + # Change pointer type order from e.g. "const void *" to "void const *". + # If the string starts with 'const', reorder it to be after the first type. + paramdecl += prefix.replace('const ', '') + text + ' const' + tail + else: + paramdecl += prefix + text + tail + + # Clear prefix for subsequent iterations + prefix = '' + + paramdecl = paramdecl + prefix + + if aligncol == 0: + # Squeeze out multiple spaces other than the indentation + paramdecl = indent + ' '.join(paramdecl.split()) + return paramdecl + + def getCParamTypeLength(self, param): + """Return the length of the type field is an indented, formatted + declaration for a `` or `` block (e.g. function parameter + or structure/union member). + + - param - Element (`` or ``) to identify""" + if self.genOpts is None: + raise MissingGeneratorOptionsError() + if self.genOpts.conventions is None: + raise MissingGeneratorOptionsConventionsError() + + # Allow for missing tag + newLen = 0 + paramdecl = ' ' + noneStr(param.text) + for elem in param: + text = noneStr(elem.text) + tail = noneStr(elem.tail) + + if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): + # OpenXR-specific macro insertion + tail = self.genOpts.conventions.make_voidpointer_alias(tail) + if elem.tag == 'name': + # Align at specified column, if possible + newLen = len(paramdecl.rstrip()) + self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) + paramdecl += text + tail + + return newLen + + def getMaxCParamTypeLength(self, info): + """Return the length of the longest type field for a member/parameter. + + - info - TypeInfo or CommandInfo. + """ + lengths = (self.getCParamTypeLength(member) + for member in info.getMembers()) + return max(lengths) + + def getHandleParent(self, typename): + """Get the parent of a handle object.""" + if self.registry is None: + raise MissingRegistryError() + + info = self.registry.typedict.get(typename) + if info is None: + return None + + elem = info.elem + if elem is not None: + return elem.get('parent') + + return None + + def iterateHandleAncestors(self, typename): + """Iterate through the ancestors of a handle type.""" + current = self.getHandleParent(typename) + while current is not None: + yield current + current = self.getHandleParent(current) + + def getHandleAncestors(self, typename): + """Get the ancestors of a handle object.""" + return list(self.iterateHandleAncestors(typename)) + + def getTypeCategory(self, typename): + """Get the category of a type.""" + if self.registry is None: + raise MissingRegistryError() + + info = self.registry.typedict.get(typename) + if info is None: + return None + + elem = info.elem + if elem is not None: + return elem.get('category') + return None + + def isStructAlwaysValid(self, structname): + """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance).""" + # A conventions object is required for this call. + if not self.conventions: + raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.") + if self.registry is None: + raise MissingRegistryError() + + if self.conventions.type_always_valid(structname): + return True + + category = self.getTypeCategory(structname) + if self.conventions.category_requires_validation(category): + return False + + info = self.registry.typedict.get(structname) + if info is None: + self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict') + + members = info.getMembers() + + for member in members: + member_name = getElemName(member) + if member_name in (self.conventions.structtype_member_name, + self.conventions.nextpointer_member_name): + return False + + if member.get('noautovalidity'): + return False + + member_type = getElemType(member) + + if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member): + return False + + if self.conventions.type_always_valid(member_type): + continue + + member_category = self.getTypeCategory(member_type) + + if self.conventions.category_requires_validation(member_category): + return False + + if member_category in ('struct', 'union'): + if self.isStructAlwaysValid(member_type) is False: + return False + + return True + + def paramIsArray(self, param): + """Check if the parameter passed in is a pointer to an array. + + param the XML information for the param + """ + return param.get('len') is not None + + def paramIsPointer(self, param): + """Check if the parameter passed in is a pointer. + + param the XML information for the param + """ + tail = param.find('type').tail + return tail is not None and '*' in tail + + def isEnumRequired(self, elem): + """Return True if this `` element is + required, False otherwise + + - elem - `` element to test""" + required = elem.get('required') is not None + self.logMsg('diag', 'isEnumRequired:', elem.get('name'), + '->', required) + return required + + # @@@ This code is overridden by equivalent code now run in + # @@@ Registry.generateFeature + + required = False + + extname = elem.get('extname') + if extname is not None: + # 'supported' attribute was injected when the element was + # moved into the group in Registry.parseTree() + if self.genOpts.defaultExtensions == elem.get('supported'): + required = True + elif re.match(self.genOpts.addExtensions, extname) is not None: + required = True + elif elem.get('version') is not None: + required = re.match(self.genOpts.emitversions, elem.get('version')) is not None + else: + required = True + + return required + + def makeCDecls(self, cmd): + """Return C prototype and function pointer typedef for a + `` Element, as a two-element list of strings. + + - cmd - Element containing a `` tag""" + if self.genOpts is None: + raise MissingGeneratorOptionsError() + proto = cmd.find('proto') + params = cmd.findall('param') + # Begin accumulating prototype and typedef strings + pdecl = self.genOpts.apicall + tdecl = 'typedef ' + + # Insert the function return type/name. + # For prototypes, add APIENTRY macro before the name + # For typedefs, add (APIENTRY *) around the name and + # use the PFN_cmdnameproc naming convention. + # Done by walking the tree for element by element. + # etree has elem.text followed by (elem[i], elem[i].tail) + # for each child element and any following text + # Leading text + pdecl += noneStr(proto.text) + tdecl += noneStr(proto.text) + # For each child element, if it is a wrap in appropriate + # declaration. Otherwise append its contents and tail contents. + for elem in proto: + text = noneStr(elem.text) + tail = noneStr(elem.tail) + if elem.tag == 'name': + pdecl += self.makeProtoName(text, tail) + tdecl += self.makeTypedefName(text, tail) + else: + pdecl += text + tail + tdecl += text + tail + + if self.genOpts.alignFuncParam == 0: + # Squeeze out multiple spaces - there is no indentation + pdecl = ' '.join(pdecl.split()) + tdecl = ' '.join(tdecl.split()) + + # Now add the parameter declaration list, which is identical + # for prototypes and typedefs. Concatenate all the text from + # a node without the tags. No tree walking required + # since all tags are ignored. + # Uses: self.indentFuncProto + # self.indentFuncPointer + # self.alignFuncParam + n = len(params) + # Indented parameters + if n > 0: + indentdecl = '(\n' + indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) + for p in params) + indentdecl += ');' + else: + indentdecl = '(void);' + # Non-indented parameters + paramdecl = '(' + if n > 0: + paramnames = [] + if self.misracppstyle(): + for p in params: + param = '' + firstIter = True; + for t in p.itertext(): + if (firstIter): + prefix = t + firstIter = False + else: + # Change pointer type order from e.g. "const void *" to "void const *". + # If the string starts with 'const', reorder it to be after the first type. + if (prefix.find('const ') != -1): + param += prefix.replace('const ', '') + t + ' const ' + else: + param += prefix + t + # Clear prefix for subsequent iterations + prefix = '' + paramnames.append(param); + else: + paramnames = (''.join(t for t in p.itertext()) + for p in params) + paramdecl += ', '.join(paramnames) + else: + paramdecl += 'void' + paramdecl += ");" + return [pdecl + indentdecl, tdecl + paramdecl] + + def newline(self): + """Print a newline to the output file (utility function)""" + write('', file=self.outFile) + + def setRegistry(self, registry): + self.registry = registry diff --git a/src/gfxstream/codegen/scripts/genvk.py b/src/gfxstream/codegen/scripts/genvk.py new file mode 100755 index 00000000000..676c78c4f50 --- /dev/null +++ b/src/gfxstream/codegen/scripts/genvk.py @@ -0,0 +1,1117 @@ +#!/usr/bin/python3 +# +# Copyright 2013-2023 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import os +import pdb +import re +import sys +import copy +import time +import xml.etree.ElementTree as etree + +sys.path.append(os.path.abspath(os.path.dirname(__file__))) + +from cgenerator import CGeneratorOptions, COutputGenerator +# Vulkan SC modules +from json_parser import JSONParserGenerator, JSONParserOptions +from schema_generator import SchemaGeneratorOptions, SchemaOutputGenerator +from json_generator import JSONGeneratorOptions, JSONOutputGenerator +from json_h_generator import JSONHeaderOutputGenerator, JSONHeaderGeneratorOptions +from json_c_generator import JSONCOutputGenerator, JSONCGeneratorOptions + +from docgenerator import DocGeneratorOptions, DocOutputGenerator +from extensionmetadocgenerator import (ExtensionMetaDocGeneratorOptions, + ExtensionMetaDocOutputGenerator) +from interfacedocgenerator import InterfaceDocGenerator +from generator import write +from spirvcapgenerator import SpirvCapabilityOutputGenerator +from hostsyncgenerator import HostSynchronizationOutputGenerator +from formatsgenerator import FormatsOutputGenerator +from syncgenerator import SyncOutputGenerator +from jsgenerator import JSOutputGenerator +from pygenerator import PyOutputGenerator +from rubygenerator import RubyOutputGenerator +from reflib import logDiag, logWarn, logErr, setLogFile +from reg import Registry +from validitygenerator import ValidityOutputGenerator +from apiconventions import APIConventions + +# Simple timer functions +startTime = None + + +def startTimer(timeit): + global startTime + if timeit: + startTime = time.process_time() + + +def endTimer(timeit, msg): + global startTime + if timeit and startTime is not None: + endTime = time.process_time() + logDiag(msg, endTime - startTime) + startTime = None + + +def makeREstring(strings, default=None, strings_are_regex=False): + """Turn a list of strings into a regexp string matching exactly those strings.""" + if strings or default is None: + if not strings_are_regex: + strings = (re.escape(s) for s in strings) + return '^(' + '|'.join(strings) + ')$' + return default + + +def makeGenOpts(args): + """Returns a directory of [ generator function, generator options ] indexed + by specified short names. The generator options incorporate the following + parameters: + + args is an parsed argument object; see below for the fields that are used.""" + global genOpts + genOpts = {} + + # Default class of extensions to include, or None + defaultExtensions = args.defaultExtensions + + # Additional extensions to include (list of extensions) + extensions = args.extension + + # Extensions to remove (list of extensions) + removeExtensions = args.removeExtensions + + # Extensions to emit (list of extensions) + emitExtensions = args.emitExtensions + + # SPIR-V capabilities / features to emit (list of extensions & capabilities) + emitSpirv = args.emitSpirv + + # Vulkan Formats to emit + emitFormats = args.emitFormats + + # Features to include (list of features) + features = args.feature + + # Whether to disable inclusion protect in headers + protect = args.protect + + # Output target directory + directory = args.directory + + # Path to generated files, particularly apimap.py + genpath = args.genpath + + # Generate MISRA C-friendly headers + misracstyle = args.misracstyle; + + # Generate MISRA C++-friendly headers + misracppstyle = args.misracppstyle; + + # Descriptive names for various regexp patterns used to select + # versions and extensions + allFormats = allSpirv = allFeatures = allExtensions = r'.*' + + # Turn lists of names/patterns into matching regular expressions + addExtensionsPat = makeREstring(extensions, None) + removeExtensionsPat = makeREstring(removeExtensions, None) + emitExtensionsPat = makeREstring(emitExtensions, allExtensions) + emitSpirvPat = makeREstring(emitSpirv, allSpirv) + emitFormatsPat = makeREstring(emitFormats, allFormats) + featuresPat = makeREstring(features, allFeatures) + + # Copyright text prefixing all headers (list of strings). + # The SPDX formatting below works around constraints of the 'reuse' tool + prefixStrings = [ + '/*', + '** Copyright 2015-2023 The Khronos Group Inc.', + '**', + '** SPDX-License-Identifier' + ': Apache-2.0', + '*/', + '' + ] + + # Text specific to Vulkan headers + vkPrefixStrings = [ + '/*', + '** This header is generated from the Khronos Vulkan XML API Registry.', + '**', + '*/', + '' + ] + + vulkanLayer = args.vulkanLayer + + # Defaults for generating re-inclusion protection wrappers (or not) + protectFile = protect + + # An API style conventions object + conventions = APIConventions() + + if args.apiname is not None: + defaultAPIName = args.apiname + else: + defaultAPIName = conventions.xml_api_name + + # APIs to merge + mergeApiNames = args.mergeApiNames + + isCTS = args.isCTS + + # API include files for spec and ref pages + # Overwrites include subdirectories in spec source tree + # The generated include files do not include the calling convention + # macros (apientry etc.), unlike the header files. + # Because the 1.0 core branch includes ref pages for extensions, + # all the extension interfaces need to be generated, even though + # none are used by the core spec itself. + genOpts['apiinc'] = [ + DocOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = genpath, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + apicall = '', + apientry = '', + apientryp = '*', + alignFuncParam = 48, + expandEnumerants = False) + ] + + # JavaScript, Python, and Ruby representations of API information, used + # by scripts that do not need to load the full XML. + genOpts['apimap.cjs'] = [ + JSOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'apimap.cjs', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + reparentEnums = False) + ] + + genOpts['apimap.py'] = [ + PyOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'apimap.py', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + reparentEnums = False) + ] + + genOpts['apimap.rb'] = [ + RubyOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'apimap.rb', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + reparentEnums = False) + ] + + + # API validity files for spec + # + # requireCommandAliases is set to True because we need validity files + # for the command something is promoted to even when the promoted-to + # feature is not included. This avoids wordy includes of validity files. + genOpts['validinc'] = [ + ValidityOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + requireCommandAliases = True, + ) + ] + + # API host sync table files for spec + genOpts['hostsyncinc'] = [ + HostSynchronizationOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + reparentEnums = False) + ] + + # Extension metainformation for spec extension appendices + # Includes all extensions by default, but only so that the generated + # 'promoted_extensions_*' files refer to all extensions that were + # promoted to a core version. + genOpts['extinc'] = [ + ExtensionMetaDocOutputGenerator, + ExtensionMetaDocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = None, + defaultExtensions = defaultExtensions, + addExtensions = addExtensionsPat, + removeExtensions = None, + emitExtensions = emitExtensionsPat) + ] + + # Version and extension interface docs for version/extension appendices + # Includes all extensions by default. + genOpts['interfaceinc'] = [ + InterfaceDocGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + reparentEnums = False) + ] + + genOpts['spirvcapinc'] = [ + SpirvCapabilityOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + emitSpirv = emitSpirvPat, + reparentEnums = False) + ] + + # Used to generate various format chapter tables + genOpts['formatsinc'] = [ + FormatsOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + emitFormats = emitFormatsPat, + reparentEnums = False) + ] + + # Used to generate various synchronization chapter tables + genOpts['syncinc'] = [ + SyncOutputGenerator, + DocGeneratorOptions( + conventions = conventions, + filename = 'timeMarker', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + reparentEnums = False) + ] + + # Platform extensions, in their own header files + # Each element of the platforms[] array defines information for + # generating a single platform: + # [0] is the generated header file name + # [1] is the set of platform extensions to generate + # [2] is additional extensions whose interfaces should be considered, + # but suppressed in the output, to avoid duplicate definitions of + # dependent types like VkDisplayKHR and VkSurfaceKHR which come from + # non-platform extensions. + + # Track all platform extensions, for exclusion from vulkan_core.h + allPlatformExtensions = [] + + # Extensions suppressed for all WSI platforms (WSI extensions required + # by all platforms) + commonSuppressExtensions = [ 'VK_KHR_display', 'VK_KHR_swapchain' ] + + # Extensions required and suppressed for beta "platform". This can + # probably eventually be derived from the requires= attributes of + # the extension blocks. + betaRequireExtensions = [ + 'VK_KHR_portability_subset', + 'VK_KHR_video_encode_queue', + 'VK_EXT_video_encode_h264', + 'VK_EXT_video_encode_h265', + 'VK_NV_displacement_micromap', + 'VK_AMDX_shader_enqueue', + ] + + betaSuppressExtensions = [ + 'VK_KHR_video_queue', + 'VK_EXT_opacity_micromap', + 'VK_KHR_pipeline_library', + ] + + platforms = [ + [ 'vulkan_android.h', [ 'VK_KHR_android_surface', + 'VK_ANDROID_external_memory_android_hardware_buffer', + 'VK_ANDROID_external_format_resolve' + ], commonSuppressExtensions + + [ 'VK_KHR_format_feature_flags2', + ] ], + [ 'vulkan_fuchsia.h', [ 'VK_FUCHSIA_imagepipe_surface', + 'VK_FUCHSIA_external_memory', + 'VK_FUCHSIA_external_semaphore', + 'VK_FUCHSIA_buffer_collection' ], commonSuppressExtensions ], + [ 'vulkan_ggp.h', [ 'VK_GGP_stream_descriptor_surface', + 'VK_GGP_frame_token' ], commonSuppressExtensions ], + [ 'vulkan_ios.h', [ 'VK_MVK_ios_surface' ], commonSuppressExtensions ], + [ 'vulkan_macos.h', [ 'VK_MVK_macos_surface' ], commonSuppressExtensions ], + [ 'vulkan_vi.h', [ 'VK_NN_vi_surface' ], commonSuppressExtensions ], + [ 'vulkan_wayland.h', [ 'VK_KHR_wayland_surface' ], commonSuppressExtensions ], + [ 'vulkan_win32.h', [ 'VK_.*_win32(|_.*)', 'VK_.*_winrt(|_.*)', 'VK_EXT_full_screen_exclusive' ], + commonSuppressExtensions + + [ 'VK_KHR_external_semaphore', + 'VK_KHR_external_memory_capabilities', + 'VK_KHR_external_fence', + 'VK_KHR_external_fence_capabilities', + 'VK_KHR_get_surface_capabilities2', + 'VK_NV_external_memory_capabilities', + ] ], + [ 'vulkan_xcb.h', [ 'VK_KHR_xcb_surface' ], commonSuppressExtensions ], + [ 'vulkan_xlib.h', [ 'VK_KHR_xlib_surface' ], commonSuppressExtensions ], + [ 'vulkan_directfb.h', [ 'VK_EXT_directfb_surface' ], commonSuppressExtensions ], + [ 'vulkan_xlib_xrandr.h', [ 'VK_EXT_acquire_xlib_display' ], commonSuppressExtensions ], + [ 'vulkan_metal.h', [ 'VK_EXT_metal_surface', + 'VK_EXT_metal_objects' ], commonSuppressExtensions ], + [ 'vulkan_screen.h', [ 'VK_QNX_screen_surface', + 'VK_QNX_external_memory_screen_buffer' ], commonSuppressExtensions ], + [ 'vulkan_sci.h', [ 'VK_NV_external_sci_sync', + 'VK_NV_external_sci_sync2', + 'VK_NV_external_memory_sci_buf'], commonSuppressExtensions ], + [ 'vulkan_beta.h', betaRequireExtensions, betaSuppressExtensions ], + ] + + for platform in platforms: + headername = platform[0] + + allPlatformExtensions += platform[1] + + addPlatformExtensionsRE = makeREstring( + platform[1] + platform[2], strings_are_regex=True) + emitPlatformExtensionsRE = makeREstring( + platform[1], strings_are_regex=True) + + opts = CGeneratorOptions( + conventions = conventions, + filename = headername, + directory = directory, + genpath = None, + apiname = defaultAPIName, + mergeApiNames = mergeApiNames, + profile = None, + versions = featuresPat, + emitversions = None, + defaultExtensions = None, + addExtensions = addPlatformExtensionsRE, + removeExtensions = None, + emitExtensions = emitPlatformExtensionsRE, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48, + misracstyle = misracstyle, + misracppstyle = misracppstyle) + + genOpts[headername] = [ COutputGenerator, opts ] + + # Header for core API + extensions. + # To generate just the core API, + # change to 'defaultExtensions = None' below. + # + # By default this adds all enabled, non-platform extensions. + # It removes all platform extensions (from the platform headers options + # constructed above) as well as any explicitly specified removals. + + removeExtensionsPat = makeREstring( + allPlatformExtensions + removeExtensions, None, strings_are_regex=True) + + genOpts['vulkan_core.h'] = [ + COutputGenerator, + CGeneratorOptions( + conventions = conventions, + filename = 'vulkan_core.h', + directory = directory, + genpath = None, + apiname = defaultAPIName, + mergeApiNames = mergeApiNames, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = defaultExtensions, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48, + misracstyle = misracstyle, + misracppstyle = misracppstyle) + ] + + # Vulkan versions to include for SC header - SC *removes* features from 1.0/1.1/1.2 + scVersions = makeREstring(['VK_VERSION_1_0', 'VK_VERSION_1_1', 'VK_VERSION_1_2', 'VKSC_VERSION_1_0']) + + genOpts['vulkan_sc_core.h'] = [ + COutputGenerator, + CGeneratorOptions( + conventions = conventions, + filename = 'vulkan_sc_core.h', + directory = directory, + apiname = 'vulkansc', + profile = None, + versions = scVersions, + emitversions = scVersions, + defaultExtensions = 'vulkansc', + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48, + misracstyle = misracstyle, + misracppstyle = misracppstyle) + ] + + genOpts['vulkan_sc_core.hpp'] = [ + COutputGenerator, + CGeneratorOptions( + conventions = conventions, + filename = 'vulkan_sc_core.hpp', + directory = directory, + apiname = 'vulkansc', + profile = None, + versions = scVersions, + emitversions = scVersions, + defaultExtensions = 'vulkansc', + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48, + misracstyle = misracstyle, + misracppstyle = misracppstyle) + ] + + genOpts['vk.json'] = [ + SchemaOutputGenerator, + SchemaGeneratorOptions( + conventions = conventions, + filename = 'vk.json', + directory = directory, + apiname = 'vulkansc', + profile = None, + versions = scVersions, + emitversions = scVersions, + defaultExtensions = 'vulkansc', + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48) + ] + + if vulkanLayer: + genOpts['vulkan_json_data.hpp'] = [ + JSONOutputGenerator, + JSONGeneratorOptions( + conventions = conventions, + filename = 'vulkan_json_data.hpp', + directory = directory, + apiname = 'vulkan', + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = None, + addExtensions = addExtensionsPat, + removeExtensions = None, + emitExtensions = None, + vulkanLayer = vulkanLayer, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48) + ] + else: + genOpts['vulkan_json_data.hpp'] = [ + JSONOutputGenerator, + JSONGeneratorOptions( + conventions = conventions, + filename = 'vulkan_json_data.hpp', + directory = directory, + apiname = 'vulkansc', + profile = None, + versions = scVersions, + emitversions = scVersions, + defaultExtensions = 'vulkansc', + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + vulkanLayer = vulkanLayer, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + isCTS = isCTS, + alignFuncParam = 48) + ] + + # keep any relevant platform extensions for the following generators + # (needed for e.g. the vulkan_sci extensions) + explicitRemoveExtensionsPat = makeREstring( + removeExtensions, None, strings_are_regex=True) + + # Raw C header file generator. + genOpts['vulkan_json_gen.h'] = [ + JSONHeaderOutputGenerator, + JSONHeaderGeneratorOptions( + conventions = conventions, + filename = 'vulkan_json_gen.h', + directory = directory, + apiname = 'vulkansc', + profile = None, + versions = scVersions, + emitversions = scVersions, + defaultExtensions = 'vulkansc', + addExtensions = addExtensionsPat, + removeExtensions = explicitRemoveExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48) + ] + + # Raw C source file generator. + genOpts['vulkan_json_gen.c'] = [ + JSONCOutputGenerator, + JSONCGeneratorOptions( + conventions = conventions, + filename = 'vulkan_json_gen.c', + directory = directory, + apiname = 'vulkansc', + profile = None, + versions = scVersions, + emitversions = scVersions, + defaultExtensions = 'vulkansc', + addExtensions = addExtensionsPat, + removeExtensions = explicitRemoveExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48) + ] + + genOpts['vulkan_json_parser.hpp'] = [ + JSONParserGenerator, + JSONParserOptions( + conventions = conventions, + filename = 'vulkan_json_parser.hpp', + directory = directory, + apiname = 'vulkansc', + profile = None, + versions = scVersions, + emitversions = scVersions, + defaultExtensions = 'vulkansc', + addExtensions = addExtensionsPat, + removeExtensions = explicitRemoveExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + isCTS = isCTS, + alignFuncParam = 48) + ] + + # Unused - vulkan10.h target. + # It is possible to generate a header with just the Vulkan 1.0 + + # extension interfaces defined, but since the promoted KHR extensions + # are now defined in terms of the 1.1 interfaces, such a header is very + # similar to vulkan_core.h. + genOpts['vulkan10.h'] = [ + COutputGenerator, + CGeneratorOptions( + conventions = conventions, + filename = 'vulkan10.h', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = 'VK_VERSION_1_0', + emitversions = 'VK_VERSION_1_0', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + emitExtensions = None, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48, + misracstyle = misracstyle, + misracppstyle = misracppstyle) + ] + + # Video header target - combines all video extension dependencies into a + # single header, at present. + genOpts['vk_video.h'] = [ + COutputGenerator, + CGeneratorOptions( + conventions = conventions, + filename = 'vk_video.h', + directory = directory, + genpath = None, + apiname = 'vulkan', + profile = None, + versions = None, + emitversions = None, + defaultExtensions = defaultExtensions, + addExtensions = addExtensionsPat, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = '', + apientry = '', + apientryp = '', + alignFuncParam = 48, + misracstyle = misracstyle, + misracppstyle = misracppstyle) + ] + + # Video extension 'Std' interfaces, each in its own header files + # These are not Vulkan extensions, or a part of the Vulkan API at all. + # They are treated in a similar fashion for generation purposes, but + # all required APIs for each interface must be explicitly required. + # + # Each element of the videoStd[] array is an extension name defining an + # interface, and is also the basis for the generated header file name. + + videoStd = [ + 'vulkan_video_codecs_common', + 'vulkan_video_codec_h264std', + 'vulkan_video_codec_h264std_decode', + 'vulkan_video_codec_h264std_encode', + 'vulkan_video_codec_h265std', + 'vulkan_video_codec_h265std_decode', + 'vulkan_video_codec_h265std_encode', + ] + + # Unused at present + # addExtensionRE = makeREstring(videoStd) + for codec in videoStd: + headername = f'{codec}.h' + + # Consider all of the codecs 'extensions', but only emit this one + emitExtensionRE = makeREstring([codec]) + + opts = CGeneratorOptions( + conventions = conventions, + filename = headername, + directory = directory, + genpath = None, + apiname = defaultAPIName, + mergeApiNames = mergeApiNames, + profile = None, + versions = None, + emitversions = None, + defaultExtensions = None, + addExtensions = emitExtensionRE, + removeExtensions = None, + emitExtensions = emitExtensionRE, + requireDepends = False, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = False, + protectFile = protectFile, + protectFeature = False, + alignFuncParam = 48, + ) + + genOpts[headername] = [ COutputGenerator, opts ] + + # Unused - vulkan11.h target. + # It is possible to generate a header with just the Vulkan 1.0 + + # extension interfaces defined, but since the promoted KHR extensions + # are now defined in terms of the 1.1 interfaces, such a header is very + # similar to vulkan_core.h. + genOpts['vulkan11.h'] = [ + COutputGenerator, + CGeneratorOptions( + conventions = conventions, + filename = 'vulkan11.h', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = '^VK_VERSION_1_[01]$', + emitversions = '^VK_VERSION_1_[01]$', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + emitExtensions = None, + prefixText = prefixStrings + vkPrefixStrings, + genFuncPointers = True, + protectFile = protectFile, + protectFeature = False, + protectProto = '#ifndef', + protectProtoStr = 'VK_NO_PROTOTYPES', + apicall = 'VKAPI_ATTR ', + apientry = 'VKAPI_CALL ', + apientryp = 'VKAPI_PTR *', + alignFuncParam = 48, + misracstyle = misracstyle, + misracppstyle = misracppstyle) + ] + + genOpts['alias.h'] = [ + COutputGenerator, + CGeneratorOptions( + conventions = conventions, + filename = 'alias.h', + directory = directory, + genpath = None, + apiname = defaultAPIName, + profile = None, + versions = featuresPat, + emitversions = featuresPat, + defaultExtensions = defaultExtensions, + addExtensions = None, + removeExtensions = removeExtensionsPat, + emitExtensions = emitExtensionsPat, + prefixText = None, + genFuncPointers = False, + protectFile = False, + protectFeature = False, + protectProto = '', + protectProtoStr = '', + apicall = '', + apientry = '', + apientryp = '', + alignFuncParam = 36) + ] + + +def genTarget(args): + """Create an API generator and corresponding generator options based on + the requested target and command line options. + + This is encapsulated in a function so it can be profiled and/or timed. + The args parameter is an parsed argument object containing the following + fields that are used: + + - target - target to generate + - directory - directory to generate it in + - protect - True if re-inclusion wrappers should be created + - extensions - list of additional extensions to include in generated interfaces""" + + # Create generator options with parameters specified on command line + makeGenOpts(args) + + # Select a generator matching the requested target + if args.target in genOpts: + createGenerator = genOpts[args.target][0] + options = genOpts[args.target][1] + + logDiag('* Building', options.filename) + logDiag('* options.versions =', options.versions) + logDiag('* options.emitversions =', options.emitversions) + logDiag('* options.defaultExtensions =', options.defaultExtensions) + logDiag('* options.addExtensions =', options.addExtensions) + logDiag('* options.removeExtensions =', options.removeExtensions) + logDiag('* options.emitExtensions =', options.emitExtensions) + logDiag('* options.emitSpirv =', options.emitSpirv) + logDiag('* options.emitFormats =', options.emitFormats) + + gen = createGenerator(errFile=errWarn, + warnFile=errWarn, + diagFile=diag) + return (gen, options) + else: + logErr('No generator options for unknown target:', args.target) + return None + + +# -feature name +# -extension name +# For both, "name" may be a single name, or a space-separated list +# of names, or a regular expression. +if __name__ == '__main__': + parser = argparse.ArgumentParser() + + parser.add_argument('-apiname', action='store', + default=None, + help='Specify API to generate (defaults to repository-specific conventions object value)') + parser.add_argument('-mergeApiNames', action='store', + default=None, + help='Specify a comma separated list of APIs to merge into the target API') + parser.add_argument('-defaultExtensions', action='store', + default=APIConventions().xml_api_name, + help='Specify a single class of extensions to add to targets') + parser.add_argument('-extension', action='append', + default=[], + help='Specify an extension or extensions to add to targets') + parser.add_argument('-removeExtensions', action='append', + default=[], + help='Specify an extension or extensions to remove from targets') + parser.add_argument('-emitExtensions', action='append', + default=[], + help='Specify an extension or extensions to emit in targets') + parser.add_argument('-emitSpirv', action='append', + default=[], + help='Specify a SPIR-V extension or capability to emit in targets') + parser.add_argument('-emitFormats', action='append', + default=[], + help='Specify Vulkan Formats to emit in targets') + parser.add_argument('-feature', action='append', + default=[], + help='Specify a core API feature name or names to add to targets') + parser.add_argument('-debug', action='store_true', + help='Enable debugging') + parser.add_argument('-dump', action='store_true', + help='Enable dump to stderr') + parser.add_argument('-diagfile', action='store', + default=None, + help='Write diagnostics to specified file') + parser.add_argument('-errfile', action='store', + default=None, + help='Write errors and warnings to specified file instead of stderr') + parser.add_argument('-noprotect', dest='protect', action='store_false', + help='Disable inclusion protection in output headers') + parser.add_argument('-profile', action='store_true', + help='Enable profiling') + parser.add_argument('-registry', action='store', + default='vk.xml', + help='Use specified registry file instead of vk.xml') + parser.add_argument('-time', action='store_true', + help='Enable timing') + parser.add_argument('-genpath', action='store', default='gen', + help='Path to generated files') + parser.add_argument('-o', action='store', dest='directory', + default='.', + help='Create target and related files in specified directory') + parser.add_argument('target', metavar='target', nargs='?', + help='Specify target') + parser.add_argument('-quiet', action='store_true', default=True, + help='Suppress script output during normal execution.') + parser.add_argument('-verbose', action='store_false', dest='quiet', default=True, + help='Enable script output during normal execution.') + parser.add_argument('--vulkanLayer', action='store_true', dest='vulkanLayer', + help='Enable scripts to generate VK specific vulkan_json_data.hpp for json_gen_layer.') + parser.add_argument('-misracstyle', dest='misracstyle', action='store_true', + help='generate MISRA C-friendly headers') + parser.add_argument('-misracppstyle', dest='misracppstyle', action='store_true', + help='generate MISRA C++-friendly headers') + parser.add_argument('--iscts', action='store_true', dest='isCTS', + help='Specify if this should generate CTS compatible code') + + args = parser.parse_args() + + # This splits arguments which are space-separated lists + args.feature = [name for arg in args.feature for name in arg.split()] + args.extension = [name for arg in args.extension for name in arg.split()] + + # create error/warning & diagnostic files + if args.errfile: + errWarn = open(args.errfile, 'w', encoding='utf-8') + else: + errWarn = sys.stderr + + if args.diagfile: + diag = open(args.diagfile, 'w', encoding='utf-8') + else: + diag = None + + if args.time: + # Log diagnostics and warnings + setLogFile(setDiag = True, setWarn = True, filename = '-') + + # Create the API generator & generator options + (gen, options) = genTarget(args) + + # Create the registry object with the specified generator and generator + # options. The options are set before XML loading as they may affect it. + reg = Registry(gen, options) + + # Parse the specified registry XML into an ElementTree object + startTimer(args.time) + tree = etree.parse(args.registry) + endTimer(args.time, '* Time to make ElementTree =') + + # Load the XML tree into the registry object + startTimer(args.time) + reg.loadElementTree(tree) + endTimer(args.time, '* Time to parse ElementTree =') + + if args.dump: + logDiag('* Dumping registry to regdump.txt') + reg.dumpReg(filehandle=open('regdump.txt', 'w', encoding='utf-8')) + + # Finally, use the output generator to create the requested target + if args.debug: + pdb.run('reg.apiGen()') + else: + startTimer(args.time) + reg.apiGen() + endTimer(args.time, '* Time to generate ' + options.filename + ' =') + + if not args.quiet: + logDiag('* Generated', options.filename) diff --git a/src/gfxstream/codegen/scripts/reg.py b/src/gfxstream/codegen/scripts/reg.py new file mode 100644 index 00000000000..4b5a80f7139 --- /dev/null +++ b/src/gfxstream/codegen/scripts/reg.py @@ -0,0 +1,1779 @@ +#!/usr/bin/python3 -i +# +# Copyright 2013-2023 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Types and classes for manipulating an API registry.""" + +import copy +import re +import sys +import xml.etree.ElementTree as etree +from collections import defaultdict, deque, namedtuple + +from generator import GeneratorOptions, OutputGenerator, noneStr, write +from apiconventions import APIConventions + +def apiNameMatch(str, supported): + """Return whether a required api name matches a pattern specified for an + XML 'api' attribute or 'supported' attribute. + + - str - API name such as 'vulkan' or 'openxr'. May be None, in which + case it never matches (this should not happen). + - supported - comma-separated list of XML API names. May be None, in + which case str always matches (this is the usual case).""" + + if str is not None: + return supported is None or str in supported.split(',') + + # Fallthrough case - either str is None or the test failed + return False + +def matchAPIProfile(api, profile, elem): + """Return whether an API and profile + being generated matches an element's profile + + - api - string naming the API to match + - profile - string naming the profile to match + - elem - Element which (may) have 'api' and 'profile' + attributes to match to. + + If a tag is not present in the Element, the corresponding API + or profile always matches. + + Otherwise, the tag must exactly match the API or profile. + + Thus, if 'profile' = core: + + - `` with no attribute will match + - `` will match + - `` will not match + + Possible match conditions: + + ``` + Requested Element + Profile Profile + --------- -------- + None None Always matches + 'string' None Always matches + None 'string' Does not match. Cannot generate multiple APIs + or profiles, so if an API/profile constraint + is present, it must be asked for explicitly. + 'string' 'string' Strings must match + ``` + + ** In the future, we will allow regexes for the attributes, + not just strings, so that `api="^(gl|gles2)"` will match. Even + this is not really quite enough, we might prefer something + like `"gl(core)|gles1(common-lite)"`.""" + # Match 'api', if present + elem_api = elem.get('api') + if elem_api: + if api is None: + raise UserWarning("No API requested, but 'api' attribute is present with value '" + + elem_api + "'") + elif api != elem_api: + # Requested API does not match attribute + return False + elem_profile = elem.get('profile') + if elem_profile: + if profile is None: + raise UserWarning("No profile requested, but 'profile' attribute is present with value '" + + elem_profile + "'") + elif profile != elem_profile: + # Requested profile does not match attribute + return False + return True + + +def mergeAPIs(tree, fromApiNames, toApiName): + """Merge multiple APIs using the precedence order specified in apiNames. + Also deletes elements. + + tree - Element at the root of the hierarchy to merge. + apiNames - list of strings of API names.""" + + stack = deque() + stack.append(tree) + + while len(stack) > 0: + parent = stack.pop() + + for child in parent.findall('*'): + if child.tag == 'remove': + # Remove elements + parent.remove(child) + else: + stack.append(child) + + supportedList = child.get('supported') + if supportedList: + supportedList = supportedList.split(',') + for apiName in [toApiName] + fromApiNames: + if apiName in supportedList: + child.set('supported', toApiName) + + if child.get('api'): + definitionName = None + definitionVariants = [] + + # Keep only one definition with the same name if there are multiple definitions + if child.tag in ['type']: + if child.get('name') is not None: + definitionName = child.get('name') + definitionVariants = parent.findall(f"{child.tag}[@name='{definitionName}']") + else: + definitionName = child.find('name').text + definitionVariants = parent.findall(f"{child.tag}/name[.='{definitionName}']/..") + elif child.tag in ['member', 'param']: + definitionName = child.find('name').text + definitionVariants = parent.findall(f"{child.tag}/name[.='{definitionName}']/..") + elif child.tag in ['enum', 'feature']: + definitionName = child.get('name') + definitionVariants = parent.findall(f"{child.tag}[@name='{definitionName}']") + elif child.tag in ['require']: + definitionName = child.get('feature') + definitionVariants = parent.findall(f"{child.tag}[@feature='{definitionName}']") + elif child.tag in ['command']: + definitionName = child.find('proto/name').text + definitionVariants = parent.findall(f"{child.tag}/proto/name[.='{definitionName}']/../..") + + if definitionName: + bestMatchApi = None + requires = None + for apiName in [toApiName] + fromApiNames: + for variant in definitionVariants: + # Keep any requires attributes from the target API + if variant.get('requires') and variant.get('api') == apiName: + requires = variant.get('requires') + # Find the best matching definition + if apiName in variant.get('api').split(',') and bestMatchApi is None: + bestMatchApi = variant.get('api') + + if bestMatchApi: + for variant in definitionVariants: + if variant.get('api') != bestMatchApi: + # Only keep best matching definition + parent.remove(variant) + else: + # Add requires attribute from the target API if it is not overridden + if requires is not None and variant.get('requires') is None: + variant.set('requires', requires) + variant.set('api', toApiName) + + +def stripNonmatchingAPIs(tree, apiName, actuallyDelete = True): + """Remove tree Elements with 'api' attributes matching apiName. + + tree - Element at the root of the hierarchy to strip. Only its + children can actually be removed, not the tree itself. + apiName - string which much match a command-separated component of + the 'api' attribute. + actuallyDelete - only delete matching elements if True.""" + + stack = deque() + stack.append(tree) + + while len(stack) > 0: + parent = stack.pop() + + for child in parent.findall('*'): + api = child.get('api') + + if apiNameMatch(apiName, api): + # Add child to the queue + stack.append(child) + elif not apiNameMatch(apiName, api): + # Child does not match requested api. Remove it. + if actuallyDelete: + parent.remove(child) + + +class BaseInfo: + """Base class for information about a registry feature + (type/group/enum/command/API/extension). + + Represents the state of a registry feature, used during API generation. + """ + + def __init__(self, elem): + self.required = False + """should this feature be defined during header generation + (has it been removed by a profile or version)?""" + + self.declared = False + "has this feature been defined already?" + + self.elem = elem + "etree Element for this feature" + + def resetState(self): + """Reset required/declared to initial values. Used + prior to generating a new API interface.""" + self.required = False + self.declared = False + + def compareKeys(self, info, key, required = False): + """Return True if self.elem and info.elem have the same attribute + value for key. + If 'required' is not True, also returns True if neither element + has an attribute value for key.""" + + if required and key not in self.elem.keys(): + return False + return self.elem.get(key) == info.elem.get(key) + + def compareElem(self, info, infoName): + """Return True if self.elem and info.elem have the same definition. + info - the other object + infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / + 'extension'""" + + if infoName == 'enum': + if self.compareKeys(info, 'extends'): + # Either both extend the same type, or no type + if (self.compareKeys(info, 'value', required = True) or + self.compareKeys(info, 'bitpos', required = True)): + # If both specify the same value or bit position, + # they are equal + return True + elif (self.compareKeys(info, 'extnumber') and + self.compareKeys(info, 'offset') and + self.compareKeys(info, 'dir')): + # If both specify the same relative offset, they are equal + return True + elif (self.compareKeys(info, 'alias')): + # If both are aliases of the same value + return True + else: + return False + else: + # The same enum cannot extend two different types + return False + else: + # Non-s should never be redefined + return False + + +class TypeInfo(BaseInfo): + """Registry information about a type. No additional state + beyond BaseInfo is required.""" + + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.additionalValidity = [] + self.removedValidity = [] + + def getMembers(self): + """Get a collection of all member elements for this type, if any.""" + return self.elem.findall('member') + + def resetState(self): + BaseInfo.resetState(self) + self.additionalValidity = [] + self.removedValidity = [] + + +class GroupInfo(BaseInfo): + """Registry information about a group of related enums + in an block, generally corresponding to a C "enum" type.""" + + def __init__(self, elem): + BaseInfo.__init__(self, elem) + + +class EnumInfo(BaseInfo): + """Registry information about an enum""" + + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.type = elem.get('type') + """numeric type of the value of the tag + ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 )""" + if self.type is None: + self.type = '' + + +class CmdInfo(BaseInfo): + """Registry information about a command""" + + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.additionalValidity = [] + self.removedValidity = [] + + def getParams(self): + """Get a collection of all param elements for this command, if any.""" + return self.elem.findall('param') + + def resetState(self): + BaseInfo.resetState(self) + self.additionalValidity = [] + self.removedValidity = [] + + +class FeatureInfo(BaseInfo): + """Registry information about an API + or .""" + + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.name = elem.get('name') + "feature name string (e.g. 'VK_KHR_surface')" + + self.emit = False + "has this feature been defined already?" + + self.sortorder = int(elem.get('sortorder', 0)) + """explicit numeric sort key within feature and extension groups. + Defaults to 0.""" + + # Determine element category (vendor). Only works + # for elements. + if elem.tag == 'feature': + # Element category (vendor) is meaningless for + self.category = 'VERSION' + """category, e.g. VERSION or khr/vendor tag""" + + self.version = elem.get('name') + """feature name string""" + + self.versionNumber = elem.get('number') + """versionNumber - API version number, taken from the 'number' + attribute of . Extensions do not have API version + numbers and are assigned number 0.""" + + self.number = 0 + self.supported = None + else: + # Extract vendor portion of __ + self.category = self.name.split('_', 2)[1] + self.version = "0" + self.versionNumber = "0" + + self.number = int(elem.get('number','0')) + """extension number, used for ordering and for assigning + enumerant offsets. features do not have extension + numbers and are assigned number 0, as are extensions without + numbers, so sorting works.""" + + self.supported = elem.get('supported', 'disabled') + +class SpirvInfo(BaseInfo): + """Registry information about an API + or .""" + + def __init__(self, elem): + BaseInfo.__init__(self, elem) + +class FormatInfo(BaseInfo): + """Registry information about an API .""" + + def __init__(self, elem, condition): + BaseInfo.__init__(self, elem) + # Need to save the condition here when it is known + self.condition = condition + +class SyncStageInfo(BaseInfo): + """Registry information about .""" + + def __init__(self, elem, condition): + BaseInfo.__init__(self, elem) + # Need to save the condition here when it is known + self.condition = condition + +class SyncAccessInfo(BaseInfo): + """Registry information about .""" + + def __init__(self, elem, condition): + BaseInfo.__init__(self, elem) + # Need to save the condition here when it is known + self.condition = condition + +class SyncPipelineInfo(BaseInfo): + """Registry information about .""" + + def __init__(self, elem): + BaseInfo.__init__(self, elem) + +class Registry: + """Object representing an API registry, loaded from an XML file.""" + + def __init__(self, gen=None, genOpts=None): + if gen is None: + # If not specified, give a default object so messaging will work + self.gen = OutputGenerator() + else: + self.gen = gen + "Output generator used to write headers / messages" + + if genOpts is None: + # If no generator is provided, we may still need the XML API name + # (for example, in genRef.py). + self.genOpts = GeneratorOptions(apiname = APIConventions().xml_api_name) + else: + self.genOpts = genOpts + "Options controlling features to write and how to format them" + + self.gen.registry = self + self.gen.genOpts = self.genOpts + self.gen.genOpts.registry = self + + self.tree = None + "ElementTree containing the root ``" + + self.typedict = {} + "dictionary of TypeInfo objects keyed by type name" + + self.groupdict = {} + "dictionary of GroupInfo objects keyed by group name" + + self.enumdict = {} + "dictionary of EnumInfo objects keyed by enum name" + + self.cmddict = {} + "dictionary of CmdInfo objects keyed by command name" + + self.apidict = {} + "dictionary of FeatureInfo objects for `` elements keyed by API name" + + self.extensions = [] + "list of `` Elements" + + self.extdict = {} + "dictionary of FeatureInfo objects for `` elements keyed by extension name" + + self.spirvextdict = {} + "dictionary of FeatureInfo objects for `` elements keyed by spirv extension name" + + self.spirvcapdict = {} + "dictionary of FeatureInfo objects for `` elements keyed by spirv capability name" + + self.formatsdict = {} + "dictionary of FeatureInfo objects for `` elements keyed by VkFormat name" + + self.syncstagedict = {} + "dictionary of Sync*Info objects for `` elements keyed by VkPipelineStageFlagBits2 name" + + self.syncaccessdict = {} + "dictionary of Sync*Info objects for `` elements keyed by VkAccessFlagBits2 name" + + self.syncpipelinedict = {} + "dictionary of Sync*Info objects for `` elements keyed by pipeline type name" + + self.emitFeatures = False + """True to actually emit features for a version / extension, + or False to just treat them as emitted""" + + self.breakPat = None + "regexp pattern to break on when generating names" + # self.breakPat = re.compile('VkFenceImportFlagBits.*') + + self.requiredextensions = [] # Hack - can remove it after validity generator goes away + + # ** Global types for automatic source generation ** + # Length Member data + self.commandextensiontuple = namedtuple('commandextensiontuple', + ['command', # The name of the command being modified + 'value', # The value to append to the command + 'extension']) # The name of the extension that added it + self.validextensionstructs = defaultdict(list) + self.commandextensionsuccesses = [] + self.commandextensionerrors = [] + + self.filename = None + + def loadElementTree(self, tree): + """Load ElementTree into a Registry object and parse it.""" + self.tree = tree + self.parseTree() + + def loadFile(self, file): + """Load an API registry XML file into a Registry object and parse it""" + self.filename = file + self.tree = etree.parse(file) + self.parseTree() + + def setGenerator(self, gen): + """Specify output generator object. + + `None` restores the default generator.""" + self.gen = gen + self.gen.setRegistry(self) + + def addElementInfo(self, elem, info, infoName, dictionary): + """Add information about an element to the corresponding dictionary. + + Intended for internal use only. + + - elem - ``/``/``/``/``/``/``/``/``/``/``/`` Element + - info - corresponding {Type|Group|Enum|Cmd|Feature|Spirv|Format|SyncStage|SyncAccess|SyncPipeline}Info object + - infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' / 'spirvextension' / 'spirvcapability' / 'format' / 'syncstage' / 'syncaccess' / 'syncpipeline' + - dictionary - self.{type|group|enum|cmd|api|ext|format|spirvext|spirvcap|sync}dict + + The dictionary key is the element 'name' attribute.""" + + # self.gen.logMsg('diag', 'Adding ElementInfo.required =', + # info.required, 'name =', elem.get('name')) + key = elem.get('name') + if key in dictionary: + if not dictionary[key].compareElem(info, infoName): + self.gen.logMsg('warn', 'Attempt to redefine', key, + '(this should not happen)') + else: + dictionary[key] = info + + def lookupElementInfo(self, fname, dictionary): + """Find a {Type|Enum|Cmd}Info object by name. + + Intended for internal use only. + + If an object qualified by API name exists, use that. + + - fname - name of type / enum / command + - dictionary - self.{type|enum|cmd}dict""" + key = (fname, self.genOpts.apiname) + if key in dictionary: + # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) + return dictionary[key] + if fname in dictionary: + # self.gen.logMsg('diag', 'Found generic element for feature', fname) + return dictionary[fname] + + return None + + def breakOnName(self, regexp): + """Specify a feature name regexp to break on when generating features.""" + self.breakPat = re.compile(regexp) + + def parseTree(self): + """Parse the registry Element, once created""" + # This must be the Element for the root + if self.tree is None: + raise RuntimeError("Tree not initialized!") + self.reg = self.tree.getroot() + + # Preprocess the tree in one of the following ways: + # - either merge a set of APIs to another API based on their 'api' attributes + # - or remove all elements with non-matching 'api' attributes + # The preprocessing happens through a breath-first tree traversal. + # This is a blunt hammer, but eliminates the need to track and test + # the apis deeper in processing to select the correct elements and + # avoid duplicates. + # Schema validation should prevent duplicate elements with + # overlapping api attributes, or where one element has an api + # attribute and the other does not. + + if self.genOpts.mergeApiNames: + mergeAPIs(self.reg, self.genOpts.mergeApiNames.split(','), self.genOpts.apiname) + else: + stripNonmatchingAPIs(self.reg, self.genOpts.apiname, actuallyDelete = True) + + # Create dictionary of registry types from toplevel tags + # and add 'name' attribute to each tag (where missing) + # based on its element. + # + # There is usually one block; more are OK + # Required attributes: 'name' or nested tag contents + self.typedict = {} + for type_elem in self.reg.findall('types/type'): + # If the does not already have a 'name' attribute, set + # it from contents of its tag. + if type_elem.get('name') is None: + name_elem = type_elem.find('name') + if name_elem is None or not name_elem.text: + raise RuntimeError("Type without a name!") + type_elem.set('name', name_elem.text) + self.addElementInfo(type_elem, TypeInfo(type_elem), 'type', self.typedict) + + # Create dictionary of registry enum groups from tags. + # + # Required attributes: 'name'. If no name is given, one is + # generated, but that group cannot be identified and turned into an + # enum type definition - it is just a container for tags. + self.groupdict = {} + for group in self.reg.findall('enums'): + self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) + + # Create dictionary of registry enums from tags + # + # tags usually define different namespaces for the values + # defined in those tags, but the actual names all share the + # same dictionary. + # Required attributes: 'name', 'value' + # For containing which have type="enum" or type="bitmask", + # tag all contained s are required. This is a stopgap until + # a better scheme for tagging core and extension enums is created. + self.enumdict = {} + for enums in self.reg.findall('enums'): + required = (enums.get('type') is not None) + for enum in enums.findall('enum'): + enumInfo = EnumInfo(enum) + enumInfo.required = required + self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) + + # Create dictionary of registry commands from tags + # and add 'name' attribute to each tag (where missing) + # based on its element. + # + # There is usually only one block; more are OK. + # Required attributes: 'name' or tag contents + self.cmddict = {} + # List of commands which alias others. Contains + # [ aliasName, element ] + # for each alias + cmdAlias = [] + for cmd in self.reg.findall('commands/command'): + # If the does not already have a 'name' attribute, set + # it from contents of its tag. + name = cmd.get('name') + if name is None: + name_elem = cmd.find('proto/name') + if name_elem is None or not name_elem.text: + raise RuntimeError("Command without a name!") + name = cmd.set('name', name_elem.text) + ci = CmdInfo(cmd) + self.addElementInfo(cmd, ci, 'command', self.cmddict) + alias = cmd.get('alias') + if alias: + cmdAlias.append([name, alias, cmd]) + + # Now loop over aliases, injecting a copy of the aliased command's + # Element with the aliased prototype name replaced with the command + # name - if it exists. + for (name, alias, cmd) in cmdAlias: + if alias in self.cmddict: + aliasInfo = self.cmddict[alias] + cmdElem = copy.deepcopy(aliasInfo.elem) + cmdElem.find('proto/name').text = name + cmdElem.set('name', name) + cmdElem.set('alias', alias) + ci = CmdInfo(cmdElem) + # Replace the dictionary entry for the CmdInfo element + self.cmddict[name] = ci + + # @ newString = etree.tostring(base, encoding="unicode").replace(aliasValue, aliasName) + # @elem.append(etree.fromstring(replacement)) + else: + self.gen.logMsg('warn', 'No matching found for command', + cmd.get('name'), 'alias', alias) + + # Create dictionaries of API and extension interfaces + # from toplevel and tags. + self.apidict = {} + format_condition = dict() + for feature in self.reg.findall('feature'): + featureInfo = FeatureInfo(feature) + self.addElementInfo(feature, featureInfo, 'feature', self.apidict) + + # Add additional enums defined only in tags + # to the corresponding enumerated type. + # When seen here, the element, processed to contain the + # numeric enum value, is added to the corresponding + # element, as well as adding to the enum dictionary. It is no + # longer removed from the element it is introduced in. + # Instead, generateRequiredInterface ignores elements + # that extend enumerated types. + # + # For tags which are actually just constants, if there is + # no 'extends' tag but there is a 'value' or 'bitpos' tag, just + # add an EnumInfo record to the dictionary. That works because + # output generation of constants is purely dependency-based, and + # does not need to iterate through the XML tags. + for elem in feature.findall('require'): + for enum in elem.findall('enum'): + addEnumInfo = False + groupName = enum.get('extends') + if groupName is not None: + # self.gen.logMsg('diag', 'Found extension enum', + # enum.get('name')) + # Add version number attribute to the element + enum.set('version', featureInfo.version) + # Look up the GroupInfo with matching groupName + if groupName in self.groupdict: + # self.gen.logMsg('diag', 'Matching group', + # groupName, 'found, adding element...') + gi = self.groupdict[groupName] + gi.elem.append(copy.deepcopy(enum)) + else: + self.gen.logMsg('warn', 'NO matching group', + groupName, 'for enum', enum.get('name'), 'found.') + if groupName == "VkFormat": + format_name = enum.get('name') + if enum.get('alias'): + format_name = enum.get('alias') + format_condition[format_name] = featureInfo.name + addEnumInfo = True + elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): + # self.gen.logMsg('diag', 'Adding extension constant "enum"', + # enum.get('name')) + addEnumInfo = True + if addEnumInfo: + enumInfo = EnumInfo(enum) + self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) + + sync_pipeline_stage_condition = dict() + sync_access_condition = dict() + + self.extensions = self.reg.findall('extensions/extension') + self.extdict = {} + for feature in self.extensions: + featureInfo = FeatureInfo(feature) + self.addElementInfo(feature, featureInfo, 'extension', self.extdict) + + # Add additional enums defined only in tags + # to the corresponding core type. + # Algorithm matches that of enums in a "feature" tag as above. + # + # This code also adds a 'extnumber' attribute containing the + # extension number, used for enumerant value calculation. + for elem in feature.findall('require'): + for enum in elem.findall('enum'): + addEnumInfo = False + groupName = enum.get('extends') + if groupName is not None: + # self.gen.logMsg('diag', 'Found extension enum', + # enum.get('name')) + + # Add block's extension number attribute to + # the element unless specified explicitly, such + # as when redefining an enum in another extension. + extnumber = enum.get('extnumber') + if not extnumber: + enum.set('extnumber', str(featureInfo.number)) + + enum.set('extname', featureInfo.name) + enum.set('supported', noneStr(featureInfo.supported)) + # Look up the GroupInfo with matching groupName + if groupName in self.groupdict: + # self.gen.logMsg('diag', 'Matching group', + # groupName, 'found, adding element...') + gi = self.groupdict[groupName] + gi.elem.append(copy.deepcopy(enum)) + else: + self.gen.logMsg('warn', 'NO matching group', + groupName, 'for enum', enum.get('name'), 'found.') + # This is Vulkan-specific + if groupName == "VkFormat": + format_name = enum.get('name') + if enum.get('alias'): + format_name = enum.get('alias') + if format_name in format_condition: + format_condition[format_name] += "," + featureInfo.name + else: + format_condition[format_name] = featureInfo.name + elif groupName == "VkPipelineStageFlagBits2": + stage_flag = enum.get('name') + if enum.get('alias'): + stage_flag = enum.get('alias') + featureName = elem.get('depends') if elem.get('depends') is not None else featureInfo.name + if stage_flag in sync_pipeline_stage_condition: + sync_pipeline_stage_condition[stage_flag] += "," + featureName + else: + sync_pipeline_stage_condition[stage_flag] = featureName + elif groupName == "VkAccessFlagBits2": + access_flag = enum.get('name') + if enum.get('alias'): + access_flag = enum.get('alias') + featureName = elem.get('depends') if elem.get('depends') is not None else featureInfo.name + if access_flag in sync_access_condition: + sync_access_condition[access_flag] += "," + featureName + else: + sync_access_condition[access_flag] = featureName + + addEnumInfo = True + elif enum.get('value') or enum.get('bitpos') or enum.get('alias'): + # self.gen.logMsg('diag', 'Adding extension constant "enum"', + # enum.get('name')) + addEnumInfo = True + if addEnumInfo: + enumInfo = EnumInfo(enum) + self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) + + # Parse out all spirv tags in dictionaries + # Use addElementInfo to catch duplicates + for spirv in self.reg.findall('spirvextensions/spirvextension'): + spirvInfo = SpirvInfo(spirv) + self.addElementInfo(spirv, spirvInfo, 'spirvextension', self.spirvextdict) + for spirv in self.reg.findall('spirvcapabilities/spirvcapability'): + spirvInfo = SpirvInfo(spirv) + self.addElementInfo(spirv, spirvInfo, 'spirvcapability', self.spirvcapdict) + + for format in self.reg.findall('formats/format'): + condition = None + format_name = format.get('name') + if format_name in format_condition: + condition = format_condition[format_name] + formatInfo = FormatInfo(format, condition) + self.addElementInfo(format, formatInfo, 'format', self.formatsdict) + + for stage in self.reg.findall('sync/syncstage'): + condition = None + stage_flag = stage.get('name') + if stage_flag in sync_pipeline_stage_condition: + condition = sync_pipeline_stage_condition[stage_flag] + syncInfo = SyncStageInfo(stage, condition) + self.addElementInfo(stage, syncInfo, 'syncstage', self.syncstagedict) + + for access in self.reg.findall('sync/syncaccess'): + condition = None + access_flag = access.get('name') + if access_flag in sync_access_condition: + condition = sync_access_condition[access_flag] + syncInfo = SyncAccessInfo(access, condition) + self.addElementInfo(access, syncInfo, 'syncaccess', self.syncaccessdict) + + for pipeline in self.reg.findall('sync/syncpipeline'): + syncInfo = SyncPipelineInfo(pipeline) + self.addElementInfo(pipeline, syncInfo, 'syncpipeline', self.syncpipelinedict) + + def dumpReg(self, maxlen=120, filehandle=sys.stdout): + """Dump all the dictionaries constructed from the Registry object. + + Diagnostic to dump the dictionaries to specified file handle (default stdout). + Truncates type / enum / command elements to maxlen characters (default 120)""" + write('***************************************', file=filehandle) + write(' ** Dumping Registry contents **', file=filehandle) + write('***************************************', file=filehandle) + write('// Types', file=filehandle) + for name in self.typedict: + tobj = self.typedict[name] + write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) + write('// Groups', file=filehandle) + for name in self.groupdict: + gobj = self.groupdict[name] + write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) + write('// Enums', file=filehandle) + for name in self.enumdict: + eobj = self.enumdict[name] + write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) + write('// Commands', file=filehandle) + for name in self.cmddict: + cobj = self.cmddict[name] + write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) + write('// APIs', file=filehandle) + for key in self.apidict: + write(' API Version ', key, '->', + etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) + write('// Extensions', file=filehandle) + for key in self.extdict: + write(' Extension', key, '->', + etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) + write('// SPIR-V', file=filehandle) + for key in self.spirvextdict: + write(' SPIR-V Extension', key, '->', + etree.tostring(self.spirvextdict[key].elem)[0:maxlen], file=filehandle) + for key in self.spirvcapdict: + write(' SPIR-V Capability', key, '->', + etree.tostring(self.spirvcapdict[key].elem)[0:maxlen], file=filehandle) + write('// VkFormat', file=filehandle) + for key in self.formatsdict: + write(' VkFormat', key, '->', + etree.tostring(self.formatsdict[key].elem)[0:maxlen], file=filehandle) + + def markTypeRequired(self, typename, required): + """Require (along with its dependencies) or remove (but not its dependencies) a type. + + - typename - name of type + - required - boolean (to tag features as required or not) + """ + self.gen.logMsg('diag', 'tagging type:', typename, '-> required =', required) + + # Get TypeInfo object for tag corresponding to typename + typeinfo = self.lookupElementInfo(typename, self.typedict) + if typeinfo is not None: + if required: + # Tag type dependencies in 'alias' and 'required' attributes as + # required. This does not un-tag dependencies in a + # tag. See comments in markRequired() below for the reason. + for attrib_name in ['requires', 'alias']: + depname = typeinfo.elem.get(attrib_name) + if depname: + self.gen.logMsg('diag', 'Generating dependent type', + depname, 'for', attrib_name, 'type', typename) + # Do not recurse on self-referential structures. + if typename != depname: + self.markTypeRequired(depname, required) + else: + self.gen.logMsg('diag', 'type', typename, 'is self-referential') + # Tag types used in defining this type (e.g. in nested + # tags) + # Look for in entire tree, + # not just immediate children + for subtype in typeinfo.elem.findall('.//type'): + self.gen.logMsg('diag', 'markRequired: type requires dependent ', subtype.text) + if typename != subtype.text: + self.markTypeRequired(subtype.text, required) + else: + self.gen.logMsg('diag', 'type', typename, 'is self-referential') + # Tag enums used in defining this type, for example in + # member[MEMBER_SIZE] + for subenum in typeinfo.elem.findall('.//enum'): + self.gen.logMsg('diag', 'markRequired: type requires dependent ', subenum.text) + self.markEnumRequired(subenum.text, required) + # Tag type dependency in 'bitvalues' attributes as + # required. This ensures that the bit values for a flag + # are emitted + depType = typeinfo.elem.get('bitvalues') + if depType: + self.gen.logMsg('diag', 'Generating bitflag type', + depType, 'for type', typename) + self.markTypeRequired(depType, required) + group = self.lookupElementInfo(depType, self.groupdict) + if group is not None: + group.flagType = typeinfo + + typeinfo.required = required + elif '.h' not in typename: + self.gen.logMsg('warn', 'type:', typename, 'IS NOT DEFINED') + + def markEnumRequired(self, enumname, required): + """Mark an enum as required or not. + + - enumname - name of enum + - required - boolean (to tag features as required or not)""" + + self.gen.logMsg('diag', 'markEnumRequired: tagging enum:', enumname, '-> required =', required) + enum = self.lookupElementInfo(enumname, self.enumdict) + if enum is not None: + # If the enum is part of a group, and is being removed, then + # look it up in that tag and remove the Element there, + # so that it is not visible to generators (which traverse the + # tag elements rather than using the dictionaries). + if not required: + groupName = enum.elem.get('extends') + if groupName is not None: + self.gen.logMsg('diag', f'markEnumRequired: Removing extending enum {enum.elem.get("name")}') + + # Look up the Info with matching groupName + if groupName in self.groupdict: + gi = self.groupdict[groupName] + gienum = gi.elem.find("enum[@name='" + enumname + "']") + if gienum is not None: + # Remove copy of this enum from the group + gi.elem.remove(gienum) + else: + self.gen.logMsg('warn', 'markEnumRequired: Cannot remove enum', + enumname, 'not found in group', + groupName) + else: + self.gen.logMsg('warn', 'markEnumRequired: Cannot remove enum', + enumname, 'from nonexistent group', + groupName) + else: + # This enum is not an extending enum. + # The XML tree must be searched for all that + # might have it, so we know the parent to delete from. + + enumName = enum.elem.get('name') + + self.gen.logMsg('diag', f'markEnumRequired: Removing non-extending enum {enumName}') + + count = 0 + for enums in self.reg.findall('enums'): + for thisEnum in enums.findall('enum'): + if thisEnum.get('name') == enumName: + # Actually remove it + count = count + 1 + enums.remove(thisEnum) + + if count == 0: + self.gen.logMsg('warn', f'markEnumRequired: {enumName}) not found in any tag') + + enum.required = required + # Tag enum dependencies in 'alias' attribute as required + depname = enum.elem.get('alias') + if depname: + self.gen.logMsg('diag', 'markEnumRequired: Generating dependent enum', + depname, 'for alias', enumname, 'required =', enum.required) + self.markEnumRequired(depname, required) + else: + self.gen.logMsg('warn', f'markEnumRequired: {enumname} IS NOT DEFINED') + + def markCmdRequired(self, cmdname, required): + """Mark a command as required or not. + + - cmdname - name of command + - required - boolean (to tag features as required or not)""" + self.gen.logMsg('diag', 'tagging command:', cmdname, '-> required =', required) + cmd = self.lookupElementInfo(cmdname, self.cmddict) + if cmd is not None: + cmd.required = required + + # Tag command dependencies in 'alias' attribute as required + # + # This is usually not done, because command 'aliases' are not + # actual C language aliases like type and enum aliases. Instead + # they are just duplicates of the function signature of the + # alias. This means that there is no dependency of a command + # alias on what it aliases. One exception is validity includes, + # where the spec markup needs the promoted-to validity include + # even if only the promoted-from command is being built. + if self.genOpts.requireCommandAliases: + depname = cmd.elem.get('alias') + if depname: + self.gen.logMsg('diag', 'Generating dependent command', + depname, 'for alias', cmdname) + self.markCmdRequired(depname, required) + + # Tag all parameter types of this command as required. + # This does not remove types of commands in a + # tag, because many other commands may use the same type. + # We could be more clever and reference count types, + # instead of using a boolean. + if required: + # Look for in entire tree, + # not just immediate children + for type_elem in cmd.elem.findall('.//type'): + self.gen.logMsg('diag', 'markRequired: command implicitly requires dependent type', type_elem.text) + self.markTypeRequired(type_elem.text, required) + else: + self.gen.logMsg('warn', 'command:', cmdname, 'IS NOT DEFINED') + + def markRequired(self, featurename, feature, required): + """Require or remove features specified in the Element. + + - featurename - name of the feature + - feature - Element for `` or `` tag + - required - boolean (to tag features as required or not)""" + self.gen.logMsg('diag', 'markRequired (feature = , required =', required, ')') + + # Loop over types, enums, and commands in the tag + # @@ It would be possible to respect 'api' and 'profile' attributes + # in individual features, but that is not done yet. + for typeElem in feature.findall('type'): + self.markTypeRequired(typeElem.get('name'), required) + for enumElem in feature.findall('enum'): + self.markEnumRequired(enumElem.get('name'), required) + + for cmdElem in feature.findall('command'): + self.markCmdRequired(cmdElem.get('name'), required) + + # Extensions may need to extend existing commands or other items in the future. + # So, look for extend tags. + for extendElem in feature.findall('extend'): + extendType = extendElem.get('type') + if extendType == 'command': + commandName = extendElem.get('name') + successExtends = extendElem.get('successcodes') + if successExtends is not None: + for success in successExtends.split(','): + self.commandextensionsuccesses.append(self.commandextensiontuple(command=commandName, + value=success, + extension=featurename)) + errorExtends = extendElem.get('errorcodes') + if errorExtends is not None: + for error in errorExtends.split(','): + self.commandextensionerrors.append(self.commandextensiontuple(command=commandName, + value=error, + extension=featurename)) + else: + self.gen.logMsg('warn', 'extend type:', extendType, 'IS NOT SUPPORTED') + + def getAlias(self, elem, dict): + """Check for an alias in the same require block. + + - elem - Element to check for an alias""" + + # Try to find an alias + alias = elem.get('alias') + if alias is None: + name = elem.get('name') + typeinfo = self.lookupElementInfo(name, dict) + if not typeinfo: + self.gen.logMsg('error', name, 'is not a known name') + alias = typeinfo.elem.get('alias') + + return alias + + def checkForCorrectionAliases(self, alias, require, tag): + """Check for an alias in the same require block. + + - alias - String name of the alias + - require - `` block from the registry + - tag - tag to look for in the require block""" + + # For the time being, the code below is bypassed. It has the effect + # of excluding "spelling aliases" created to comply with the style + # guide, but this leaves references out of the specification and + # causes broken internal links. + # + # if alias and require.findall(tag + "[@name='" + alias + "']"): + # return True + + return False + + def fillFeatureDictionary(self, interface, featurename, api, profile): + """Capture added interfaces for a `` or ``. + + - interface - Element for `` or ``, containing + `` and `` tags + - featurename - name of the feature + - api - string specifying API name being generated + - profile - string specifying API profile being generated""" + + # Explicitly initialize known types - errors for unhandled categories + self.gen.featureDictionary[featurename] = { + "enumconstant": {}, + "command": {}, + "enum": {}, + "struct": {}, + "handle": {}, + "basetype": {}, + "include": {}, + "define": {}, + "bitmask": {}, + "union": {}, + "funcpointer": {}, + } + + # marks things that are required by this version/profile + for require in interface.findall('require'): + if matchAPIProfile(api, profile, require): + + # Determine the required extension or version needed for a require block + # Assumes that only one of these is specified + # 'extension', and therefore 'required_key', may be a boolean + # expression of extension names. + # 'required_key' is used only as a dictionary key at + # present, and passed through to the script generators, so + # they must be prepared to parse that boolean expression. + required_key = require.get('depends') + + # Loop over types, enums, and commands in the tag + for typeElem in require.findall('type'): + typename = typeElem.get('name') + typeinfo = self.lookupElementInfo(typename, self.typedict) + + if typeinfo: + # Remove aliases in the same extension/feature; these are always added as a correction. Do not need the original to be visible. + alias = self.getAlias(typeElem, self.typedict) + if not self.checkForCorrectionAliases(alias, require, 'type'): + # Resolve the type info to the actual type, so we get an accurate read for 'structextends' + while alias: + typeinfo = self.lookupElementInfo(alias, self.typedict) + alias = typeinfo.elem.get('alias') + + typecat = typeinfo.elem.get('category') + typeextends = typeinfo.elem.get('structextends') + if not required_key in self.gen.featureDictionary[featurename][typecat]: + self.gen.featureDictionary[featurename][typecat][required_key] = {} + if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]: + self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = [] + self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename) + else: + self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) + + + for enumElem in require.findall('enum'): + enumname = enumElem.get('name') + typeinfo = self.lookupElementInfo(enumname, self.enumdict) + + # Remove aliases in the same extension/feature; these are always added as a correction. Do not need the original to be visible. + alias = self.getAlias(enumElem, self.enumdict) + if not self.checkForCorrectionAliases(alias, require, 'enum'): + enumextends = enumElem.get('extends') + if not required_key in self.gen.featureDictionary[featurename]['enumconstant']: + self.gen.featureDictionary[featurename]['enumconstant'][required_key] = {} + if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]: + self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = [] + self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname) + else: + self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) + + for cmdElem in require.findall('command'): + # Remove aliases in the same extension/feature; these are always added as a correction. Do not need the original to be visible. + alias = self.getAlias(cmdElem, self.cmddict) + if not self.checkForCorrectionAliases(alias, require, 'command'): + if not required_key in self.gen.featureDictionary[featurename]['command']: + self.gen.featureDictionary[featurename]['command'][required_key] = [] + self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name')) + else: + self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename)) + + def requireFeatures(self, interface, featurename, api, profile): + """Process `` tags for a `` or ``. + + - interface - Element for `` or ``, containing + `` tags + - featurename - name of the feature + - api - string specifying API name being generated + - profile - string specifying API profile being generated""" + + # marks things that are required by this version/profile + for feature in interface.findall('require'): + if matchAPIProfile(api, profile, feature): + self.markRequired(featurename, feature, True) + + def removeFeatures(self, interface, featurename, api, profile): + """Process `` tags for a `` or ``. + + - interface - Element for `` or ``, containing + `` tags + - featurename - name of the feature + - api - string specifying API name being generated + - profile - string specifying API profile being generated""" + + # marks things that are removed by this version/profile + for feature in interface.findall('remove'): + if matchAPIProfile(api, profile, feature): + self.markRequired(featurename, feature, False) + + def assignAdditionalValidity(self, interface, api, profile): + # Loop over all usage inside all tags. + for feature in interface.findall('require'): + if matchAPIProfile(api, profile, feature): + for v in feature.findall('usage'): + if v.get('command'): + self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v)) + if v.get('struct'): + self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v)) + + def removeAdditionalValidity(self, interface, api, profile): + # Loop over all usage inside all tags. + for feature in interface.findall('remove'): + if matchAPIProfile(api, profile, feature): + for v in feature.findall('usage'): + if v.get('command'): + self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v)) + if v.get('struct'): + self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v)) + + def generateFeature(self, fname, ftype, dictionary, explicit=False): + """Generate a single type / enum group / enum / command, + and all its dependencies as needed. + + - fname - name of feature (``/``/``) + - ftype - type of feature, 'type' | 'enum' | 'command' + - dictionary - of *Info objects - self.{type|enum|cmd}dict + - explicit - True if this is explicitly required by the top-level + XML tag, False if it is a dependency of an explicit + requirement.""" + + self.gen.logMsg('diag', 'generateFeature: generating', ftype, fname) + + if not (explicit or self.genOpts.requireDepends): + self.gen.logMsg('diag', 'generateFeature: NOT generating', ftype, fname, 'because generator does not require dependencies') + return + + f = self.lookupElementInfo(fname, dictionary) + if f is None: + # No such feature. This is an error, but reported earlier + self.gen.logMsg('diag', 'No entry found for feature', fname, + 'returning!') + return + + # If feature is not required, or has already been declared, return + if not f.required: + self.gen.logMsg('diag', 'Skipping', ftype, fname, '(not required)') + return + if f.declared: + self.gen.logMsg('diag', 'Skipping', ftype, fname, '(already declared)') + return + # Always mark feature declared, as though actually emitted + f.declared = True + + # Determine if this is an alias, and of what, if so + alias = f.elem.get('alias') + if alias: + self.gen.logMsg('diag', fname, 'is an alias of', alias) + + # Pull in dependent declaration(s) of the feature. + # For types, there may be one type in the 'requires' attribute of + # the element, one in the 'alias' attribute, and many in + # embedded and tags within the element. + # For commands, there may be many in tags within the element. + # For enums, no dependencies are allowed (though perhaps if you + # have a uint64 enum, it should require that type). + genProc = None + followupFeature = None + if ftype == 'type': + genProc = self.gen.genType + + # Generate type dependencies in 'alias' and 'requires' attributes + if alias: + self.generateFeature(alias, 'type', self.typedict) + requires = f.elem.get('requires') + if requires: + self.gen.logMsg('diag', 'Generating required dependent type', + requires) + self.generateFeature(requires, 'type', self.typedict) + + # Generate types used in defining this type (e.g. in nested + # tags) + # Look for in entire tree, + # not just immediate children + for subtype in f.elem.findall('.//type'): + self.gen.logMsg('diag', 'Generating required dependent ', + subtype.text) + self.generateFeature(subtype.text, 'type', self.typedict) + + # Generate enums used in defining this type, for example in + # member[MEMBER_SIZE] + for subtype in f.elem.findall('.//enum'): + self.gen.logMsg('diag', 'Generating required dependent ', + subtype.text) + self.generateFeature(subtype.text, 'enum', self.enumdict) + + # If the type is an enum group, look up the corresponding + # group in the group dictionary and generate that instead. + if f.elem.get('category') == 'enum': + self.gen.logMsg('diag', 'Type', fname, 'is an enum group, so generate that instead') + group = self.lookupElementInfo(fname, self.groupdict) + if alias is not None: + # An alias of another group name. + # Pass to genGroup with 'alias' parameter = aliased name + self.gen.logMsg('diag', 'Generating alias', fname, + 'for enumerated type', alias) + # Now, pass the *aliased* GroupInfo to the genGroup, but + # with an additional parameter which is the alias name. + genProc = self.gen.genGroup + f = self.lookupElementInfo(alias, self.groupdict) + elif group is None: + self.gen.logMsg('warn', 'Skipping enum type', fname, + ': No matching enumerant group') + return + else: + genProc = self.gen.genGroup + f = group + + # @ The enum group is not ready for generation. At this + # @ point, it contains all tags injected by + # @ tags without any verification of whether + # @ they are required or not. It may also contain + # @ duplicates injected by multiple consistent + # @ definitions of an . + + # @ Pass over each enum, marking its enumdict[] entry as + # @ required or not. Mark aliases of enums as required, + # @ too. + + enums = group.elem.findall('enum') + + self.gen.logMsg('diag', 'generateFeature: checking enums for group', fname) + + # Check for required enums, including aliases + # LATER - Check for, report, and remove duplicates? + enumAliases = [] + for elem in enums: + name = elem.get('name') + + required = False + + extname = elem.get('extname') + version = elem.get('version') + if extname is not None: + # 'supported' attribute was injected when the element was + # moved into the group in Registry.parseTree() + supported_list = elem.get('supported').split(",") + if self.genOpts.defaultExtensions in supported_list: + required = True + elif re.match(self.genOpts.addExtensions, extname) is not None: + required = True + elif version is not None: + required = re.match(self.genOpts.emitversions, version) is not None + else: + required = True + + self.gen.logMsg('diag', '* required =', required, 'for', name) + if required: + # Mark this element as required (in the element, not the EnumInfo) + elem.set('required', 'true') + # If it is an alias, track that for later use + enumAlias = elem.get('alias') + if enumAlias: + enumAliases.append(enumAlias) + for elem in enums: + name = elem.get('name') + if name in enumAliases: + elem.set('required', 'true') + self.gen.logMsg('diag', '* also need to require alias', name) + if f is None: + raise RuntimeError("Should not get here") + if f.elem.get('category') == 'bitmask': + followupFeature = f.elem.get('bitvalues') + elif ftype == 'command': + # Generate command dependencies in 'alias' attribute + if alias: + self.generateFeature(alias, 'command', self.cmddict) + + genProc = self.gen.genCmd + for type_elem in f.elem.findall('.//type'): + depname = type_elem.text + self.gen.logMsg('diag', 'Generating required parameter type', + depname) + self.generateFeature(depname, 'type', self.typedict) + elif ftype == 'enum': + # Generate enum dependencies in 'alias' attribute + if alias: + self.generateFeature(alias, 'enum', self.enumdict) + genProc = self.gen.genEnum + + # Actually generate the type only if emitting declarations + if self.emitFeatures: + self.gen.logMsg('diag', 'Emitting', ftype, 'decl for', fname) + if genProc is None: + raise RuntimeError("genProc is None when we should be emitting") + genProc(f, fname, alias) + else: + self.gen.logMsg('diag', 'Skipping', ftype, fname, + '(should not be emitted)') + + if followupFeature: + self.gen.logMsg('diag', 'Generating required bitvalues ', + followupFeature) + self.generateFeature(followupFeature, "type", self.typedict) + + def generateRequiredInterface(self, interface): + """Generate all interfaces required by an API version or extension. + + - interface - Element for `` or ``""" + + # Loop over all features inside all tags. + for features in interface.findall('require'): + for t in features.findall('type'): + self.generateFeature(t.get('name'), 'type', self.typedict, explicit=True) + for e in features.findall('enum'): + # If this is an enum extending an enumerated type, do not + # generate it - this has already been done in reg.parseTree, + # by copying this element into the enumerated type. + enumextends = e.get('extends') + if not enumextends: + self.generateFeature(e.get('name'), 'enum', self.enumdict, explicit=True) + for c in features.findall('command'): + self.generateFeature(c.get('name'), 'command', self.cmddict, explicit=True) + + def generateSpirv(self, spirv, dictionary): + if spirv is None: + self.gen.logMsg('diag', 'No entry found for element', name, + 'returning!') + return + + name = spirv.elem.get('name') + # No known alias for spirv elements + alias = None + if spirv.emit: + genProc = self.gen.genSpirv + genProc(spirv, name, alias) + + def stripUnsupportedAPIs(self, dictionary, attribute, supportedDictionary): + """Strip unsupported APIs from attributes of APIs. + dictionary - *Info dictionary of APIs to be updated + attribute - attribute name to look for in each API + supportedDictionary - dictionary in which to look for supported + API elements in the attribute""" + + for key in dictionary: + eleminfo = dictionary[key] + attribstring = eleminfo.elem.get(attribute) + if attribstring is not None: + apis = [] + stripped = False + for api in attribstring.split(','): + ##print('Checking API {} referenced by {}'.format(api, key)) + if api in supportedDictionary and supportedDictionary[api].required: + apis.append(api) + else: + stripped = True + ##print('\t**STRIPPING API {} from {}'.format(api, key)) + + # Update the attribute after stripping stuff. + # Could sort apis before joining, but it is not a clear win + if stripped: + eleminfo.elem.set(attribute, ','.join(apis)) + + def stripUnsupportedAPIsFromList(self, dictionary, supportedDictionary): + """Strip unsupported APIs from attributes of APIs. + dictionary - dictionary of list of structure name strings + supportedDictionary - dictionary in which to look for supported + API elements in the attribute""" + + for key in dictionary: + attribstring = dictionary[key] + if attribstring is not None: + apis = [] + stripped = False + for api in attribstring: + ##print('Checking API {} referenced by {}'.format(api, key)) + if supportedDictionary[api].required: + apis.append(api) + else: + stripped = True + ##print('\t**STRIPPING API {} from {}'.format(api, key)) + + # Update the attribute after stripping stuff. + # Could sort apis before joining, but it is not a clear win + if stripped: + dictionary[key] = apis + + def generateFormat(self, format, dictionary): + if format is None: + self.gen.logMsg('diag', 'No entry found for format element', + 'returning!') + return + + name = format.elem.get('name') + # No known alias for VkFormat elements + alias = None + if format.emit: + genProc = self.gen.genFormat + genProc(format, name, alias) + + def generateSyncStage(self, sync): + genProc = self.gen.genSyncStage + genProc(sync) + + def generateSyncAccess(self, sync): + genProc = self.gen.genSyncAccess + genProc(sync) + + def generateSyncPipeline(self, sync): + genProc = self.gen.genSyncPipeline + genProc(sync) + + def tagValidExtensionStructs(self): + """Construct a "validextensionstructs" list for parent structures + based on "structextends" tags in child structures. + Only do this for structures tagged as required.""" + + for typeinfo in self.typedict.values(): + type_elem = typeinfo.elem + if typeinfo.required and type_elem.get('category') == 'struct': + struct_extends = type_elem.get('structextends') + if struct_extends is not None: + for parent in struct_extends.split(','): + # self.gen.logMsg('diag', type_elem.get('name'), 'extends', parent) + self.validextensionstructs[parent].append(type_elem.get('name')) + + # Sort the lists so they do not depend on the XML order + for parent in self.validextensionstructs: + self.validextensionstructs[parent].sort() + + def apiGen(self): + """Generate interface for specified versions using the current + generator and generator options""" + + self.gen.logMsg('diag', '*******************************************') + self.gen.logMsg('diag', ' Registry.apiGen file:', self.genOpts.filename, + 'api:', self.genOpts.apiname, + 'profile:', self.genOpts.profile) + self.gen.logMsg('diag', '*******************************************') + + # Could reset required/declared flags for all features here. + # This has been removed as never used. The initial motivation was + # the idea of calling apiGen() repeatedly for different targets, but + # this has never been done. The 20% or so build-time speedup that + # might result is not worth the effort to make it actually work. + # + # self.apiReset() + + # Compile regexps used to select versions & extensions + regVersions = re.compile(self.genOpts.versions) + regEmitVersions = re.compile(self.genOpts.emitversions) + regAddExtensions = re.compile(self.genOpts.addExtensions) + regRemoveExtensions = re.compile(self.genOpts.removeExtensions) + regEmitExtensions = re.compile(self.genOpts.emitExtensions) + regEmitSpirv = re.compile(self.genOpts.emitSpirv) + regEmitFormats = re.compile(self.genOpts.emitFormats) + + # Get all matching API feature names & add to list of FeatureInfo + # Note we used to select on feature version attributes, not names. + features = [] + apiMatch = False + for key in self.apidict: + fi = self.apidict[key] + api = fi.elem.get('api') + if apiNameMatch(self.genOpts.apiname, api): + apiMatch = True + if regVersions.match(fi.name): + # Matches API & version #s being generated. Mark for + # emission and add to the features[] list . + # @@ Could use 'declared' instead of 'emit'? + fi.emit = (regEmitVersions.match(fi.name) is not None) + features.append(fi) + if not fi.emit: + self.gen.logMsg('diag', 'NOT tagging feature api =', api, + 'name =', fi.name, 'version =', fi.version, + 'for emission (does not match emitversions pattern)') + else: + self.gen.logMsg('diag', 'Including feature api =', api, + 'name =', fi.name, 'version =', fi.version, + 'for emission (matches emitversions pattern)') + else: + self.gen.logMsg('diag', 'NOT including feature api =', api, + 'name =', fi.name, 'version =', fi.version, + '(does not match requested versions)') + else: + self.gen.logMsg('diag', 'NOT including feature api =', api, + 'name =', fi.name, + '(does not match requested API)') + if not apiMatch: + self.gen.logMsg('warn', 'No matching API versions found!') + + # Get all matching extensions, in order by their extension number, + # and add to the list of features. + # Start with extensions whose 'supported' attributes match the API + # being generated. Add extensions matching the pattern specified in + # regExtensions, then remove extensions matching the pattern + # specified in regRemoveExtensions + for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'): + extName = ei.name + include = False + + # Include extension if defaultExtensions is not None and is + # exactly matched by the 'supported' attribute. + if apiNameMatch(self.genOpts.defaultExtensions, + ei.elem.get('supported')): + self.gen.logMsg('diag', 'Including extension', + extName, "(defaultExtensions matches the 'supported' attribute)") + include = True + + # Include additional extensions if the extension name matches + # the regexp specified in the generator options. This allows + # forcing extensions into an interface even if they are not + # tagged appropriately in the registry. + # However, we still respect the 'supported' attribute. + if regAddExtensions.match(extName) is not None: + if not apiNameMatch(self.genOpts.apiname, ei.elem.get('supported')): + self.gen.logMsg('diag', 'NOT including extension', + extName, '(matches explicitly requested, but does not match the \'supported\' attribute)') + include = False + else: + self.gen.logMsg('diag', 'Including extension', + extName, '(matches explicitly requested extensions to add)') + include = True + # Remove extensions if the name matches the regexp specified + # in generator options. This allows forcing removal of + # extensions from an interface even if they are tagged that + # way in the registry. + if regRemoveExtensions.match(extName) is not None: + self.gen.logMsg('diag', 'Removing extension', + extName, '(matches explicitly requested extensions to remove)') + include = False + + # If the extension is to be included, add it to the + # extension features list. + if include: + ei.emit = (regEmitExtensions.match(extName) is not None) + features.append(ei) + if not ei.emit: + self.gen.logMsg('diag', 'NOT tagging extension', + extName, + 'for emission (does not match emitextensions pattern)') + + # Hack - can be removed when validity generator goes away + # (Jon) I am not sure what this does, or if it should + # respect the ei.emit flag above. + self.requiredextensions.append(extName) + else: + self.gen.logMsg('diag', 'NOT including extension', + extName, '(does not match api attribute or explicitly requested extensions)') + + # Add all spirv elements to list + # generators decide to emit them all or not + # Currently no filtering as no client of these elements needs filtering + spirvexts = [] + for key in self.spirvextdict: + si = self.spirvextdict[key] + si.emit = (regEmitSpirv.match(key) is not None) + spirvexts.append(si) + spirvcaps = [] + for key in self.spirvcapdict: + si = self.spirvcapdict[key] + si.emit = (regEmitSpirv.match(key) is not None) + spirvcaps.append(si) + + formats = [] + for key in self.formatsdict: + si = self.formatsdict[key] + si.emit = (regEmitFormats.match(key) is not None) + formats.append(si) + + # Sort the features list, if a sort procedure is defined + if self.genOpts.sortProcedure: + self.genOpts.sortProcedure(features) + + # Passes 1+2: loop over requested API versions and extensions tagging + # types/commands/features as required (in an block) or no + # longer required (in an block). s are processed + # after all s, so removals win. + # If a profile other than 'None' is being generated, it must + # match the profile attribute (if any) of the and + # tags. + self.gen.logMsg('diag', 'PASS 1: TAG FEATURES') + for f in features: + self.gen.logMsg('diag', 'PASS 1: Tagging required and features for', f.name) + self.fillFeatureDictionary(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) + self.requireFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) + self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile) + + for f in features: + self.gen.logMsg('diag', 'PASS 2: Tagging removed features for', f.name) + self.removeFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile) + self.removeAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile) + + # Now, strip references to APIs that are not required. + # At present such references may occur in: + # Structs in 'structextends' attributes + # Enums in 'successcodes' and 'errorcodes' attributes + self.stripUnsupportedAPIs(self.typedict, 'structextends', self.typedict) + self.stripUnsupportedAPIs(self.cmddict, 'successcodes', self.enumdict) + self.stripUnsupportedAPIs(self.cmddict, 'errorcodes', self.enumdict) + self.stripUnsupportedAPIsFromList(self.validextensionstructs, self.typedict) + + # Construct lists of valid extension structures + self.tagValidExtensionStructs() + + # @@May need to strip / + # tags of these forms: + # + # + # + # + + # Pass 3: loop over specified API versions and extensions printing + # declarations for required things which have not already been + # generated. + self.gen.logMsg('diag', 'PASS 3: GENERATE INTERFACES FOR FEATURES') + self.gen.beginFile(self.genOpts) + for f in features: + self.gen.logMsg('diag', 'PASS 3: Generating interface for', + f.name) + emit = self.emitFeatures = f.emit + if not emit: + self.gen.logMsg('diag', 'PASS 3: NOT declaring feature', + f.elem.get('name'), 'because it is not tagged for emission') + # Generate the interface (or just tag its elements as having been + # emitted, if they have not been). + self.gen.beginFeature(f.elem, emit) + self.generateRequiredInterface(f.elem) + self.gen.endFeature() + # Generate spirv elements + for s in spirvexts: + self.generateSpirv(s, self.spirvextdict) + for s in spirvcaps: + self.generateSpirv(s, self.spirvcapdict) + for s in formats: + self.generateFormat(s, self.formatsdict) + for s in self.syncstagedict: + self.generateSyncStage(self.syncstagedict[s]) + for s in self.syncaccessdict: + self.generateSyncAccess(self.syncaccessdict[s]) + for s in self.syncpipelinedict: + self.generateSyncPipeline(self.syncpipelinedict[s]) + self.gen.endFile() + + def apiReset(self): + """Reset type/enum/command dictionaries before generating another API. + + Use between apiGen() calls to reset internal state.""" + for datatype in self.typedict: + self.typedict[datatype].resetState() + for enum in self.enumdict: + self.enumdict[enum].resetState() + for cmd in self.cmddict: + self.cmddict[cmd].resetState() + for cmd in self.apidict: + self.apidict[cmd].resetState()