summaryrefslogtreecommitdiff
path: root/codegen/vulkan/scripts/spirvcapgenerator.py
diff options
context:
space:
mode:
Diffstat (limited to 'codegen/vulkan/scripts/spirvcapgenerator.py')
-rw-r--r--codegen/vulkan/scripts/spirvcapgenerator.py308
1 files changed, 308 insertions, 0 deletions
diff --git a/codegen/vulkan/scripts/spirvcapgenerator.py b/codegen/vulkan/scripts/spirvcapgenerator.py
new file mode 100644
index 00000000..85740f34
--- /dev/null
+++ b/codegen/vulkan/scripts/spirvcapgenerator.py
@@ -0,0 +1,308 @@
+#!/usr/bin/python3 -i
+#
+# Copyright 2013-2021 The Khronos Group Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from generator import OutputGenerator, write
+from spec_tools.attributes import ExternSyncEntry
+from spec_tools.util import getElemName
+
+import pdb
+
+def makeLink(link, altlink = None):
+ """Create an asciidoctor link, optionally with altlink text
+ if provided"""
+
+ if altlink is not None:
+ return '<<{},{}>>'.format(link, altlink)
+ else:
+ return '<<{}>>'.format(link)
+
+class SpirvCapabilityOutputGenerator(OutputGenerator):
+ """SpirvCapabilityOutputGenerator - subclass of OutputGenerator.
+ Generates AsciiDoc includes of the SPIR-V capabilities table for the
+ features chapter of the API specification.
+
+ ---- methods ----
+ SpirvCapabilityOutputGenerator(errFile, warnFile, diagFile) - args as for
+ OutputGenerator. Defines additional internal state.
+ ---- methods overriding base class ----
+ genCmd(cmdinfo)"""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ def beginFile(self, genOpts):
+ OutputGenerator.beginFile(self, genOpts)
+
+ # Accumulate SPIR-V capability and feature information
+ self.spirv = []
+
+ def getCondition(self, enable):
+ """Return a strings which is the condition under which an
+ enable is supported.
+
+ - enable - ElementTree corresponding to an <enable> XML tag for a
+ SPIR-V capability or extension"""
+
+ if enable.get('version'):
+ # Turn VK_API_VERSION_1_0 -> VK_VERSION_1_0
+ return enable.get('version').replace('API_', '')
+ elif enable.get('extension'):
+ return enable.get('extension')
+ elif enable.get('struct') or enable.get('property'):
+ return enable.get('requires')
+ else:
+ self.logMsg('error', 'Unrecognized SPIR-V enable')
+ return ''
+
+ def getConditions(self, enables):
+ """Return a sorted list of strings which are conditions under which
+ one or more of the enables is supported.
+
+ - enables - ElementTree corresponding to a <spirvcapability> or
+ <spirvextension> XML tag"""
+
+ conditions = set()
+ for enable in enables.findall('enable'):
+ condition = self.getCondition(enable)
+ if condition != None:
+ conditions.add(condition)
+ return sorted(conditions)
+
+ def endFile(self):
+ captable = []
+ exttable = []
+
+ # How to "indent" a pseudo-column for better use of space.
+ # {captableindent} is defined in appendices/spirvenv.txt
+ indent = '{captableindent}'
+
+ for elem in self.spirv:
+ conditions = self.getConditions(elem)
+
+ # Combine all conditions for enables and surround the row with
+ # them
+ if len(conditions) > 0:
+ condition_string = ','.join(conditions)
+ prefix = [ 'ifdef::{}[]'.format(condition_string) ]
+ suffix = [ 'endif::{}[]'.format(condition_string) ]
+ else:
+ prefix = []
+ suffix = []
+
+ body = []
+
+ # Generate an anchor for each capability
+ if elem.tag == 'spirvcapability':
+ anchor = '[[spirvenv-capabilities-table-{}]]'.format(
+ elem.get('name'))
+ else:
+ # <spirvextension> entries don't get anchors
+ anchor = ''
+
+ # First "cell" in a table row, and a break for the other "cells"
+ body.append('| {}code:{} +'.format(anchor, elem.get('name')))
+
+ # Iterate over each enable emitting a formatting tag for it
+ # Protect the term if there is a version or extension
+ # requirement, and if there are multiple enables (otherwise,
+ # the ifdef protecting the entire row will suffice).
+
+ enables = [e for e in elem.findall('enable')]
+
+ remaining = len(enables)
+ for subelem in enables:
+ remaining -= 1
+
+ # Sentinel value
+ linktext = None
+ if subelem.get('version'):
+ version = subelem.get('version')
+
+ # Convert API enum VK_API_VERSION_m_n to conditional
+ # used for spec builds (VK_VERSION_m_n)
+ enable = version.replace('API_', '')
+ # Convert API enum to anchor for version appendices (versions-m.n)
+ link = 'versions-' + version[-3:].replace('_', '.')
+ altlink = version
+
+ linktext = makeLink(link, altlink)
+ elif subelem.get('extension'):
+ extension = subelem.get('extension')
+
+ enable = extension
+ link = extension
+ altlink = None
+
+ # This uses the extension name macro, rather than
+ # asciidoc markup
+ linktext = '`apiext:{}`'.format(extension)
+ elif subelem.get('struct'):
+ struct = subelem.get('struct')
+ feature = subelem.get('feature')
+ requires = subelem.get('requires')
+ alias = subelem.get('alias')
+
+ link_name = feature
+ # For cases, like bufferDeviceAddressEXT where need manual help
+ if alias:
+ link_name = alias
+
+ enable = requires
+ link = 'features-' + link_name
+ altlink = 'sname:{}::pname:{}'.format(struct, feature)
+
+ linktext = makeLink(link, altlink)
+ else:
+ property = subelem.get('property')
+ member = subelem.get('member')
+ requires = subelem.get('requires')
+ value = subelem.get('value')
+
+ enable = requires
+ # Properties should have a "feature" prefix
+ link = 'limits-' + member
+ # Display the property value by itself if it is not a boolean (matches original table)
+ # DenormPreserve is an example where it makes sense to just show the
+ # member value as it is just a boolean and the name implies "true"
+ # GroupNonUniformVote is an example where the whole name is too long
+ # better to just display the value
+ if value == "VK_TRUE":
+ altlink = 'sname:{}::pname:{}'.format(property, member)
+ else:
+ altlink = '{}'.format(value)
+
+ linktext = makeLink(link, altlink)
+
+ # If there are no more enables, don't continue the last line
+ if remaining > 0:
+ continuation = ' +'
+ else:
+ continuation = ''
+
+ # condition_string != enable is a small optimization
+ if enable is not None and condition_string != enable:
+ body.append('ifdef::{}[]'.format(enable))
+ body.append('{} {}{}'.format(indent, linktext, continuation))
+ if enable is not None and condition_string != enable:
+ body.append('endif::{}[]'.format(enable))
+
+ if elem.tag == 'spirvcapability':
+ captable += prefix + body + suffix
+ else:
+ exttable += prefix + body + suffix
+
+ # Generate the asciidoc include files
+ self.writeBlock('captable.txt', captable)
+ self.writeBlock('exttable.txt', exttable)
+
+ # Finish processing in superclass
+ OutputGenerator.endFile(self)
+
+ def writeBlock(self, 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)"""
+
+ filename = self.genOpts.directory + '/' + basename
+ self.logMsg('diag', '# Generating include file:', filename)
+ with open(filename, 'w', encoding='utf-8') as fp:
+ write(self.genOpts.conventions.warning_comment, file=fp)
+
+ if len(contents) > 0:
+ for str in contents:
+ write(str, file=fp)
+ else:
+ self.logMsg('diag', '# No contents for:', filename)
+
+ def paramIsArray(self, param):
+ """Check if the parameter passed in is a pointer to an array."""
+ return param.get('len') is not None
+
+ def paramIsPointer(self, param):
+ """Check if the parameter passed in is a pointer."""
+ tail = param.find('type').tail
+ return tail is not None and '*' in tail
+
+ def makeThreadSafetyBlocks(self, cmd, paramtext):
+ # See also makeThreadSafetyBlock in validitygenerator.py - similar but not entirely identical
+ protoname = cmd.find('proto/name').text
+
+ # Find and add any parameters that are thread unsafe
+ explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
+ if explicitexternsyncparams is not None:
+ for param in explicitexternsyncparams:
+ self.makeThreadSafetyForParam(protoname, param)
+
+ # Find and add any "implicit" parameters that are thread unsafe
+ implicitexternsyncparams = cmd.find('implicitexternsyncparams')
+ if implicitexternsyncparams is not None:
+ for elem in implicitexternsyncparams:
+ entry = ValidityEntry()
+ entry += elem.text
+ entry += ' in '
+ entry += self.makeFLink(protoname)
+ self.threadsafety['implicit'] += entry
+
+ # Add a VU for any command requiring host synchronization.
+ # This could be further parameterized, if a future non-Vulkan API
+ # requires it.
+ if self.genOpts.conventions.is_externsync_command(protoname):
+ entry = ValidityEntry()
+ entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from, in '
+ entry += self.makeFLink(protoname)
+ self.threadsafety['implicit'] += entry
+
+ def makeThreadSafetyForParam(self, protoname, param):
+ """Create thread safety validity for a single param of a command."""
+ externsyncattribs = ExternSyncEntry.parse_externsync_from_param(param)
+ param_name = getElemName(param)
+
+ for attrib in externsyncattribs:
+ entry = ValidityEntry()
+ is_array = False
+ if attrib.entirely_extern_sync:
+ # "true" or "true_with_children"
+ if self.paramIsArray(param):
+ entry += 'Each element of the '
+ is_array = True
+ elif self.paramIsPointer(param):
+ entry += 'The object referenced by the '
+ else:
+ entry += 'The '
+
+ entry += self.makeParameterName(param_name)
+ entry += ' parameter'
+
+ if attrib.children_extern_sync:
+ entry += ', and any child handles,'
+
+ else:
+ # parameter/member reference
+ readable = attrib.get_human_readable(make_param_name=self.makeParameterName)
+ is_array = (' element of ' in readable)
+ entry += readable
+
+ entry += ' in '
+ entry += self.makeFLink(protoname)
+
+ if is_array:
+ self.threadsafety['parameterlists'] += entry
+ else:
+ self.threadsafety['parameters'] += entry
+
+ def genSpirv(self, capinfo, name, alias):
+ """Generate SPIR-V capabilities
+
+ capinfo - dictionary entry for an XML <spirvcapability> or
+ <spirvextension> element
+ name - name attribute of capinfo.elem"""
+
+ OutputGenerator.genSpirv(self, capinfo, name, alias)
+
+ # Just accumulate each element, process in endFile
+ self.spirv.append(capinfo.elem)