blob: 85740f340a52ccd28b5bdb26aee4401f8027642d [file] [log] [blame]
#!/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)