| #!/usr/bin/python3 -i |
| # |
| # Copyright (c) 2013-2018 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 os,re,sys |
| from generator import * |
| |
| # 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) |
| class ValidityOutputGenerator(OutputGenerator): |
| """Generate specified API interfaces in a specific style, such as a C header""" |
| def __init__(self, |
| errFile = sys.stderr, |
| warnFile = sys.stderr, |
| diagFile = sys.stdout): |
| OutputGenerator.__init__(self, errFile, warnFile, diagFile) |
| |
| def beginFile(self, genOpts): |
| OutputGenerator.beginFile(self, genOpts) |
| def endFile(self): |
| OutputGenerator.endFile(self) |
| def beginFeature(self, interface, emit): |
| # Start processing in superclass |
| OutputGenerator.beginFeature(self, interface, emit) |
| def endFeature(self): |
| # Finish processing in superclass |
| OutputGenerator.endFeature(self) |
| |
| def makeParameterName(self, name): |
| return 'pname:' + name |
| |
| def makeStructName(self, name): |
| return 'sname:' + name |
| |
| def makeBaseTypeName(self, name): |
| return 'basetype:' + name |
| |
| def makeEnumerationName(self, name): |
| return 'elink:' + name |
| |
| def makeEnumerantName(self, name): |
| return 'ename:' + name |
| |
| def makeFLink(self, name): |
| return 'flink:' + name |
| |
| # Create a unique namespaced Valid Usage anchor name given a |
| # blockname - command or structure |
| # pname - parameter or member (may be None) |
| # category - distinct implicit VU type |
| def makeAnchor(self, blockname, pname, category): |
| # For debugging |
| # return '* ' |
| if pname != None: |
| return '* [[VUID-%s-%s-%s]] ' % (blockname, pname, category) |
| else: |
| return '* [[VUID-%s-%s]] ' % (blockname, category) |
| |
| # |
| # Generate an include file |
| # |
| # directory - subdirectory to put file in |
| # basename - base name of the file |
| # contents - contents of the file (Asciidoc boilerplate aside) |
| def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes): |
| # 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('// WARNING: DO NOT MODIFY! This file is automatically generated from the vk.xml registry', file=fp) |
| |
| # Valid Usage |
| if validity is not None: |
| write('.Valid Usage (Implicit)', file=fp) |
| write('****', file=fp) |
| write(validity, file=fp, end='') |
| write('****', file=fp) |
| write('', file=fp) |
| |
| # Host Synchronization |
| if threadsafety is not None: |
| 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 is not None: |
| 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 |
| if successcodes is not None or errorcodes is not None: |
| write('.Return Codes', file=fp) |
| write('****', file=fp) |
| if successcodes is not None: |
| 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 errorcodes is not None: |
| 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() |
| |
| # |
| # Check if the parameter passed in is a pointer |
| def paramIsPointer(self, param): |
| ispointer = False |
| paramtype = param.find('type') |
| if paramtype.tail is not None and '*' in paramtype.tail: |
| ispointer = True |
| |
| return ispointer |
| |
| # |
| # Check if the parameter passed in is a static array |
| def paramIsStaticArray(self, param): |
| if param.find('name').tail is not None: |
| if param.find('name').tail[0] == '[': |
| return True |
| |
| # |
| # Get the length of a parameter that's been identified as a static array |
| def staticArrayLength(self, param): |
| paramname = param.find('name') |
| paramenumsize = param.find('enum') |
| |
| if paramenumsize is not None: |
| return paramenumsize.text |
| else: |
| return paramname.tail[1:-1] |
| |
| # |
| # Check if the parameter passed in is a pointer to an array |
| def paramIsArray(self, param): |
| return param.attrib.get('len') is not None |
| |
| # |
| # Get the parent of a handle object |
| def getHandleParent(self, typename): |
| types = self.registry.tree.findall("types/type") |
| for elem in types: |
| if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename: |
| return elem.attrib.get('parent') |
| |
| return None |
| |
| # |
| # Get the ancestors of a handle object |
| def getHandleAncestors(self, typename): |
| ancestors = [] |
| current = typename |
| while True: |
| current = self.getHandleParent(current) |
| if current is None: |
| return ancestors |
| ancestors.append(current) |
| |
| # |
| # Get the ancestors of a handle object |
| def getHandleDispatchableAncestors(self, typename): |
| ancestors = [] |
| current = typename |
| while True: |
| current = self.getHandleParent(current) |
| if current is None: |
| return ancestors |
| if self.isHandleTypeDispatchable(current): |
| ancestors.append(current) |
| |
| # |
| # Check if a parent object is dispatchable or not |
| def isHandleTypeDispatchable(self, handlename): |
| 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): |
| |
| # See if the handle is optional |
| isOptional = False |
| |
| # Simple, if it's optional, return true |
| if param.attrib.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.attrib.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): |
| lengths = param.attrib.get('len').split(',') |
| for length in lengths: |
| if (length) != 'null-terminated' and (length) != '1': |
| for otherparam in params: |
| if otherparam.find('name').text == length: |
| if otherparam.attrib.get('optional') is not None: |
| return True |
| |
| return False |
| # |
| # Get the category of a type |
| def getTypeCategory(self, typename): |
| types = self.registry.tree.findall("types/type") |
| for elem in types: |
| if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename: |
| return elem.attrib.get('category') |
| |
| # |
| # Make a chunk of text for the end of a parameter if it is an array |
| def makeAsciiDocPreChunk(self, blockname, param, params): |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| # General pre-amble. Check optionality and add stuff. |
| asciidoc = self.makeAnchor(blockname, paramname.text, 'parameter') |
| |
| if self.paramIsStaticArray(param): |
| asciidoc += 'Any given element of ' |
| |
| elif self.paramIsArray(param): |
| lengths = param.attrib.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.attrib.get('optional') is not None: |
| 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.attrib.get('optional') is not None: |
| 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.attrib.get('optional') is not None: |
| asciidoc += 'and ' |
| |
| if param.attrib.get('optional') is not None: |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' is not `NULL`, ' |
| |
| elif param.attrib.get('optional') is not None: |
| # Don't generate this stub for bitflags |
| if self.getTypeCategory(paramtype.text) != 'bitmask': |
| if param.attrib.get('optional').split(',')[0] == 'true': |
| asciidoc += 'If ' |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' is not ' |
| if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype.text): |
| asciidoc += '`NULL`' |
| elif self.getTypeCategory(paramtype.text) == 'handle': |
| asciidoc += 'dlink:VK_NULL_HANDLE' |
| else: |
| asciidoc += '`0`' |
| |
| asciidoc += ', ' |
| |
| return asciidoc |
| |
| # |
| # Make the generic asciidoc line chunk portion used for all parameters. |
| # May return an empty string if nothing to validate. |
| def createValidationLineForParameterIntroChunk(self, blockname, param, params, typetext): |
| asciidoc = '' |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| asciidoc += self.makeAsciiDocPreChunk(blockname, param, params) |
| |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' must: be ' |
| |
| if self.paramIsArray(param): |
| # Arrays. These are hard to get right, apparently |
| |
| lengths = param.attrib.get('len').split(',') |
| |
| if (lengths[0]) == 'null-terminated': |
| asciidoc += 'a null-terminated ' |
| elif (lengths[0]) == '1': |
| asciidoc += 'a valid pointer to ' |
| else: |
| asciidoc += 'a valid pointer 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 pointers to ' |
| else: |
| asciidoc += 'valid pointers 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.text == '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.text == '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.text) |
| if (typecategory != 'struct' and typecategory != 'union' and typecategory != 'basetype' and typecategory is not None) or not self.isStructAlwaysValid(blockname, paramtype.text): |
| 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 = paramtype.tail.count('*') |
| |
| # Treat void* as an int |
| if paramtype.text == 'void': |
| pointercount -= 1 |
| |
| # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that. |
| asciidoc += 'a ' |
| for i in range(0, pointercount): |
| asciidoc += 'valid pointer to a ' |
| |
| # Handle void* and pointers to it |
| if paramtype.text == 'void': |
| # If there is only void*, it is just optional int - we don't need any language. |
| if pointercount == 0 and param.attrib.get('optional') is not None: |
| return '' # early return |
| else: |
| if param.attrib.get('optional').split(',')[pointercount] is not None: |
| # 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.text != '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 is not None and 'const' in param.text) |
| if self.getTypeCategory(paramtype.text) == 'bitmask' and not isOutputParam: |
| isMandatory = param.attrib.get('optional') is None #TODO does not really handle if someone tries something like optional="true,false" |
| if isMandatory: |
| asciidoc += self.makeAnchor(blockname, paramname.text, 'requiredbitmask') |
| if self.paramIsArray(param): |
| asciidoc += 'Each element of ' |
| asciidoc += 'pname:' |
| asciidoc += paramname.text |
| asciidoc += ' must: not be `0`' |
| asciidoc += '\n' |
| |
| return asciidoc |
| |
| def makeAsciiDocLineForParameter(self, blockname, param, params, typetext): |
| if param.attrib.get('noautovalidity') is not None: |
| return '' |
| asciidoc = self.createValidationLineForParameterIntroChunk(blockname, param, params, typetext) |
| |
| return asciidoc |
| |
| # Check if a structure is always considered valid (i.e. there are no rules to its acceptance) |
| def isStructAlwaysValid(self, blockname, structname): |
| |
| struct = self.registry.tree.find("types/type[@name='" + structname + "']") |
| |
| if struct.attrib.get('returnedonly') is not None: |
| return True |
| |
| params = struct.findall('member') |
| |
| for param in params: |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| typecategory = self.getTypeCategory(paramtype.text) |
| |
| if paramname.text == 'pNext': |
| return False |
| |
| if paramname.text == 'sType': |
| return False |
| |
| if param.attrib.get('noautovalidity') is not None: |
| return False |
| |
| if paramtype.text == 'void' or paramtype.text == 'char' or self.paramIsArray(param) or self.paramIsPointer(param): |
| if self.makeAsciiDocLineForParameter(blockname, param, params, '') != '': |
| return False |
| elif typecategory == 'handle' or typecategory == 'enum' or typecategory == 'bitmask': |
| return False |
| elif typecategory == 'struct' or typecategory == 'union': |
| if self.isStructAlwaysValid(blockname, paramtype.text) is False: |
| return False |
| |
| return True |
| |
| # |
| # Make an entire asciidoc line for a given parameter |
| def createValidationLineForParameter(self, blockname, param, params, typecategory): |
| asciidoc = '' |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| if paramtype.text == 'void' or paramtype.text == '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 = paramtype.text.replace('Flags', 'FlagBits') |
| if self.registry.tree.find("enums[@name='" + bitsname + "']") is None: |
| asciidoc += self.makeAnchor(blockname, paramname.text, 'zerobitmask') |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' must: be `0`' |
| asciidoc += '\n' |
| else: |
| if self.paramIsArray(param): |
| # |
| if param.text is not None and 'const' in param.text: |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combinations of ' + self.makeEnumerationName(bitsname) + ' value') |
| else: |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype.text) + ' value') |
| elif self.paramIsPointer(param): |
| if param.text is not None and 'const' in param.text: |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values') |
| else: |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype.text) + ' 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.text) + ' handle') |
| elif typecategory == 'enum': |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype.text) + ' value') |
| elif typecategory == 'struct': |
| if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(blockname, paramtype.text): |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype.text) + ' structure') |
| elif typecategory == 'union': |
| if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(blockname, paramtype.text): |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype.text) + ' union') |
| elif self.paramIsArray(param) or self.paramIsPointer(param): |
| asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeBaseTypeName(paramtype.text) + ' value') |
| |
| return asciidoc |
| |
| # |
| # Make an asciidoc validity entry for a handle's parent object |
| def makeAsciiDocHandleParent(self, blockname, param, params): |
| asciidoc = '' |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| # Deal with handle parents |
| handleparent = self.getHandleParent(paramtype.text) |
| if handleparent is not None: |
| parentreference = None |
| for otherparam in params: |
| if otherparam.find('type').text == handleparent: |
| parentreference = otherparam.find('name').text |
| if parentreference is not None: |
| asciidoc += self.makeAnchor(blockname, paramname.text, 'parent') |
| |
| if self.isHandleOptional(param, params): |
| if self.paramIsArray(param): |
| asciidoc += 'Each element of ' |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' that is a valid handle' |
| else: |
| asciidoc += 'If ' |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' is a valid handle, it' |
| else: |
| if self.paramIsArray(param): |
| asciidoc += 'Each element of ' |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' must: have been created, allocated, or retrieved from ' |
| asciidoc += self.makeParameterName(parentreference) |
| |
| asciidoc += '\n' |
| return asciidoc |
| |
| # |
| # Make an asciidoc validity entry for a common ancestors between handles |
| def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params): |
| asciidoc = '' |
| |
| if len(handles) > 1: |
| ancestormap = {} |
| anyoptional = False |
| |
| # Find all the ancestors |
| for param in handles: |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text): |
| ancestors = self.getHandleDispatchableAncestors(paramtype.text) |
| |
| ancestormap[param] = ancestors |
| |
| anyoptional |= self.isHandleOptional(param, params) |
| |
| # Remove redundant ancestor lists |
| for param in handles: |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| removals = [] |
| for ancestors in ancestormap.items(): |
| if paramtype.text 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(): |
| paramname = param.find('name') |
| parametertext = self.makeParameterName(paramname.text) |
| 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 |
| |
| # |
| # Generate an asciidoc validity line for the sType value of a struct |
| def makeStructureType(self, blockname, param): |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| asciidoc = self.makeAnchor(blockname, paramname.text, 'sType') |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' must: be ' |
| |
| values = param.attrib.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]+))', blockname): |
| 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 |
| |
| # |
| # Generate an asciidoc validity line for the pNext value of a struct |
| def makeStructureExtensionPointer(self, blockname, param): |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| if (param.attrib.get('validextensionstructs') is not None): |
| self.logMsg('warn', blockname, 'validextensionstructs is deprecated/removed', '\n') |
| |
| asciidoc = self.makeAnchor(blockname, paramname.text, 'pNext') |
| 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.required): |
| extensionstructs.append('slink:' + struct) |
| else: |
| self.logMsg('diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required') |
| |
| if len(extensionstructs) == 0: |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' must: be `NULL`' |
| elif len(extensionstructs) == 1: |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' must: be `NULL` or a pointer to a valid instance of ' |
| asciidoc += extensionstructs[0] |
| else: |
| asciidoc += 'Each ' |
| asciidoc += self.makeParameterName(paramname.text) |
| asciidoc += ' member of any structure (including this one) in the pname:pNext chain must: be either `NULL` or a pointer to a valid instance of ' |
| |
| if len(extensionstructs) == 2: |
| asciidoc += extensionstructs[0] + ' or ' + extensionstructs[1] |
| else: |
| asciidoc += (', ').join(extensionstructs[:-1]) + ', or ' + extensionstructs[-1] |
| asciidoc += '\n' |
| |
| asciidoc += self.makeAnchor(blockname, 'sType', 'unique') |
| asciidoc += 'Each pname:sType member in the pname:pNext chain must: be unique' |
| |
| asciidoc += '\n' |
| |
| return asciidoc |
| |
| # |
| # Generate all the valid usage information for a given struct that's only ever filled out by the implementation other than sType and pNext |
| def makeValidUsageStatementsReturnedOnly(self, cmd, blockname, params): |
| # Start the asciidoc block for this |
| asciidoc = '' |
| |
| for param in params: |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| # 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.text) |
| |
| if param.attrib.get('noautovalidity') is None: |
| # Generate language to independently validate a parameter |
| if paramtype.text == 'VkStructureType' and paramname.text == 'sType': |
| asciidoc += self.makeStructureType(blockname, param) |
| elif paramname.text == 'pNext' and paramtype.text == 'void' and cmd.attrib.get('structextends') is None: |
| asciidoc += self.makeStructureExtensionPointer(blockname, param) |
| |
| # In case there's nothing to report, return None |
| if asciidoc == '': |
| return None |
| |
| return asciidoc |
| |
| # |
| # Generate all the valid usage information for a given struct or command |
| def makeValidUsageStatements(self, cmd, blockname, params): |
| # Start the asciidoc block for this |
| asciidoc = '' |
| |
| handles = [] |
| anyparentedhandlesoptional = False |
| parentdictionary = {} |
| arraylengths = set() |
| for param in params: |
| paramname = param.find('name') |
| paramtype = param.find('type') |
| |
| # 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.text) |
| |
| if param.attrib.get('noautovalidity') is None: |
| # Generate language to independently validate a parameter |
| if paramtype.text == 'VkStructureType' and paramname.text == 'sType': |
| asciidoc += self.makeStructureType(blockname, param) |
| elif paramtype.text == 'void' and paramname.text == 'pNext': |
| if cmd.attrib.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.attrib.get('len') |
| if arraylength is not None: |
| for onelength in arraylength.split(','): |
| arraylengths.add(onelength) |
| |
| # For any vkQueue* functions, there might be queue type data |
| if 'vkQueue' in blockname: |
| # The queue type must be valid |
| queuetypes = cmd.attrib.get('queues') |
| if queuetypes is not None: |
| 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.attrib.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.attrib.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.attrib.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: |
| paramname = param.find('name') |
| |
| for arraylength in arraylengths: |
| if paramname.text == arraylength and param.attrib.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 `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 = param.find('type') |
| # 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.text) |
| |
| 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.attrib.get('externsync') |
| paramname = param.find('name') |
| for externsyncattrib in externsyncattribs.split(','): |
| paramdecl += '* ' |
| paramdecl += 'Host access to ' |
| if externsyncattrib == 'true': |
| if self.paramIsArray(param): |
| paramdecl += 'each member of ' + self.makeParameterName(paramname.text) |
| elif self.paramIsPointer(param): |
| paramdecl += 'the object referenced by ' + self.makeParameterName(paramname.text) |
| else: |
| paramdecl += self.makeParameterName(paramname.text) |
| else: |
| paramdecl += 'pname:' |
| paramdecl += externsyncattrib |
| paramdecl += ' must: be externally synchronized\n' |
| |
| # 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 (paramdecl == ''): |
| return None |
| else: |
| return paramdecl |
| |
| def makeCommandPropertiesTableEntry(self, cmd, name): |
| |
| if 'vkCmd' in name: |
| # Must be called inside/outside a renderpass appropriately |
| cmdbufferlevel = cmd.attrib.get('cmdbufferlevel') |
| cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(',')) |
| |
| renderpass = cmd.attrib.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.attrib.get('queues') |
| queues = (' + \n').join(queues.title().split(',')) |
| |
| pipeline = cmd.attrib.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.attrib.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 == 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 makeSuccessCodes(self, cmd, name): |
| successcodes = cmd.attrib.get('successcodes') |
| if successcodes is not None: |
| successcodes = self.findRequiredEnums(successcodes.split(',')) |
| if len(successcodes) > 0: |
| return '* ename:' + '\n* ename:'.join(successcodes) |
| return None |
| |
| def makeErrorCodes(self, cmd, name): |
| errorcodes = cmd.attrib.get('errorcodes') |
| if errorcodes is not None: |
| errorcodes = self.findRequiredEnums(errorcodes.split(',')) |
| if len(errorcodes) > 0: |
| return '* ename:' + '\n* ename:'.join(errorcodes) |
| return None |
| |
| # |
| # Command generation |
| def genCmd(self, cmdinfo, name, alias): |
| 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') |
| 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) |
| |
| # |
| # Struct Generation |
| def genStruct(self, typeinfo, typename, alias): |
| 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.attrib.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 sType and pNext validation |
| params = typeinfo.elem.findall('member') |
| |
| validity = self.makeValidUsageStatementsReturnedOnly(typeinfo.elem, typename, params) |
| |
| self.writeInclude('structs', typename, validity, None, None, None, None) |
| |
| # |
| # Group (e.g. C "enum" type) generation. |
| # For the validity generator, this just tags individual enumerants |
| # as required or not. |
| def genGroup(self, groupinfo, groupName, alias): |
| 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) |
| # |
| # Type Generation |
| def genType(self, typeinfo, typename, alias): |
| OutputGenerator.genType(self, typeinfo, typename, alias) |
| |
| # @@@ (Jon) something needs to be done here to handle aliases, probably |
| |
| category = typeinfo.elem.get('category') |
| if (category == 'struct' or category == 'union'): |
| self.genStruct(typeinfo, typename, alias) |