mirror of
https://github.com/status-im/Vulkan-Docs.git
synced 2025-02-10 05:24:38 +00:00
* Update release number to 104. Public Issues: * Remove the incorrect line from "`Initial`" to "`Invalid`" state in the <<commandbuffer-lifecycle-diagram, Lifecycle of a command buffer>> diagram (public issue 881). * Add Fuchsia platform to <<boilerplate-wsi-header-table, Window System Extensions and Headers>> table (public pull request 933). * Change the type of slink:VkBufferDeviceAddressCreateInfoEXT::pname:deviceAddress from basetype:VkDeviceSize to basetype:VkDeviceAddress. These are both typedefs of code:uint64_t, so it is an ABI-compatible change (public issue 934). Internal Issues: * Remove generated header files and update the CI tests to build a copy of the headers for use by the hpp-generate / hpp-compile CI stages. Targets to generate the headers will not be removed, but keeping these generated files in the repository increased the frequency of conflicts between branches when merging to master (internal issue 745). * Reword "`undefined: behavior if *action*" to "`must: not do *action*`" in the places the old terminology was used, and add a new <<writing-undefined, Describing Undefined Behavior>> section of the style guide to explain how to write such language in the future (internal issue 1579). * Move almost all Python scripts into the toplevel `scripts/` directory. Apply extensive internal edits to clean up and simplify the scripts, and try to follow PEP8 guidelines. Generalize the scripts with the use of a Conventions object controlling many aspects of output generation, to enable their use in other Khronos projects with similar requirements. Autogenerate extension interface refpages (these are experimental and may be retired going forward). New Extensions: * `VK_AMD_display_native_hdr` * `VK_EXT_full_screen_exclusive` (internal issue 1439) * `VK_EXT_host_query_reset` * `VK_EXT_pipeline_creation_feedback` (internal issue 1560) * `VK_KHR_surface_protected_capabilities` (internal issue 1520)
1416 lines
59 KiB
Python
1416 lines
59 KiB
Python
#!/usr/bin/python3 -i
|
|
#
|
|
# Copyright (c) 2013-2019 The Khronos Group Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import pdb
|
|
import re
|
|
import sys
|
|
from collections import OrderedDict, namedtuple
|
|
from functools import reduce
|
|
|
|
from generator import OutputGenerator, write
|
|
|
|
class UnhandledCaseError(RuntimeError):
|
|
def __init__(self):
|
|
super().__init__('Got a case in the validity generator that we have not explicitly handled.')
|
|
|
|
def _genericIterateIntersection(a, b):
|
|
"""Iterate through all elements in a that are also in b.
|
|
|
|
Somewhat like a set's intersection(),
|
|
but not type-specific so it can work with OrderedDicts, etc.
|
|
It also returns a generator instead of a set,
|
|
so you can pick what container type you'd like,
|
|
if any.
|
|
"""
|
|
return (x for x in a if x in b)
|
|
|
|
|
|
def _make_ordered_dict(gen):
|
|
"""Make an ordered dict (with None as the values) from a generator."""
|
|
return OrderedDict(((x, None) for x in gen))
|
|
|
|
|
|
def _orderedDictIntersection(a, b):
|
|
return _make_ordered_dict(_genericIterateIntersection(a, b))
|
|
|
|
|
|
def _genericIsDisjoint(a, b):
|
|
"""Return true if nothing in a is also in b.
|
|
|
|
Like a set's is_disjoint(),
|
|
but not type-specific so it can work with OrderedDicts, etc.
|
|
"""
|
|
for _ in _genericIterateIntersection(a, b):
|
|
return False
|
|
# if we never enter the loop...
|
|
return True
|
|
|
|
class LengthEntry:
|
|
"""An entry in a (comma-separated) len attribute"""
|
|
|
|
def __init__(self, val):
|
|
self.other_param_name = None
|
|
self.null_terminated = False
|
|
self.number = None
|
|
if val == 'null-terminated':
|
|
self.null_terminated = True
|
|
return
|
|
|
|
if val.isdigit():
|
|
self.number = int(val)
|
|
return
|
|
|
|
# Must be another param name.
|
|
self.other_param_name = val
|
|
|
|
@staticmethod
|
|
def parse_len_from_param(param):
|
|
"""Get a list of LengthEntry."""
|
|
return [LengthEntry(elt) for elt in param.get('len').split(',')]
|
|
|
|
|
|
def _getElemName(elem, default=None):
|
|
"""Get the name associated with an element, either a name child or name attribute."""
|
|
name_elem = elem.find('name')
|
|
if name_elem is not None:
|
|
return name_elem.text
|
|
# Fallback if there is no child.
|
|
return elem.get('name', default)
|
|
|
|
def _getElemType(elem, default=None):
|
|
"""Get the type associated with an element, either a type child or type attribute."""
|
|
type_elem = elem.find('type')
|
|
if type_elem is not None:
|
|
return type_elem.text
|
|
# Fallback if there is no child.
|
|
return elem.get('type', default)
|
|
|
|
def _findNamedElem(elems, name):
|
|
"""Traverse a collection of elements with 'name' nodes or attributes, looking for and returning one with the right name.
|
|
|
|
NOTE: Many places where this is used might be better served by changing to a dictionary.
|
|
"""
|
|
for elem in elems:
|
|
if _getElemName(elem) == name:
|
|
return elem
|
|
return None
|
|
|
|
|
|
def _findNamedObject(collection, name):
|
|
"""Traverse a collection of elements with 'name' attributes, looking for and returning one with the right name.
|
|
|
|
NOTE: Many places where this is used might be better served by changing to a dictionary.
|
|
"""
|
|
for elt in collection:
|
|
if elt.name == name:
|
|
return elt
|
|
return None
|
|
|
|
|
|
class ValidityCollection:
|
|
"""Combines validity for a single entity."""
|
|
|
|
def __init__(self, entity, conventions):
|
|
self.entity = entity
|
|
self.conventions = conventions
|
|
self.lines = []
|
|
|
|
def possiblyAddExtensionRequirement(self, entity_data, entity_preface):
|
|
"""Add an extension-related validity statement if required.
|
|
|
|
entity_preface is a string that goes between "must be enabled prior to "
|
|
and the name of the entity, and normally ends in a macro.
|
|
For instance, might be "calling flink:" for a function.
|
|
"""
|
|
if entity_data.extensionname:
|
|
msg = 'The {} extension must: be enabled prior to {}{}'.format(
|
|
self.conventions.formatExtension(entity_data.extensionname), entity_preface, self.entity)
|
|
self.addValidityEntry(msg, 'extension', 'notenabled')
|
|
|
|
def addValidityEntry(self, msg, *args):
|
|
"""Add a validity entry, optionally with a VUID anchor.
|
|
|
|
If any trailing arguments are supplied,
|
|
an anchor is generated by concatenating them with dashes
|
|
at the end of the VUID anchor name.
|
|
"""
|
|
parts = ['*']
|
|
if args:
|
|
parts.append('[[{}]]'.format(
|
|
'-'.join(['VUID', self.entity] + list(args))))
|
|
parts.append(msg)
|
|
self.lines.append(' '.join(parts))
|
|
|
|
def addText(self, msg):
|
|
"""Add already formatted validity text."""
|
|
if not msg:
|
|
return
|
|
msg = msg.rstrip()
|
|
if not msg:
|
|
return
|
|
self.lines.append(msg)
|
|
|
|
@property
|
|
def text(self):
|
|
"""Access validity statements as a single string."""
|
|
if not self.lines:
|
|
return None
|
|
return '\n'.join(self.lines) + '\n'
|
|
|
|
|
|
class ValidityOutputGenerator(OutputGenerator):
|
|
"""ValidityOutputGenerator - subclass of OutputGenerator.
|
|
|
|
Generates AsciiDoc includes of valid usage information, for reference
|
|
pages and the Vulkan specification. Similar to DocOutputGenerator.
|
|
|
|
---- methods ----
|
|
ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
|
|
OutputGenerator. Defines additional internal state.
|
|
---- methods overriding base class ----
|
|
beginFile(genOpts)
|
|
endFile()
|
|
beginFeature(interface, emit)
|
|
endFeature()
|
|
genCmd(cmdinfo)
|
|
"""
|
|
|
|
def __init__(self,
|
|
errFile = sys.stderr,
|
|
warnFile = sys.stderr,
|
|
diagFile = sys.stdout):
|
|
OutputGenerator.__init__(self, errFile, warnFile, diagFile)
|
|
|
|
@property
|
|
def null(self):
|
|
"""Preferred spelling of NULL.
|
|
|
|
Delegates to the object implementing ConventionsBase.
|
|
"""
|
|
return self.conventions.null
|
|
|
|
@property
|
|
def structtype_member_name(self):
|
|
"""Return name of the structure type member.
|
|
|
|
Delegates to the object implementing ConventionsBase.
|
|
"""
|
|
return self.conventions.structtype_member_name
|
|
|
|
@property
|
|
def nextpointer_member_name(self):
|
|
"""Return name of the structure pointer chain member.
|
|
|
|
Delegates to the object implementing ConventionsBase.
|
|
"""
|
|
return self.conventions.nextpointer_member_name
|
|
|
|
def makeProseList(self, elements, connective='and'):
|
|
"""Make a (comma-separated) list for use in prose.
|
|
|
|
Adds a connective (by default, 'and')
|
|
before the last element if there are more than 1.
|
|
|
|
Delegates to the object implementing ConventionsBase.
|
|
"""
|
|
return self.conventions.makeProseList(elements, connective)
|
|
|
|
def makeValidityCollection(self, entity_name):
|
|
"""Create a ValidityCollection object, passing along our Conventions."""
|
|
return ValidityCollection(entity_name, self.conventions)
|
|
|
|
def beginFile(self, genOpts):
|
|
if not genOpts.conventions:
|
|
raise RuntimeError('Must specify conventions object to generator options')
|
|
self.conventions = genOpts.conventions
|
|
OutputGenerator.beginFile(self, genOpts)
|
|
|
|
def endFile(self):
|
|
OutputGenerator.endFile(self)
|
|
|
|
def beginFeature(self, interface, emit):
|
|
# Start processing in superclass
|
|
OutputGenerator.beginFeature(self, interface, emit)
|
|
self.currentExtension = interface.get('name')
|
|
|
|
def endFeature(self):
|
|
# Finish processing in superclass
|
|
OutputGenerator.endFeature(self)
|
|
|
|
@property
|
|
def struct_macro(self):
|
|
"""Get the appropriate format macro for a structure."""
|
|
# delegate to conventions
|
|
return self.conventions.struct_macro
|
|
|
|
def makeStructName(self, name):
|
|
"""Prepend the appropriate format macro for a structure to a structure type name."""
|
|
# delegate to conventions
|
|
return self.conventions.makeStructName(name)
|
|
|
|
def makeParameterName(self, name):
|
|
"""Prepend the appropriate format macro for a parameter/member to a parameter name."""
|
|
return 'pname:' + name
|
|
|
|
def makeBaseTypeName(self, name):
|
|
"""Prepend the appropriate format macro for a 'base type' to a type name."""
|
|
return 'basetype:' + name
|
|
|
|
def makeEnumerationName(self, name):
|
|
"""Prepend the appropriate format macro for an enumeration type to a enum type name."""
|
|
return 'elink:' + name
|
|
|
|
def makeFuncPointerName(self, name):
|
|
"""Prepend the appropriate format macro for a function pointer type to a type name."""
|
|
return 'tlink:' + name
|
|
|
|
def makeExternalTypeName(self, name):
|
|
"""Prepend the appropriate format macro for an external type like uint32_t to a type name."""
|
|
# delegate to conventions
|
|
return self.conventions.makeExternalTypeName(name)
|
|
|
|
def makeEnumerantName(self, name):
|
|
"""Prepend the appropriate format macro for an enumerate (value) to a enum value name."""
|
|
return 'ename:' + name
|
|
|
|
def makeAnchor(self, blockname, pname, category):
|
|
"""Create a unique namespaced Valid Usage anchor name.
|
|
|
|
blockname - command or structure
|
|
pname - parameter or member (may be None)
|
|
category - distinct implicit VU type
|
|
"""
|
|
# For debugging
|
|
# return '* '
|
|
if pname is not None:
|
|
return '* [[VUID-%s-%s-%s]] ' % (blockname, pname, category)
|
|
|
|
return '* [[VUID-%s-%s]] ' % (blockname, category)
|
|
|
|
def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes):
|
|
"""Generate an include file.
|
|
|
|
directory - subdirectory to put file in (relative pathname)
|
|
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 validity 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)
|
|
|
|
# Valid Usage
|
|
if validity:
|
|
write('.Valid Usage (Implicit)', file=fp)
|
|
write('****', file=fp)
|
|
write(validity, file=fp, end='')
|
|
write('****', file=fp)
|
|
write('', file=fp)
|
|
|
|
# Host Synchronization
|
|
if threadsafety:
|
|
write('.Host Synchronization', file=fp)
|
|
write('****', file=fp)
|
|
write(threadsafety, file=fp, end='')
|
|
write('****', file=fp)
|
|
write('', file=fp)
|
|
|
|
# Command Properties - contained within a block, to avoid table numbering
|
|
if commandpropertiesentry:
|
|
write('.Command Properties', file=fp)
|
|
write('****', file=fp)
|
|
write('[options="header", width="100%"]', file=fp)
|
|
write('|====', file=fp)
|
|
write('|<<VkCommandBufferLevel,Command Buffer Levels>>|<<vkCmdBeginRenderPass,Render Pass Scope>>|<<VkQueueFlagBits,Supported Queue Types>>|<<synchronization-pipeline-stages-types,Pipeline Type>>', file=fp)
|
|
write(commandpropertiesentry, file=fp)
|
|
write('|====', file=fp)
|
|
write('****', file=fp)
|
|
write('', file=fp)
|
|
|
|
# Success Codes - contained within a block, to avoid table numbering
|
|
has_success = (successcodes is not None and len(successcodes) > 0)
|
|
has_errors = (errorcodes is not None and len(errorcodes) > 0)
|
|
if has_success or has_errors:
|
|
write('.Return Codes', file=fp)
|
|
write('****', file=fp)
|
|
if has_success:
|
|
write('ifndef::doctype-manpage[]', file=fp)
|
|
write('<<fundamentals-successcodes,Success>>::', file=fp)
|
|
write('endif::doctype-manpage[]', file=fp)
|
|
write('ifdef::doctype-manpage[]', file=fp)
|
|
write('On success, this command returns::', file=fp)
|
|
write('endif::doctype-manpage[]', file=fp)
|
|
write(successcodes, file=fp)
|
|
if has_errors:
|
|
write('ifndef::doctype-manpage[]', file=fp)
|
|
write('<<fundamentals-errorcodes,Failure>>::', file=fp)
|
|
write('endif::doctype-manpage[]', file=fp)
|
|
write('ifdef::doctype-manpage[]', file=fp)
|
|
write('On failure, this command returns::', file=fp)
|
|
write('endif::doctype-manpage[]', file=fp)
|
|
write(errorcodes, file=fp)
|
|
write('****', file=fp)
|
|
write('', file=fp)
|
|
|
|
fp.close()
|
|
|
|
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 paramIsStaticArray(self, param):
|
|
"""Check if the parameter passed in is a static array."""
|
|
tail = param.find('name').tail
|
|
return tail and tail[0] == '['
|
|
|
|
def staticArrayLength(self, param):
|
|
"""Get the length of a parameter that's been identified as a static array."""
|
|
paramenumsize = param.find('enum')
|
|
if paramenumsize is not None:
|
|
return paramenumsize.text
|
|
|
|
return param.find('name').tail[1:-1]
|
|
|
|
def paramIsArray(self, param):
|
|
"""Check if the parameter passed in is a pointer to an array."""
|
|
return param.get('len') is not None
|
|
|
|
def getHandleParent(self, typename):
|
|
"""Get the parent of a handle object."""
|
|
types = self.registry.tree.findall("types/type")
|
|
elem = _findNamedElem(types, typename)
|
|
if elem:
|
|
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."""
|
|
ancestors = []
|
|
current = typename
|
|
while True:
|
|
current = self.getHandleParent(current)
|
|
if current is None:
|
|
return ancestors
|
|
ancestors.append(current)
|
|
|
|
def getHandleDispatchableAncestors(self, typename):
|
|
"""Get the ancestors of a handle object."""
|
|
ancestors = []
|
|
current = typename
|
|
while True:
|
|
current = self.getHandleParent(current)
|
|
if current is None:
|
|
return ancestors
|
|
if self.isHandleTypeDispatchable(current):
|
|
ancestors.append(current)
|
|
|
|
def isHandleTypeDispatchable(self, handlename):
|
|
"""Check if a parent object is dispatchable or not."""
|
|
handle = self.registry.tree.find("types/type/[name='" + handlename + "'][@category='handle']")
|
|
if handle is not None and handle.find('type').text == 'VK_DEFINE_HANDLE':
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def isHandleOptional(self, param, params):
|
|
# Simple, if it's optional, return true
|
|
if param.get('optional') is not None:
|
|
return True
|
|
|
|
# If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes.
|
|
if param.get('noautovalidity') is not None:
|
|
return True
|
|
|
|
# If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional
|
|
if self.paramIsArray(param):
|
|
for length in LengthEntry.parse_len_from_param(param):
|
|
if not length.other_param_name:
|
|
# don't care about constants or "null-terminated"
|
|
continue
|
|
|
|
other_param = _findNamedElem(params, length.other_param_name)
|
|
if other_param is None:
|
|
self.logMsg('warn', length.other_param_name, 'is listed as a length for parameter', param, 'but no such parameter exists')
|
|
if other_param and other_param.get('optional'):
|
|
return True
|
|
|
|
return False
|
|
|
|
def getTypeCategory(self, typename):
|
|
"""Get the category of a type."""
|
|
types = self.registry.tree.findall("types/type")
|
|
elem = _findNamedElem(types, typename)
|
|
if elem is not None:
|
|
return elem.get('category')
|
|
return None
|
|
|
|
def makeAsciiDocPreChunk(self, blockname, param, params):
|
|
"""Make a chunk of text for the end of a parameter if it is an array."""
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
|
|
# General pre-amble. Check optionality and add stuff.
|
|
asciidoc = self.makeAnchor(blockname, param_name, 'parameter')
|
|
|
|
if self.paramIsStaticArray(param):
|
|
asciidoc += 'Any given element of '
|
|
|
|
elif self.paramIsArray(param):
|
|
lengths = param.get('len').split(',')
|
|
|
|
# Find all the parameters that are called out as optional, so we can document that they might be zero, and the array may be ignored
|
|
optionallengths = []
|
|
for length in lengths:
|
|
if (length) != 'null-terminated' and (length) != '1':
|
|
for otherparam in params:
|
|
if otherparam.find('name').text == length:
|
|
if otherparam.get('optional'):
|
|
if self.paramIsPointer(otherparam):
|
|
optionallengths.append('the value referenced by ' + self.makeParameterName(length))
|
|
else:
|
|
optionallengths.append(self.makeParameterName(length))
|
|
|
|
# Document that these arrays may be ignored if any of the length values are 0
|
|
if len(optionallengths) != 0 or param.get('optional'):
|
|
asciidoc += 'If '
|
|
|
|
if len(optionallengths) != 0:
|
|
if len(optionallengths) == 1:
|
|
|
|
asciidoc += optionallengths[0]
|
|
asciidoc += ' is '
|
|
|
|
else:
|
|
asciidoc += ', or '.join(optionallengths)
|
|
asciidoc += ' are '
|
|
|
|
asciidoc += 'not `0`, '
|
|
|
|
if len(optionallengths) != 0 and param.get('optional'):
|
|
asciidoc += 'and '
|
|
|
|
if param.get('optional'):
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' is not `NULL`, '
|
|
|
|
elif param.get('optional'):
|
|
# Don't generate this stub for bitflags
|
|
if self.getTypeCategory(paramtype) != 'bitmask':
|
|
if param.get('optional').split(',')[0] == 'true':
|
|
asciidoc += 'If '
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' is not '
|
|
if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype):
|
|
asciidoc += '`NULL`'
|
|
elif self.getTypeCategory(paramtype) == 'handle':
|
|
asciidoc += 'dlink:VK_NULL_HANDLE'
|
|
else:
|
|
asciidoc += '`0`'
|
|
|
|
asciidoc += ', '
|
|
|
|
return asciidoc
|
|
|
|
def createValidationLineForParameterIntroChunk(self, blockname, param, params, typetext):
|
|
"""Make the generic asciidoc line chunk portion used for all parameters.
|
|
|
|
May return an empty string if nothing to validate.
|
|
"""
|
|
|
|
# Vulkan says 'must: be a valid pointer' a lot, OpenXR just says
|
|
# 'must: be a pointer'.
|
|
valid_text = self.genOpts.conventions.valid_pointer_prefix
|
|
if valid_text == '':
|
|
valid_pointer_text = 'pointer'
|
|
else:
|
|
valid_pointer_text = valid_text + ' pointer'
|
|
|
|
asciidoc = ''
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
|
|
asciidoc += self.makeAsciiDocPreChunk(blockname, param, params)
|
|
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' must: be '
|
|
|
|
if self.paramIsArray(param):
|
|
# Arrays. These are hard to get right, apparently
|
|
|
|
lengths = param.get('len').split(',')
|
|
|
|
if lengths[0] == 'null-terminated':
|
|
asciidoc += 'a null-terminated '
|
|
elif lengths[0] == '1':
|
|
asciidoc += 'a ' + valid_pointer_text + ' to '
|
|
else:
|
|
asciidoc += 'a ' + valid_pointer_text + ' to an array of '
|
|
|
|
# Handle equations, which are currently denoted with latex
|
|
if 'latexmath:' in lengths[0]:
|
|
asciidoc += lengths[0]
|
|
else:
|
|
asciidoc += self.makeParameterName(lengths[0])
|
|
asciidoc += ' '
|
|
|
|
for length in lengths[1:]:
|
|
if length == 'null-terminated':
|
|
# This should always be the last thing.
|
|
# If it ever isn't for some bizarre reason, then this will need some massaging.
|
|
asciidoc += 'null-terminated '
|
|
elif length == '1':
|
|
asciidoc += valid_pointer_text + 's to '
|
|
else:
|
|
asciidoc += valid_pointer_text + 's to arrays of '
|
|
# Handle equations, which are currently denoted with latex
|
|
if 'latexmath:' in length:
|
|
asciidoc += length
|
|
else:
|
|
asciidoc += self.makeParameterName(length)
|
|
asciidoc += ' '
|
|
|
|
# Void pointers don't actually point at anything - remove the word "to"
|
|
if paramtype == 'void':
|
|
if lengths[-1] == '1':
|
|
if len(lengths) > 1:
|
|
asciidoc = asciidoc[:-5] # Take care of the extra s added by the post array chunk function. #HACK#
|
|
else:
|
|
asciidoc = asciidoc[:-4]
|
|
else:
|
|
# An array of void values is a byte array.
|
|
asciidoc += 'byte'
|
|
|
|
elif paramtype == 'char':
|
|
# A null terminated array of chars is a string
|
|
if lengths[-1] == 'null-terminated':
|
|
asciidoc += 'UTF-8 string'
|
|
else:
|
|
# Else it's just a bunch of chars
|
|
asciidoc += 'char value'
|
|
elif param.text is not None:
|
|
# If a value is "const" that means it won't get modified, so it must be valid going into the function.
|
|
if 'const' in param.text:
|
|
typecategory = self.getTypeCategory(paramtype)
|
|
if (typecategory not in ('struct', 'union', 'basetype') and typecategory is not None) \
|
|
or not self.isStructAlwaysValid(blockname, paramtype):
|
|
asciidoc += 'valid '
|
|
|
|
asciidoc += typetext
|
|
|
|
# pluralize
|
|
if len(lengths) > 1 or (lengths[0] != '1' and lengths[0] != 'null-terminated'):
|
|
asciidoc += 's'
|
|
|
|
elif self.paramIsPointer(param):
|
|
# Handle pointers - which are really special case arrays (i.e. they don't have a length)
|
|
#TODO should do something here if someone ever uses some intricate comma-separated `optional`
|
|
pointercount = param.find('type').tail.count('*')
|
|
|
|
# Treat void* as an int
|
|
if paramtype == 'void':
|
|
pointercount -= 1
|
|
|
|
# Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
|
|
asciidoc += 'a '
|
|
asciidoc += (valid_pointer_text + ' to a ') * pointercount
|
|
|
|
# Handle void* and pointers to it
|
|
if paramtype == 'void':
|
|
# If there is only void*, it is just optional int - we don't need any language.
|
|
if pointercount == 0 and param.get('optional') is not None:
|
|
return '' # early return
|
|
|
|
if param.get('optional') is None or param.get('optional').split(',')[pointercount]:
|
|
# The last void* is just optional int (e.g. to be filled by the impl.)
|
|
typetext = 'pointer value'
|
|
|
|
# If a value is "const" that means it won't get modified, so it must be valid going into the function.
|
|
if param.text is not None and paramtype != 'void':
|
|
if 'const' in param.text:
|
|
asciidoc += 'valid '
|
|
|
|
asciidoc += typetext
|
|
|
|
else:
|
|
# Non-pointer, non-optional things must be valid
|
|
asciidoc += 'a valid '
|
|
asciidoc += typetext
|
|
|
|
if asciidoc != '':
|
|
asciidoc += '\n'
|
|
|
|
# Add additional line for non-optional bitmasks
|
|
isOutputParam = self.paramIsPointer(param) and not (param.text and 'const' in param.text)
|
|
if self.getTypeCategory(paramtype) == 'bitmask' and not isOutputParam:
|
|
isMandatory = param.get('optional') is None #TODO does not really handle if someone tries something like optional="true,false"
|
|
if isMandatory:
|
|
asciidoc += self.makeAnchor(blockname, param_name, 'requiredbitmask')
|
|
if self.paramIsArray(param):
|
|
asciidoc += 'Each element of '
|
|
asciidoc += 'pname:'
|
|
asciidoc += param_name
|
|
asciidoc += ' must: not be `0`'
|
|
asciidoc += '\n'
|
|
|
|
return asciidoc
|
|
|
|
def makeAsciiDocLineForParameter(self, blockname, param, params, typetext):
|
|
if param.get('noautovalidity') is not None:
|
|
return ''
|
|
asciidoc = self.createValidationLineForParameterIntroChunk(blockname, param, params, typetext)
|
|
|
|
return asciidoc
|
|
|
|
def isStructAlwaysValid(self, blockname, structname):
|
|
"""Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance)."""
|
|
|
|
struct = self.registry.tree.find("types/type[@name='" + structname + "']")
|
|
|
|
if struct.get('returnedonly'):
|
|
return True
|
|
|
|
params = struct.findall('member')
|
|
|
|
for param in params:
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
typecategory = self.getTypeCategory(paramtype)
|
|
|
|
if param_name in (self.structtype_member_name, self.nextpointer_member_name):
|
|
return False
|
|
|
|
if param.get('noautovalidity'):
|
|
return False
|
|
|
|
if paramtype in ('void', 'char') or self.paramIsArray(param) or self.paramIsPointer(param):
|
|
if self.makeAsciiDocLineForParameter(blockname, param, params, '') != '':
|
|
return False
|
|
elif typecategory in ('handle', 'enum', 'bitmask'):
|
|
return False
|
|
elif typecategory in ('struct', 'union'):
|
|
if self.isStructAlwaysValid(blockname, paramtype) is False:
|
|
return False
|
|
|
|
return True
|
|
|
|
def fix_an(self, text):
|
|
"""Fix 'a' vs 'an' in a string"""
|
|
return re.sub(r' a ([a-z]+:)?([aAeEiIoOxX])', r' an \1\2', text)
|
|
|
|
def createValidationLineForParameter(self, blockname, param, params, typecategory):
|
|
"""Make an entire asciidoc line for a given parameter."""
|
|
asciidoc = ''
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
type_name = paramtype
|
|
|
|
is_array = self.paramIsArray(param)
|
|
is_pointer = self.paramIsPointer(param)
|
|
needs_recursive_validity = (is_array or
|
|
is_pointer or
|
|
not self.isStructAlwaysValid(blockname, type_name))
|
|
|
|
if type_name in ('void', 'char'):
|
|
# Chars and void are special cases - needs care inside the generator functions
|
|
# A null-terminated char array is a string, else it's chars.
|
|
# An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, '')
|
|
elif typecategory == 'bitmask':
|
|
bitsname = type_name.replace('Flags', 'FlagBits')
|
|
if self.registry.tree.find("enums[@name='" + bitsname + "']") is None:
|
|
# Empty bit mask: presumably just a placeholder (or only in an extension not enabled for this build)
|
|
asciidoc += self.makeAnchor(blockname,
|
|
param_name, 'zerobitmask')
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' must: be `0`'
|
|
asciidoc += '\n'
|
|
else:
|
|
const_in_text = param.text is not None and 'const' in param.text
|
|
|
|
if is_array:
|
|
if const_in_text:
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combinations of ' + self.makeEnumerationName(bitsname) + ' value')
|
|
else:
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype) + ' value')
|
|
elif is_pointer:
|
|
if const_in_text:
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values')
|
|
else:
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype) + ' value')
|
|
else:
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values')
|
|
elif typecategory == 'handle':
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype) + ' handle')
|
|
elif typecategory == 'enum':
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype) + ' value')
|
|
elif typecategory == 'struct':
|
|
if self.paramIsArray(param) or self.paramIsPointer(param) or not self.isStructAlwaysValid(blockname, paramtype):
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype) + ' structure')
|
|
elif typecategory == 'union':
|
|
if self.paramIsArray(param) or self.paramIsPointer(param) or not self.isStructAlwaysValid(blockname, paramtype):
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype) + ' union')
|
|
elif self.paramIsArray(param) or self.paramIsPointer(param):
|
|
asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeBaseTypeName(paramtype) + ' value')
|
|
|
|
return asciidoc
|
|
|
|
def makeAsciiDocHandleParent(self, blockname, param, params):
|
|
"""Make an asciidoc validity entry for a handle's parent object."""
|
|
asciidoc = ''
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
|
|
# Deal with handle parents
|
|
handleparent = self.getHandleParent(paramtype)
|
|
if handleparent is not None:
|
|
parentreference = None
|
|
for otherparam in params:
|
|
if otherparam.find('type').text == handleparent:
|
|
parentreference = otherparam.find('name').text
|
|
if parentreference:
|
|
asciidoc += self.makeAnchor(blockname, param_name, 'parent')
|
|
|
|
if self.isHandleOptional(param, params):
|
|
if self.paramIsArray(param):
|
|
asciidoc += 'Each element of '
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' that is a valid handle'
|
|
else:
|
|
asciidoc += 'If '
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' is a valid handle, it'
|
|
else:
|
|
if self.paramIsArray(param):
|
|
asciidoc += 'Each element of '
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' must: have been created, allocated, or retrieved from '
|
|
asciidoc += self.makeParameterName(parentreference)
|
|
|
|
asciidoc += '\n'
|
|
return asciidoc
|
|
|
|
def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params):
|
|
"""Make an asciidoc validity entry for a common ancestors between handles."""
|
|
asciidoc = ''
|
|
|
|
if len(handles) > 1:
|
|
ancestormap = {}
|
|
anyoptional = False
|
|
|
|
# Find all the ancestors
|
|
for param in handles:
|
|
paramtype = _getElemType(param)
|
|
|
|
if not self.paramIsPointer(param) or (param.text and 'const' in param.text):
|
|
ancestors = self.getHandleDispatchableAncestors(paramtype)
|
|
|
|
ancestormap[param] = ancestors
|
|
|
|
anyoptional |= self.isHandleOptional(param, params)
|
|
|
|
# Remove redundant ancestor lists
|
|
for param in handles:
|
|
paramtype = _getElemType(param)
|
|
|
|
removals = []
|
|
for ancestors in ancestormap.items():
|
|
if paramtype in ancestors[1]:
|
|
removals.append(ancestors[0])
|
|
|
|
if removals != []:
|
|
for removal in removals:
|
|
del(ancestormap[removal])
|
|
|
|
# Intersect
|
|
|
|
if len(ancestormap.values()) > 1:
|
|
current = list(ancestormap.values())[0]
|
|
for ancestors in list(ancestormap.values())[1:]:
|
|
current = [val for val in current if val in ancestors]
|
|
|
|
if len(current) > 0:
|
|
commonancestor = current[0]
|
|
|
|
if len(ancestormap.keys()) > 1:
|
|
|
|
asciidoc += self.makeAnchor(blockname, None, 'commonparent')
|
|
|
|
parametertexts = []
|
|
for param in ancestormap.keys():
|
|
param_name = _getElemName(param)
|
|
parametertext = self.makeParameterName(param_name)
|
|
if self.paramIsArray(param):
|
|
parametertext = 'the elements of ' + parametertext
|
|
parametertexts.append(parametertext)
|
|
|
|
parametertexts.sort()
|
|
|
|
if len(parametertexts) > 2:
|
|
asciidoc += 'Each of '
|
|
else:
|
|
asciidoc += 'Both of '
|
|
|
|
asciidoc += ", ".join(parametertexts[:-1])
|
|
asciidoc += ', and '
|
|
asciidoc += parametertexts[-1]
|
|
if anyoptional is True:
|
|
asciidoc += ' that are valid handles'
|
|
asciidoc += ' must: have been created, allocated, or retrieved from the same '
|
|
asciidoc += self.makeStructName(commonancestor)
|
|
asciidoc += '\n'
|
|
|
|
return asciidoc
|
|
|
|
def makeStructureType(self, structname, param):
|
|
"""Generate an asciidoc validity line for the type value of a struct."""
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
|
|
asciidoc = self.makeAnchor(structname, param_name, 'sType')
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' must: be '
|
|
|
|
values = param.get('values')
|
|
if values:
|
|
# Extract each enumerant value. They could be validated in the
|
|
# same fashion as validextensionstructs in
|
|
# makeStructureExtensionPointer, although that's not relevant in
|
|
# the current extension struct model.
|
|
valuelist = [ self.makeEnumerantName(v) for v in values.split(',') ]
|
|
else:
|
|
structuretype = ''
|
|
for elem in re.findall(r'(([A-Z][a-z]+)|([A-Z][A-Z]+))', structname):
|
|
if elem[0] == 'Vk':
|
|
structuretype += 'VK_STRUCTURE_TYPE_'
|
|
else:
|
|
structuretype += elem[0].upper()
|
|
structuretype += '_'
|
|
valuelist = [ self.makeEnumerantName(structuretype[:-1]) ]
|
|
|
|
if len(valuelist) > 0:
|
|
if len(valuelist) == 1:
|
|
asciidoc += valuelist[0]
|
|
else:
|
|
asciidoc += (', ').join(valuelist[:-1]) + ', or ' + valuelist[-1]
|
|
asciidoc += '\n'
|
|
|
|
return asciidoc
|
|
|
|
def makeStructureExtensionPointer(self, blockname, param):
|
|
"""Generate an asciidoc validity line for the pointer chain member value of a struct."""
|
|
param_name = _getElemName(param)
|
|
|
|
if param.get('validextensionstructs') is not None:
|
|
self.logMsg('warn', blockname, 'validextensionstructs is deprecated/removed', '\n')
|
|
|
|
asciidoc = self.makeAnchor(blockname, param_name, self.nextpointer_member_name)
|
|
validextensionstructs = self.registry.validextensionstructs.get(blockname)
|
|
extensionstructs = []
|
|
|
|
if validextensionstructs is not None:
|
|
# Check each structure name and skip it if not required by the
|
|
# generator. This allows tagging extension structs in the XML
|
|
# that are only included in validity when needed for the spec
|
|
# being targeted.
|
|
for struct in validextensionstructs:
|
|
# Unpleasantly breaks encapsulation. Should be a method in the registry class
|
|
type = self.registry.lookupElementInfo(struct, self.registry.typedict)
|
|
if type is None:
|
|
self.logMsg('warn', 'makeStructureExtensionPointer: struct', struct, 'is in a validextensionstructs= attribute but is not in the registry')
|
|
elif type.required:
|
|
extensionstructs.append('slink:' + struct)
|
|
else:
|
|
self.logMsg('diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required')
|
|
|
|
if not extensionstructs:
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' must: be '
|
|
asciidoc += self.null
|
|
elif len(extensionstructs) == 1:
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' must: be '
|
|
asciidoc += self.null
|
|
asciidoc +=' or a pointer to a valid instance of '
|
|
asciidoc += extensionstructs[0]
|
|
else:
|
|
asciidoc += 'Each '
|
|
asciidoc += self.makeParameterName(param_name)
|
|
asciidoc += ' member of any structure (including this one) in the '
|
|
asciidoc += 'pname:' + self.nextpointer_member_name
|
|
asciidoc += ' chain must: be either '
|
|
asciidoc += self.null
|
|
asciidoc += ' or a pointer to a valid instance of '
|
|
|
|
asciidoc += self.makeProseList(extensionstructs, 'or')
|
|
asciidoc += '\n'
|
|
|
|
asciidoc += self.makeAnchor(blockname, self.structtype_member_name, 'unique')
|
|
asciidoc += 'Each pname:' + self.structtype_member_name + ' member in the '
|
|
asciidoc += 'pname:' + self.nextpointer_member_name
|
|
asciidoc += ' chain must: be unique'
|
|
|
|
asciidoc += '\n'
|
|
|
|
return asciidoc
|
|
|
|
def makeValidUsageStatementsReturnedOnly(self, cmd, blockname, params):
|
|
"""Generate all the valid usage information for a given struct
|
|
that's only ever filled out by the implementation other than the
|
|
structure type and pointer chain members.
|
|
"""
|
|
|
|
# Start the asciidoc block for this
|
|
asciidoc = ''
|
|
|
|
for param in params:
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
|
|
# Valid usage ID tags (VUID) are generated for various
|
|
# conditions based on the name of the block (structure or
|
|
# command), name of the element (member or parameter), and type
|
|
# of VU statement.
|
|
|
|
if param.get('noautovalidity') is None:
|
|
# Generate language to independently validate a parameter
|
|
if self.conventions.is_structure_type_member(paramtype, param_name):
|
|
asciidoc += self.makeStructureType(blockname, param)
|
|
elif self.conventions.is_nextpointer_member(paramtype, param_name) and cmd.get('structextends') is None:
|
|
asciidoc += self.makeStructureExtensionPointer(blockname, param)
|
|
|
|
# In case there's nothing to report, return None
|
|
if asciidoc == '':
|
|
return None
|
|
|
|
return asciidoc
|
|
|
|
def makeValidUsageStatements(self, cmd, blockname, params):
|
|
"""Generate all the valid usage information for a given struct or command."""
|
|
# Start the asciidoc block for this
|
|
asciidoc = ''
|
|
|
|
handles = []
|
|
anyparentedhandlesoptional = False
|
|
parentdictionary = {}
|
|
arraylengths = set()
|
|
for param in params:
|
|
param_name = _getElemName(param)
|
|
paramtype = _getElemType(param)
|
|
|
|
# Valid usage ID tags (VUID) are generated for various
|
|
# conditions based on the name of the block (structure or
|
|
# command), name of the element (member or parameter), and type
|
|
# of VU statement.
|
|
|
|
# Get the type's category
|
|
typecategory = self.getTypeCategory(paramtype)
|
|
|
|
if param.get('noautovalidity') is None:
|
|
# Generate language to independently validate a parameter
|
|
if self.conventions.is_structure_type_member(paramtype, param_name):
|
|
asciidoc += self.makeStructureType(blockname, param)
|
|
elif self.conventions.is_nextpointer_member(paramtype, param_name):
|
|
if cmd.get('structextends') is None:
|
|
asciidoc += self.makeStructureExtensionPointer(blockname, param)
|
|
else:
|
|
asciidoc += self.createValidationLineForParameter(blockname, param, params, typecategory)
|
|
|
|
# Ensure that any parenting is properly validated, and list that a handle was found
|
|
if typecategory == 'handle':
|
|
handles.append(param)
|
|
|
|
# Get the array length for this parameter
|
|
arraylength = param.get('len')
|
|
if arraylength is not None:
|
|
arraylengths.update(set(arraylength.split(',')))
|
|
|
|
# For any vkQueue* functions, there might be queue type data
|
|
if 'vkQueue' in blockname:
|
|
# The queue type must be valid
|
|
queuetypes = cmd.get('queues')
|
|
if queuetypes:
|
|
queuebits = []
|
|
for queuetype in re.findall(r'([^,]+)', queuetypes):
|
|
queuebits.append(queuetype.replace('_',' '))
|
|
|
|
asciidoc += self.makeAnchor(blockname, None, 'queuetype')
|
|
asciidoc += 'The pname:queue must: support '
|
|
if len(queuebits) == 1:
|
|
asciidoc += queuebits[0]
|
|
else:
|
|
asciidoc += (', ').join(queuebits[:-1])
|
|
asciidoc += ', or '
|
|
asciidoc += queuebits[-1]
|
|
asciidoc += ' operations'
|
|
asciidoc += '\n'
|
|
|
|
if 'vkCmd' in blockname:
|
|
# The commandBuffer parameter must be being recorded
|
|
asciidoc += self.makeAnchor(blockname, 'commandBuffer', 'recording')
|
|
asciidoc += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>'
|
|
asciidoc += '\n'
|
|
|
|
#
|
|
# Start of valid queue type validation - command pool must have been
|
|
# allocated against a queue with at least one of the valid queue types
|
|
asciidoc += self.makeAnchor(blockname, 'commandBuffer', 'cmdpool')
|
|
|
|
#
|
|
# This test for vkCmdFillBuffer is a hack, since we have no path
|
|
# to conditionally have queues enabled or disabled by an extension.
|
|
# As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
|
|
if blockname == 'vkCmdFillBuffer':
|
|
if 'VK_KHR_maintenance1' in self.registry.requiredextensions:
|
|
asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support transfer, graphics or compute operations\n'
|
|
else:
|
|
asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support graphics or compute operations\n'
|
|
else:
|
|
# The queue type must be valid
|
|
queuetypes = cmd.get('queues')
|
|
queuebits = []
|
|
for queuetype in re.findall(r'([^,]+)', queuetypes):
|
|
queuebits.append(queuetype.replace('_',' '))
|
|
|
|
asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
|
|
if len(queuebits) == 1:
|
|
asciidoc += queuebits[0]
|
|
else:
|
|
asciidoc += (', ').join(queuebits[:-1])
|
|
asciidoc += ', or '
|
|
asciidoc += queuebits[-1]
|
|
asciidoc += ' operations'
|
|
asciidoc += '\n'
|
|
|
|
# Must be called inside/outside a renderpass appropriately
|
|
renderpass = cmd.get('renderpass')
|
|
|
|
if renderpass != 'both':
|
|
asciidoc += self.makeAnchor(blockname, None, 'renderpass')
|
|
asciidoc += 'This command must: only be called '
|
|
asciidoc += renderpass
|
|
asciidoc += ' of a render pass instance'
|
|
asciidoc += '\n'
|
|
|
|
# Must be in the right level command buffer
|
|
cmdbufferlevel = cmd.get('cmdbufferlevel')
|
|
|
|
if cmdbufferlevel != 'primary,secondary':
|
|
asciidoc += self.makeAnchor(blockname, None, 'bufferlevel')
|
|
asciidoc += 'pname:commandBuffer must: be a '
|
|
asciidoc += cmdbufferlevel
|
|
asciidoc += ' sname:VkCommandBuffer'
|
|
asciidoc += '\n'
|
|
|
|
# Any non-optional arraylengths should specify they must be greater than 0
|
|
for param in params:
|
|
param_name = _getElemName(param)
|
|
|
|
for arraylength in arraylengths:
|
|
if param_name == arraylength and param.get('optional') is None:
|
|
# Get all the array dependencies
|
|
arrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
|
|
|
|
# Get all the optional array dependencies, including those not generating validity for some reason
|
|
optionalarrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
|
|
optionalarrays.extend(cmd.findall("param/[@len='" + arraylength + "'][@noautovalidity='true']"))
|
|
|
|
# If arraylength can ever be not a legal part of an
|
|
# asciidoc anchor name, this will need to be altered.
|
|
asciidoc += self.makeAnchor(blockname, arraylength, 'arraylength')
|
|
|
|
# Allow lengths to be arbitrary if all their dependents are optional
|
|
if len(optionalarrays) == len(arrays) and len(optionalarrays) != 0:
|
|
asciidoc += 'If '
|
|
if len(optionalarrays) > 1:
|
|
asciidoc += 'any of '
|
|
|
|
for array in optionalarrays[:-1]:
|
|
asciidoc += self.makeParameterName(optionalarrays.find('name').text)
|
|
asciidoc += ', '
|
|
|
|
if len(optionalarrays) > 1:
|
|
asciidoc += 'and '
|
|
asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
|
|
asciidoc += ' are '
|
|
else:
|
|
asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
|
|
asciidoc += ' is '
|
|
|
|
asciidoc += 'not ' + self.null + ', '
|
|
|
|
if self.paramIsPointer(param):
|
|
asciidoc += 'the value referenced by '
|
|
|
|
elif self.paramIsPointer(param):
|
|
asciidoc += 'The value referenced by '
|
|
|
|
asciidoc += self.makeParameterName(arraylength)
|
|
asciidoc += ' must: be greater than `0`'
|
|
asciidoc += '\n'
|
|
|
|
# Find the parents of all objects referenced in this command
|
|
for param in handles:
|
|
paramtype = _getElemType(param)
|
|
# Don't detect a parent for return values!
|
|
if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text):
|
|
|
|
parent = self.getHandleParent(paramtype)
|
|
|
|
if parent is not None:
|
|
asciidoc += self.makeAsciiDocHandleParent(blockname, param, params)
|
|
|
|
# Find the common ancestor of all objects referenced in this command
|
|
asciidoc += self.makeAsciiDocHandlesCommonAncestor(blockname, handles, params)
|
|
|
|
# In case there's nothing to report, return None
|
|
if asciidoc == '':
|
|
return None
|
|
# Delimit the asciidoc block
|
|
return asciidoc
|
|
|
|
def makeThreadSafetyBlock(self, cmd, paramtext):
|
|
"""Generate C function pointer typedef for <command> Element"""
|
|
paramdecl = ''
|
|
|
|
# Find and add any parameters that are thread unsafe
|
|
explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
|
|
if explicitexternsyncparams is not None:
|
|
for param in explicitexternsyncparams:
|
|
externsyncattribs = param.get('externsync')
|
|
param_name = _getElemName(param)
|
|
for externsyncattrib in externsyncattribs.split(','):
|
|
paramdecl += '* '
|
|
paramdecl += 'Host access to '
|
|
if externsyncattrib == 'true':
|
|
if self.paramIsArray(param):
|
|
paramdecl += 'each member of ' + \
|
|
self.makeParameterName(param_name)
|
|
elif self.paramIsPointer(param):
|
|
paramdecl += 'the object referenced by ' + \
|
|
self.makeParameterName(param_name)
|
|
else:
|
|
paramdecl += self.makeParameterName(param_name)
|
|
else:
|
|
paramdecl += 'pname:'
|
|
paramdecl += externsyncattrib
|
|
paramdecl += ' must: be externally synchronized\n'
|
|
|
|
# Vulkan-specific
|
|
# For any vkCmd* functions, the command pool is externally synchronized
|
|
if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text:
|
|
paramdecl += '* '
|
|
paramdecl += 'Host access to the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized'
|
|
paramdecl += '\n'
|
|
|
|
# Find and add any "implicit" parameters that are thread unsafe
|
|
implicitexternsyncparams = cmd.find('implicitexternsyncparams')
|
|
if implicitexternsyncparams is not None:
|
|
for elem in implicitexternsyncparams:
|
|
paramdecl += '* '
|
|
paramdecl += 'Host access to '
|
|
paramdecl += elem.text
|
|
paramdecl += ' must: be externally synchronized\n'
|
|
|
|
if not paramdecl:
|
|
return None
|
|
return paramdecl
|
|
|
|
def makeCommandPropertiesTableEntry(self, cmd, name):
|
|
|
|
if 'vkCmd' in name:
|
|
# Must be called inside/outside a renderpass appropriately
|
|
cmdbufferlevel = cmd.get('cmdbufferlevel')
|
|
cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))
|
|
|
|
renderpass = cmd.get('renderpass')
|
|
renderpass = renderpass.capitalize()
|
|
|
|
#
|
|
# This test for vkCmdFillBuffer is a hack, since we have no path
|
|
# to conditionally have queues enabled or disabled by an extension.
|
|
# As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
|
|
if name == 'vkCmdFillBuffer':
|
|
if 'VK_KHR_maintenance1' in self.registry.requiredextensions:
|
|
queues = 'Transfer + \nGraphics + \nCompute'
|
|
else:
|
|
queues = 'Graphics + \nCompute'
|
|
else:
|
|
queues = cmd.get('queues')
|
|
queues = (' + \n').join(queues.title().split(','))
|
|
|
|
pipeline = cmd.get('pipeline')
|
|
if pipeline:
|
|
pipeline = pipeline.capitalize()
|
|
else:
|
|
pipeline = ''
|
|
|
|
return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues + '|' + pipeline
|
|
elif 'vkQueue' in name:
|
|
# Must be called inside/outside a renderpass appropriately
|
|
|
|
queues = cmd.get('queues')
|
|
if queues is None:
|
|
queues = 'Any'
|
|
else:
|
|
queues = (' + \n').join(queues.upper().split(','))
|
|
|
|
return '|-|-|' + queues + '|-'
|
|
|
|
return None
|
|
|
|
# Check each enumerant name in the enums list and remove it if not
|
|
# required by the generator. This allows specifying success and error
|
|
# codes for extensions that are only included in validity when needed
|
|
# for the spec being targeted.
|
|
def findRequiredEnums(self, enums):
|
|
out = []
|
|
for enum in enums:
|
|
# Unpleasantly breaks encapsulation. Should be a method in the registry class
|
|
ei = self.registry.lookupElementInfo(enum, self.registry.enumdict)
|
|
if ei is None:
|
|
self.logMsg('warn', 'findRequiredEnums: enum', enum,
|
|
'is in an attribute list but is not in the registry')
|
|
elif ei.required:
|
|
out.append(enum)
|
|
else:
|
|
self.logMsg('diag', 'findRequiredEnums: enum', enum, 'IS NOT required, skipping')
|
|
return out
|
|
|
|
def makeReturnCodeList(self, attrib, cmd, name):
|
|
"""Return a list of possible return codes for a function.
|
|
|
|
attrib is either 'successcodes' or 'errorcodes'.
|
|
"""
|
|
return_lines = []
|
|
RETURN_CODE_FORMAT = '* ename:{}'
|
|
|
|
codes_attr = cmd.get(attrib)
|
|
if codes_attr:
|
|
codes = self.findRequiredEnums(codes_attr.split(','))
|
|
if codes:
|
|
return_lines.extend((RETURN_CODE_FORMAT.format(code)
|
|
for code in codes))
|
|
|
|
applicable_ext_codes = (ext_code
|
|
for ext_code in self.registry.commandextensionsuccesses
|
|
if ext_code.command == name)
|
|
for ext_code in applicable_ext_codes:
|
|
line = RETURN_CODE_FORMAT.format(ext_code.value)
|
|
if ext_code.extension:
|
|
line += ' [only if {} is enabled]'.format(
|
|
self.conventions.formatExtension(ext_code.extension))
|
|
|
|
return_lines.append(line)
|
|
if return_lines:
|
|
return '\n'.join(return_lines)
|
|
|
|
return None
|
|
|
|
def makeSuccessCodes(self, cmd, name):
|
|
return self.makeReturnCodeList('successcodes', cmd, name)
|
|
|
|
def makeErrorCodes(self, cmd, name):
|
|
return self.makeReturnCodeList('errorcodes', cmd, name)
|
|
|
|
def genCmd(self, cmdinfo, name, alias):
|
|
"""Command generation."""
|
|
OutputGenerator.genCmd(self, cmdinfo, name, alias)
|
|
|
|
# @@@ (Jon) something needs to be done here to handle aliases, probably
|
|
|
|
# Get all the parameters
|
|
params = cmdinfo.elem.findall('param')
|
|
|
|
validity = self.makeValidUsageStatements(cmdinfo.elem, name, params)
|
|
|
|
threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
|
|
# Vulkan-specific
|
|
commandpropertiesentry = self.makeCommandPropertiesTableEntry(cmdinfo.elem, name)
|
|
successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
|
|
errorcodes = self.makeErrorCodes(cmdinfo.elem, name)
|
|
|
|
self.writeInclude('protos', name, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes)
|
|
|
|
def genStruct(self, typeinfo, typename, alias):
|
|
"""Struct Generation."""
|
|
OutputGenerator.genStruct(self, typeinfo, typename, alias)
|
|
|
|
# @@@ (Jon) something needs to be done here to handle aliases, probably
|
|
|
|
# Anything that's only ever returned can't be set by the user, so shouldn't have any validity information.
|
|
if typeinfo.elem.get('returnedonly') is None:
|
|
params = typeinfo.elem.findall('member')
|
|
|
|
validity = self.makeValidUsageStatements(typeinfo.elem, typename, params)
|
|
threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')
|
|
|
|
self.writeInclude('structs', typename, validity, threadsafety, None, None, None)
|
|
else:
|
|
# Need to generate structure type and next pointer chain member validation
|
|
params = typeinfo.elem.findall('member')
|
|
|
|
validity = self.makeValidUsageStatementsReturnedOnly(typeinfo.elem, typename, params)
|
|
|
|
self.writeInclude('structs', typename, validity, None, None, None, None)
|
|
|
|
def genGroup(self, groupinfo, groupName, alias):
|
|
"""Group (e.g. C "enum" type) generation.
|
|
For the validity generator, this just tags individual enumerants
|
|
as required or not.
|
|
"""
|
|
OutputGenerator.genGroup(self, groupinfo, groupName, alias)
|
|
|
|
# @@@ (Jon) something needs to be done here to handle aliases, probably
|
|
|
|
groupElem = groupinfo.elem
|
|
|
|
# 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.
|
|
for elem in groupElem.findall('enum'):
|
|
name = elem.get('name')
|
|
ei = self.registry.lookupElementInfo(name, self.registry.enumdict)
|
|
|
|
# Tag enumerant as required or not
|
|
ei.required = self.isEnumRequired(elem)
|
|
|
|
def genType(self, typeinfo, typename, alias):
|
|
"""Type Generation."""
|
|
OutputGenerator.genType(self, typeinfo, typename, alias)
|
|
|
|
# @@@ (Jon) something needs to be done here to handle aliases, probably
|
|
|
|
category = typeinfo.elem.get('category')
|
|
if category in ('struct', 'union'):
|
|
self.genStruct(typeinfo, typename, alias)
|