summaryrefslogtreecommitdiff
path: root/codegen/vulkan/scripts/docgenerator.py
diff options
context:
space:
mode:
authorJason Macnak <natsu@google.com>2023-05-19 14:54:16 -0700
committerJason Macnak <natsu@google.com>2023-05-22 12:03:45 -0700
commitea00ab3f8f6e353fa252dc65cce0ae505d731651 (patch)
tree97952257a570478249f2b04f346ac862949a4d38 /codegen/vulkan/scripts/docgenerator.py
parentf5d796021d5b76625d706cc9a3580a4aa9f75bb2 (diff)
downloadgfxstream-protocols-ea00ab3f8f6e353fa252dc65cce0ae505d731651.tar.gz
Prepare for move to hardware/google/gfxstream
Move codegen into what will become hardware/google/gfxstream/codegen/vulkan and move generated vulkan headers into what will become hardware/google/gfxstream/common/vulkan. Bug: 271464937 Test: presubmit Test: python android/build/python/cmake.py --gfxstream Change-Id: Ie0465ac72b4a2f7444b595cd1421e2e3cb1d0997
Diffstat (limited to 'codegen/vulkan/scripts/docgenerator.py')
-rw-r--r--codegen/vulkan/scripts/docgenerator.py482
1 files changed, 482 insertions, 0 deletions
diff --git a/codegen/vulkan/scripts/docgenerator.py b/codegen/vulkan/scripts/docgenerator.py
new file mode 100644
index 00000000..bb86eb50
--- /dev/null
+++ b/codegen/vulkan/scripts/docgenerator.py
@@ -0,0 +1,482 @@
+#!/usr/bin/python3 -i
+#
+# Copyright 2013-2021 The Khronos Group Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from pathlib import Path
+
+from generator import GeneratorOptions, OutputGenerator, noneStr, write
+
+ENUM_TABLE_PREFIX = """
+[cols=",",options="header",]
+|=======================================================================
+|Enum |Description"""
+
+ENUM_TABLE_SUFFIX = """|======================================================================="""
+
+FLAG_BLOCK_PREFIX = """.Flag Descriptions
+****"""
+
+FLAG_BLOCK_SUFFIX = """****"""
+
+def orgLevelKey(name):
+ # Sort key for organization levels of features / extensions
+ # From highest to lowest, core versions, KHR extensions, EXT extensions,
+ # and vendor extensions
+
+ prefixes = (
+ 'VK_VERSION_',
+ 'VKSC_VERSION_',
+ 'VK_KHR_',
+ 'VK_EXT_')
+
+ i = 0
+ for prefix in prefixes:
+ if name.startswith(prefix):
+ return i
+ i += 1
+
+ # Everything else (e.g. vendor extensions) is least important
+ return i
+
+class DocGeneratorOptions(GeneratorOptions):
+ """DocGeneratorOptions - subclass of GeneratorOptions for
+ generating declaration snippets for the spec.
+
+ Shares many members with CGeneratorOptions, since
+ both are writing C-style declarations."""
+
+ def __init__(self,
+ prefixText="",
+ apicall='',
+ apientry='',
+ apientryp='',
+ indentFuncProto=True,
+ indentFuncPointer=False,
+ alignFuncParam=0,
+ secondaryInclude=False,
+ expandEnumerants=True,
+ extEnumerantAdditions=False,
+ extEnumerantFormatString=" (Added by the {} extension)",
+ **kwargs):
+ """Constructor.
+
+ Since this generator outputs multiple files at once,
+ the filename is just a "stamp" to indicate last generation time.
+
+ Shares many parameters/members with CGeneratorOptions, since
+ both are writing C-style declarations:
+
+ - prefixText - list of strings to prefix generated header with
+ (usually a copyright statement + calling convention macros).
+ - 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
+
+ Additional parameters/members:
+
+ - expandEnumerants - if True, add BEGIN/END_RANGE macros in enumerated
+ type declarations
+ - secondaryInclude - if True, add secondary (no xref anchor) versions
+ of generated files
+ - extEnumerantAdditions - if True, include enumerants added by extensions
+ in comment tables for core enumeration types.
+ - extEnumerantFormatString - A format string for any additional message for
+ enumerants from extensions if extEnumerantAdditions is True. The correctly-
+ marked-up extension name will be passed.
+ """
+ GeneratorOptions.__init__(self, **kwargs)
+ self.prefixText = prefixText
+ """list of strings to prefix generated header with (usually a copyright statement + calling convention macros)."""
+
+ 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.secondaryInclude = secondaryInclude
+ """if True, add secondary (no xref anchor) versions of generated files"""
+
+ self.expandEnumerants = expandEnumerants
+ """if True, add BEGIN/END_RANGE macros in enumerated type declarations"""
+
+ self.extEnumerantAdditions = extEnumerantAdditions
+ """if True, include enumerants added by extensions in comment tables for core enumeration types."""
+
+ self.extEnumerantFormatString = extEnumerantFormatString
+ """A format string for any additional message for
+ enumerants from extensions if extEnumerantAdditions is True. The correctly-
+ marked-up extension name will be passed."""
+
+
+class DocOutputGenerator(OutputGenerator):
+ """DocOutputGenerator - subclass of OutputGenerator.
+
+ Generates AsciiDoc includes with C-language API interfaces, for reference
+ pages and the corresponding specification. Similar to COutputGenerator,
+ but each interface is written into a different file as determined by the
+ options, only actual C types are emitted, and none of the boilerplate
+ preprocessor code is emitted."""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ # Keep track of all extension numbers
+ self.extension_numbers = set()
+
+ def beginFile(self, genOpts):
+ OutputGenerator.beginFile(self, genOpts)
+
+ # This should be a separate conventions property rather than an
+ # inferred type name pattern for different APIs.
+ self.result_type = genOpts.conventions.type_prefix + "Result"
+
+ def endFile(self):
+ OutputGenerator.endFile(self)
+
+ def beginFeature(self, interface, emit):
+ # Start processing in superclass
+ OutputGenerator.beginFeature(self, interface, emit)
+
+ # Decide if we're in a core <feature> or an <extension>
+ self.in_core = (interface.tag == 'feature')
+
+ # Verify that each <extension> has a unique number during doc
+ # generation
+ # TODO move this to consistency_tools
+ if not self.in_core:
+ extension_number = interface.get('number')
+ if extension_number is not None and extension_number != "0":
+ if extension_number in self.extension_numbers:
+ self.logMsg('error', 'Duplicate extension number ', extension_number, ' detected in feature ', interface.get('name'), '\n')
+ exit(1)
+ else:
+ self.extension_numbers.add(extension_number)
+
+ def endFeature(self):
+ # Finish processing in superclass
+ OutputGenerator.endFeature(self)
+
+ def genRequirements(self, name, mustBeFound = True):
+ """Generate text showing what core versions and extensions introduce
+ an API. This relies on the map in api.py, which may be loaded at
+ runtime into self.apidict. If not present, no message is
+ generated.
+
+ - name - name of the API
+ - mustBeFound - If True, when requirements for 'name' cannot be
+ determined, a warning comment is generated.
+ """
+
+ if self.apidict:
+ if name in self.apidict.requiredBy:
+ # It's possible to get both 'A with B' and 'B with A' for
+ # the same API.
+ # To simplify this, sort the (base,dependency) requirements
+ # and put them in a set to ensure they're unique.
+ features = set()
+ for (base,dependency) in self.apidict.requiredBy[name]:
+ if dependency is not None:
+ l = sorted(
+ sorted((base, dependency)),
+ key=orgLevelKey)
+ features.add(' with '.join(l))
+ else:
+ features.add(base)
+ # Sort the overall dependencies so core versions are first
+ provider = ', '.join(sorted(features, key=orgLevelKey))
+ return f'// Provided by {provider}\n'
+ else:
+ if mustBeFound:
+ self.logMsg('warn', 'genRequirements: API {} not found'.format(name))
+ return ''
+ else:
+ # No API dictionary available, return nothing
+ return ''
+
+ def writeInclude(self, directory, basename, contents):
+ """Generate an include file.
+
+ - directory - subdirectory to put file in
+ - basename - base name of the file
+ - contents - contents of the file (Asciidoc boilerplate aside)"""
+ # Create subdirectory, if needed
+ directory = self.genOpts.directory + '/' + directory
+ self.makeDir(directory)
+
+ # Create file
+ filename = directory + '/' + basename + '.txt'
+ self.logMsg('diag', '# Generating include file:', filename)
+ fp = open(filename, 'w', encoding='utf-8')
+
+ # Asciidoc anchor
+ write(self.genOpts.conventions.warning_comment, file=fp)
+ write('[[{0},{0}]]'.format(basename), file=fp)
+
+ if self.genOpts.conventions.generate_index_terms:
+ index_terms = []
+ if basename.startswith(self.conventions.command_prefix):
+ index_terms.append(basename[2:] + " (function)")
+ elif basename.startswith(self.conventions.type_prefix):
+ index_terms.append(basename[2:] + " (type)")
+ elif basename.startswith(self.conventions.api_prefix):
+ index_terms.append(basename[len(self.conventions.api_prefix):] + " (define)")
+ index_terms.append(basename)
+ write('indexterm:[{}]'.format(','.join(index_terms)), file=fp)
+
+ write('[source,c++]', file=fp)
+ write('----', file=fp)
+ write(contents, file=fp)
+ write('----', file=fp)
+ fp.close()
+
+ if self.genOpts.secondaryInclude:
+ # Create secondary no cross-reference include file
+ filename = directory + '/' + basename + '.no-xref.txt'
+ self.logMsg('diag', '# Generating include file:', filename)
+ fp = open(filename, 'w', encoding='utf-8')
+
+ # Asciidoc anchor
+ write(self.genOpts.conventions.warning_comment, file=fp)
+ write('// Include this no-xref version without cross reference id for multiple includes of same file', file=fp)
+ write('[source,c++]', file=fp)
+ write('----', file=fp)
+ write(contents, file=fp)
+ write('----', file=fp)
+ fp.close()
+
+ def writeTable(self, basename, values):
+ """Output a table of enumerants."""
+ directory = Path(self.genOpts.directory) / 'enums'
+ self.makeDir(str(directory))
+
+ filename = str(directory / '{}.comments.txt'.format(basename))
+ self.logMsg('diag', '# Generating include file:', filename)
+
+ with open(filename, 'w', encoding='utf-8') as fp:
+ write(self.conventions.warning_comment, file=fp)
+ write(ENUM_TABLE_PREFIX, file=fp)
+
+ for data in values:
+ write("|ename:{}".format(data['name']), file=fp)
+ write("|{}".format(data['comment']), file=fp)
+
+ write(ENUM_TABLE_SUFFIX, file=fp)
+
+ def writeFlagBox(self, basename, values):
+ """Output a box of flag bit comments."""
+ directory = Path(self.genOpts.directory) / 'enums'
+ self.makeDir(str(directory))
+
+ filename = str(directory / '{}.comments.txt'.format(basename))
+ self.logMsg('diag', '# Generating include file:', filename)
+
+ with open(filename, 'w', encoding='utf-8') as fp:
+ write(self.conventions.warning_comment, file=fp)
+ write(FLAG_BLOCK_PREFIX, file=fp)
+
+ for data in values:
+ write("* ename:{} -- {}".format(data['name'],
+ data['comment']),
+ file=fp)
+
+ write(FLAG_BLOCK_SUFFIX, file=fp)
+
+ def genType(self, typeinfo, name, alias):
+ """Generate type."""
+ OutputGenerator.genType(self, typeinfo, name, alias)
+ typeElem = typeinfo.elem
+ # If the type is a struct type, traverse the embedded <member> tags
+ # generating a structure. Otherwise, emit the tag text.
+ category = typeElem.get('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)
+ elif category not in OutputGenerator.categoryToPath:
+ # If there's no path, don't write output
+ self.logMsg('diag', 'NOT writing include for {} category {}'.format(
+ name, category))
+ else:
+ body = self.genRequirements(name)
+ if alias:
+ # If the type is an alias, just emit a typedef declaration
+ body += 'typedef ' + alias + ' ' + name + ';\n'
+ self.writeInclude(OutputGenerator.categoryToPath[category],
+ name, body)
+ else:
+ # Replace <apientry /> tags with an APIENTRY-style string
+ # (from self.genOpts). Copy other text through unchanged.
+ # If the resulting text is an empty string, don't 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 body:
+ self.writeInclude(OutputGenerator.categoryToPath[category],
+ name, body + '\n')
+ else:
+ self.logMsg('diag', 'NOT writing empty include file for type', name)
+
+ def genStruct(self, typeinfo, typeName, alias):
+ """Generate struct."""
+ OutputGenerator.genStruct(self, typeinfo, typeName, alias)
+
+ typeElem = typeinfo.elem
+
+ body = self.genRequirements(typeName)
+ if alias:
+ body += 'typedef ' + alias + ' ' + typeName + ';\n'
+ else:
+ body += 'typedef ' + typeElem.get('category') + ' ' + typeName + ' {\n'
+
+ targetLen = self.getMaxCParamTypeLength(typeinfo)
+ for member in typeElem.findall('.//member'):
+ body += self.makeCParamDecl(member, targetLen + 4)
+ body += ';\n'
+ body += '} ' + typeName + ';'
+
+ self.writeInclude('structs', typeName, body)
+
+ def genEnumTable(self, groupinfo, groupName):
+ """Generate tables of enumerant values and short descriptions from
+ the XML."""
+
+ values = []
+ got_comment = False
+ missing_comments = []
+ for elem in groupinfo.elem.findall('enum'):
+ if not elem.get('required'):
+ continue
+ name = elem.get('name')
+
+ data = {
+ 'name': name,
+ }
+
+ (numVal, strVal) = self.enumToValue(elem, True)
+ data['value'] = numVal
+
+ extname = elem.get('extname')
+
+ added_by_extension_to_core = (extname is not None and self.in_core)
+ if added_by_extension_to_core and not self.genOpts.extEnumerantAdditions:
+ # We're skipping such values
+ continue
+
+ comment = elem.get('comment')
+ if comment:
+ got_comment = True
+ elif name.endswith('_UNKNOWN') and numVal == 0:
+ # This is a placeholder for 0-initialization to be clearly invalid.
+ # Just skip this silently
+ continue
+ else:
+ # Skip but record this in case it's an odd-one-out missing a comment.
+ missing_comments.append(name)
+ continue
+
+ if added_by_extension_to_core and self.genOpts.extEnumerantFormatString:
+ # Add a note to the comment
+ comment += self.genOpts.extEnumerantFormatString.format(
+ self.conventions.formatExtension(extname))
+
+ data['comment'] = comment
+ values.append(data)
+
+ if got_comment:
+ # If any had a comment, output it.
+
+ if missing_comments:
+ self.logMsg('warn', 'The following values for', groupName,
+ 'were omitted from the table due to missing comment attributes:',
+ ', '.join(missing_comments))
+
+ group_type = groupinfo.elem.get('type')
+ if groupName == self.result_type:
+ # Split this into success and failure
+ self.writeTable(groupName + '.success',
+ (data for data in values
+ if data['value'] >= 0))
+ self.writeTable(groupName + '.error',
+ (data for data in values
+ if data['value'] < 0))
+ elif group_type == 'bitmask':
+ self.writeFlagBox(groupName, values)
+ elif group_type == 'enum':
+ self.writeTable(groupName, values)
+ else:
+ raise RuntimeError("Unrecognized enums type: " + str(group_type))
+
+ def genGroup(self, groupinfo, groupName, alias):
+ """Generate group (e.g. C "enum" type)."""
+ OutputGenerator.genGroup(self, groupinfo, groupName, alias)
+
+ body = self.genRequirements(groupName)
+ if alias:
+ # If the group name is aliased, just emit a typedef declaration
+ # for the alias.
+ body += 'typedef ' + alias + ' ' + groupName + ';\n'
+ else:
+ expand = self.genOpts.expandEnumerants
+ (_, enumbody) = self.buildEnumCDecl(expand, groupinfo, groupName)
+ body += enumbody
+ if self.genOpts.conventions.generate_enum_table:
+ self.genEnumTable(groupinfo, groupName)
+
+ self.writeInclude('enums', groupName, body)
+
+ def genEnum(self, enuminfo, name, alias):
+ """Generate the C declaration for a constant (a single <enum> value)."""
+
+ OutputGenerator.genEnum(self, enuminfo, name, alias)
+
+ body = self.buildConstantCDecl(enuminfo, name, alias)
+
+ self.writeInclude('enums', name, body)
+
+ def genCmd(self, cmdinfo, name, alias):
+ "Generate command."
+ OutputGenerator.genCmd(self, cmdinfo, name, alias)
+
+ return_type = cmdinfo.elem.find('proto/type')
+ if self.genOpts.conventions.requires_error_validation(return_type):
+ # This command returns an API result code, so check that it
+ # returns at least the required errors.
+ # TODO move this to consistency_tools
+ required_errors = set(self.genOpts.conventions.required_errors)
+ errorcodes = cmdinfo.elem.get('errorcodes').split(',')
+ if not required_errors.issubset(set(errorcodes)):
+ self.logMsg('error', 'Missing required error code for command: ', name, '\n')
+ exit(1)
+
+ body = self.genRequirements(name)
+ decls = self.makeCDecls(cmdinfo.elem)
+ body += decls[0]
+ self.writeInclude('protos', name, body)