aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorMark Lobodzinski <mark@lunarg.com>2016-11-18 14:58:57 -0700
committerMark Lobodzinski <mark@lunarg.com>2016-11-21 12:06:12 -0700
commit0fcd1a7ef1f62557daa83f390bbaf4ab40465ddd (patch)
treefbc04ab17ad75f249de7fcb8dccf0f588f674e85 /scripts
parent775df73e1d1a4f216b531e9f11c109fe32266801 (diff)
downloadusermoji-0fcd1a7ef1f62557daa83f390bbaf4ab40465ddd.tar.xz
build: Move XML codegen scripts into scripts dir
- Update android-generate.bat and android-generate.sh - Updated layer CMakeLists.txt files for new path Change-Id: I1b1c9dbc9e944c90e95542fcfdda1d2e804517f3
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/generator.py498
-rw-r--r--scripts/lvl_genvk.py275
-rw-r--r--scripts/parameter_validation_generator.py987
-rwxr-xr-xscripts/reg.py813
-rw-r--r--scripts/threading_generator.py467
-rw-r--r--scripts/unique_objects_generator.py760
6 files changed, 3800 insertions, 0 deletions
diff --git a/scripts/generator.py b/scripts/generator.py
new file mode 100755
index 00000000..043121cd
--- /dev/null
+++ b/scripts/generator.py
@@ -0,0 +1,498 @@
+#!/usr/bin/python3 -i
+#
+# Copyright (c) 2013-2016 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
+
+def write( *args, **kwargs ):
+ file = kwargs.pop('file',sys.stdout)
+ end = kwargs.pop( 'end','\n')
+ file.write( ' '.join([str(arg) for arg in args]) )
+ file.write( end )
+
+# noneStr - returns string argument, or "" if argument is None.
+# Used in converting etree Elements into text.
+# str - string to convert
+def noneStr(str):
+ if (str):
+ return str
+ else:
+ return ""
+
+# enquote - returns string argument with surrounding quotes,
+# for serialization into Python code.
+def enquote(str):
+ if (str):
+ return "'" + str + "'"
+ else:
+ return None
+
+# apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a
+# function pointer type), False otherwise.
+def apiName(str):
+ return str[0:2].lower() == 'vk' or str[0:3] == 'PFN'
+
+# Primary sort key for regSortFeatures.
+# Sorts by category of the feature name string:
+# Core API features (those defined with a <feature> tag)
+# ARB/KHR/OES (Khronos extensions)
+# other (EXT/vendor extensions)
+# This will need changing for Vulkan!
+def regSortCategoryKey(feature):
+ if (feature.elem.tag == 'feature'):
+ return 0
+ elif (feature.category == 'ARB' or
+ feature.category == 'KHR' or
+ feature.category == 'OES'):
+ return 1
+ else:
+ return 2
+
+# Secondary sort key for regSortFeatures.
+# Sorts by extension name.
+def regSortNameKey(feature):
+ return feature.name
+
+# Second sort key for regSortFeatures.
+# Sorts by feature version. <extension> elements all have version number "0"
+def regSortFeatureVersionKey(feature):
+ return float(feature.version)
+
+# Tertiary sort key for regSortFeatures.
+# Sorts by extension number. <feature> elements all have extension number 0.
+def regSortExtensionNumberKey(feature):
+ return int(feature.number)
+
+# regSortFeatures - default sort procedure for features.
+# Sorts by primary key of feature category ('feature' or 'extension')
+# then by version number (for features)
+# then by extension number (for extensions)
+def regSortFeatures(featureList):
+ featureList.sort(key = regSortExtensionNumberKey)
+ featureList.sort(key = regSortFeatureVersionKey)
+ featureList.sort(key = regSortCategoryKey)
+
+# GeneratorOptions - base class for options used during header production
+# These options are target language independent, and used by
+# Registry.apiGen() and by base OutputGenerator objects.
+#
+# Members
+# filename - basename of file to generate, or None to write to stdout.
+# directory - directory in which to generate filename
+# apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
+# profile - string specifying API profile , e.g. 'core', or None.
+# versions - regex matching API versions to process interfaces for.
+# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
+# emitversions - regex matching API versions to actually emit
+# interfaces for (though all requested versions are considered
+# when deciding which interfaces to generate). For GL 4.3 glext.h,
+# this might be '1\.[2-5]|[2-4]\.[0-9]'.
+# defaultExtensions - If not None, a string which must in its
+# entirety match the pattern in the "supported" attribute of
+# the <extension>. Defaults to None. Usually the same as apiname.
+# addExtensions - regex matching names of additional extensions
+# to include. Defaults to None.
+# removeExtensions - regex matching names of extensions to
+# remove (after defaultExtensions and addExtensions). Defaults
+# to None.
+# sortProcedure - takes a list of FeatureInfo objects and sorts
+# them in place to a preferred order in the generated output.
+# Default is core API versions, ARB/KHR/OES extensions, all
+# other extensions, alphabetically within each group.
+# The regex patterns can be None or empty, in which case they match
+# nothing.
+class GeneratorOptions:
+ """Represents options during header production from an API registry"""
+ def __init__(self,
+ filename = None,
+ directory = '.',
+ apiname = None,
+ profile = None,
+ versions = '.*',
+ emitversions = '.*',
+ defaultExtensions = None,
+ addExtensions = None,
+ removeExtensions = None,
+ sortProcedure = regSortFeatures):
+ self.filename = filename
+ self.directory = directory
+ self.apiname = apiname
+ self.profile = profile
+ self.versions = self.emptyRegex(versions)
+ self.emitversions = self.emptyRegex(emitversions)
+ self.defaultExtensions = defaultExtensions
+ self.addExtensions = self.emptyRegex(addExtensions)
+ self.removeExtensions = self.emptyRegex(removeExtensions)
+ self.sortProcedure = sortProcedure
+ #
+ # Substitute a regular expression which matches no version
+ # or extension names for None or the empty string.
+ def emptyRegex(self,pat):
+ if (pat == None or pat == ''):
+ return '_nomatch_^'
+ else:
+ return pat
+
+# OutputGenerator - base class for generating API interfaces.
+# Manages basic logic, logging, and output file control
+# Derived classes actually generate formatted output.
+#
+# ---- methods ----
+# OutputGenerator(errFile, warnFile, diagFile)
+# errFile, warnFile, diagFile - file handles to write errors,
+# warnings, diagnostics to. May be None to not write.
+# logMsg(level, *args) - log messages of different categories
+# level - 'error', 'warn', or 'diag'. 'error' will also
+# raise a UserWarning exception
+# *args - print()-style arguments
+# setExtMap(map) - specify a dictionary map from extension names to
+# numbers, used in creating values for extension enumerants.
+# makeDir(directory) - create a directory, if not already done.
+# Generally called from derived generators creating hierarchies.
+# beginFile(genOpts) - start a new interface file
+# genOpts - GeneratorOptions controlling what's generated and how
+# endFile() - finish an interface file, closing it when done
+# beginFeature(interface, emit) - write interface for a feature
+# and tag generated features as having been done.
+# interface - element for the <version> / <extension> to generate
+# emit - actually write to the header only when True
+# endFeature() - finish an interface.
+# genType(typeinfo,name) - generate interface for a type
+# typeinfo - TypeInfo for a type
+# genStruct(typeinfo,name) - generate interface for a C "struct" type.
+# typeinfo - TypeInfo for a type interpreted as a struct
+# genGroup(groupinfo,name) - generate interface for a group of enums (C "enum")
+# groupinfo - GroupInfo for a group
+# genEnum(enuminfo, name) - generate interface for an enum (constant)
+# enuminfo - EnumInfo for an enum
+# name - enum name
+# genCmd(cmdinfo) - generate interface for a command
+# cmdinfo - CmdInfo for a command
+# isEnumRequired(enumElem) - return True if this <enum> element is required
+# elem - <enum> element to test
+# makeCDecls(cmd) - return C prototype and function pointer typedef for a
+# <command> Element, as a list of two strings
+# cmd - Element for the <command>
+# newline() - print a newline to the output file (utility function)
+#
+class OutputGenerator:
+ """Generate specified API interfaces in a specific style, such as a C header"""
+ #
+ # categoryToPath - map XML 'category' to include file directory name
+ categoryToPath = {
+ 'bitmask' : 'flags',
+ 'enum' : 'enums',
+ 'funcpointer' : 'funcpointers',
+ 'handle' : 'handles',
+ 'define' : 'defines',
+ 'basetype' : 'basetypes',
+ }
+ #
+ # Constructor
+ def __init__(self,
+ errFile = sys.stderr,
+ warnFile = sys.stderr,
+ diagFile = sys.stdout):
+ self.outFile = None
+ self.errFile = errFile
+ self.warnFile = warnFile
+ self.diagFile = diagFile
+ # Internal state
+ self.featureName = None
+ self.genOpts = None
+ self.registry = None
+ # Used for extension enum value generation
+ self.extBase = 1000000000
+ self.extBlockSize = 1000
+ self.madeDirs = {}
+ #
+ # logMsg - write a message of different categories to different
+ # destinations.
+ # level -
+ # 'diag' (diagnostic, voluminous)
+ # 'warn' (warning)
+ # 'error' (fatal error - raises exception after logging)
+ # *args - print()-style arguments to direct to corresponding log
+ def logMsg(self, level, *args):
+ """Log a message at the given level. Can be ignored or log to a file"""
+ if (level == 'error'):
+ strfile = io.StringIO()
+ write('ERROR:', *args, file=strfile)
+ if (self.errFile != None):
+ write(strfile.getvalue(), file=self.errFile)
+ raise UserWarning(strfile.getvalue())
+ elif (level == 'warn'):
+ if (self.warnFile != None):
+ write('WARNING:', *args, file=self.warnFile)
+ elif (level == 'diag'):
+ if (self.diagFile != None):
+ write('DIAG:', *args, file=self.diagFile)
+ else:
+ raise UserWarning(
+ '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
+ #
+ # enumToValue - parses and converts an <enum> tag into a value.
+ # Returns a list
+ # first element - integer representation of the value, or None
+ # if needsNum is False. The value must be a legal number
+ # if needsNum is True.
+ # second element - string representation of the value
+ # There are several possible representations of values.
+ # A 'value' attribute simply contains the value.
+ # A 'bitpos' attribute defines a value by specifying the bit
+ # position which is set in that value.
+ # A 'offset','extbase','extends' triplet specifies a value
+ # as an offset to a base value defined by the specified
+ # 'extbase' extension name, which is then cast to the
+ # typename specified by 'extends'. This requires probing
+ # the registry database, and imbeds knowledge of the
+ # Vulkan extension enum scheme in this function.
+ def enumToValue(self, elem, needsNum):
+ name = elem.get('name')
+ numVal = None
+ if ('value' in elem.keys()):
+ value = elem.get('value')
+ # print('About to translate value =', value, 'type =', type(value))
+ if (needsNum):
+ numVal = int(value, 0)
+ # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or
+ # 'ull'), append it to the string value.
+ # t = enuminfo.elem.get('type')
+ # if (t != None and t != '' and t != 'i' and t != 's'):
+ # value += enuminfo.type
+ self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
+ return [numVal, value]
+ if ('bitpos' in elem.keys()):
+ value = elem.get('bitpos')
+ numVal = int(value, 0)
+ numVal = 1 << numVal
+ value = '0x%08x' % numVal
+ self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
+ return [numVal, value]
+ if ('offset' in elem.keys()):
+ # Obtain values in the mapping from the attributes
+ enumNegative = False
+ offset = int(elem.get('offset'),0)
+ extnumber = int(elem.get('extnumber'),0)
+ extends = elem.get('extends')
+ if ('dir' in elem.keys()):
+ enumNegative = True
+ self.logMsg('diag', 'Enum', name, 'offset =', offset,
+ 'extnumber =', extnumber, 'extends =', extends,
+ 'enumNegative =', enumNegative)
+ # Now determine the actual enumerant value, as defined
+ # in the "Layers and Extensions" appendix of the spec.
+ numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
+ if (enumNegative):
+ numVal = -numVal
+ value = '%d' % numVal
+ # More logic needed!
+ self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
+ return [numVal, value]
+ return [None, None]
+ #
+ def makeDir(self, path):
+ self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
+ if not (path in self.madeDirs.keys()):
+ # This can get race conditions with multiple writers, see
+ # https://stackoverflow.com/questions/273192/
+ if not os.path.exists(path):
+ os.makedirs(path)
+ self.madeDirs[path] = None
+ #
+ def beginFile(self, genOpts):
+ self.genOpts = genOpts
+ #
+ # Open specified output file. Not done in constructor since a
+ # Generator can be used without writing to a file.
+ if (self.genOpts.filename != None):
+ self.outFile = open(self.genOpts.directory + '/' + self.genOpts.filename, 'w')
+ else:
+ self.outFile = sys.stdout
+ def endFile(self):
+ self.errFile and self.errFile.flush()
+ self.warnFile and self.warnFile.flush()
+ self.diagFile and self.diagFile.flush()
+ self.outFile.flush()
+ if (self.outFile != sys.stdout and self.outFile != sys.stderr):
+ self.outFile.close()
+ self.genOpts = None
+ #
+ def beginFeature(self, interface, emit):
+ self.emit = emit
+ self.featureName = interface.get('name')
+ # If there's an additional 'protect' attribute in the feature, save it
+ self.featureExtraProtect = interface.get('protect')
+ def endFeature(self):
+ # Derived classes responsible for emitting feature
+ self.featureName = None
+ self.featureExtraProtect = None
+ # Utility method to validate we're generating something only inside a
+ # <feature> tag
+ def validateFeature(self, featureType, featureName):
+ if (self.featureName == None):
+ raise UserWarning('Attempt to generate', featureType, name,
+ 'when not in feature')
+ #
+ # Type generation
+ def genType(self, typeinfo, name):
+ self.validateFeature('type', name)
+ #
+ # Struct (e.g. C "struct" type) generation
+ def genStruct(self, typeinfo, name):
+ self.validateFeature('struct', name)
+ #
+ # Group (e.g. C "enum" type) generation
+ def genGroup(self, groupinfo, name):
+ self.validateFeature('group', name)
+ #
+ # Enumerant (really, constant) generation
+ def genEnum(self, enuminfo, name):
+ self.validateFeature('enum', name)
+ #
+ # Command generation
+ def genCmd(self, cmd, name):
+ self.validateFeature('command', name)
+ #
+ # Utility functions - turn a <proto> <name> into C-language prototype
+ # and typedef declarations for that name.
+ # name - contents of <name> tag
+ # tail - whatever text follows that tag in the Element
+ def makeProtoName(self, name, tail):
+ return self.genOpts.apientry + name + tail
+ def makeTypedefName(self, name, tail):
+ return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
+ #
+ # makeCParamDecl - return a string which is an indented, formatted
+ # declaration for a <param> or <member> block (e.g. function parameter
+ # or structure/union member).
+ # param - Element (<param> or <member>) to format
+ # aligncol - if non-zero, attempt to align the nested <name> element
+ # at this column
+ def makeCParamDecl(self, param, aligncol):
+ paramdecl = ' ' + noneStr(param.text)
+ for elem in param:
+ text = noneStr(elem.text)
+ tail = noneStr(elem.tail)
+ if (elem.tag == 'name' and aligncol > 0):
+ self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
+ # Align at specified column, if possible
+ paramdecl = paramdecl.rstrip()
+ oldLen = len(paramdecl)
+ # This works around a problem where very long type names -
+ # longer than the alignment column - would run into the tail
+ # text.
+ paramdecl = paramdecl.ljust(aligncol-1) + ' '
+ newLen = len(paramdecl)
+ self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
+ paramdecl += text + tail
+ return paramdecl
+ #
+ # getCParamTypeLength - return the length of the type field is an indented, formatted
+ # declaration for a <param> or <member> block (e.g. function parameter
+ # or structure/union member).
+ # param - Element (<param> or <member>) to identify
+ def getCParamTypeLength(self, param):
+ paramdecl = ' ' + noneStr(param.text)
+ for elem in param:
+ text = noneStr(elem.text)
+ tail = noneStr(elem.tail)
+ if (elem.tag == 'name'):
+ # Align at specified column, if possible
+ newLen = len(paramdecl.rstrip())
+ self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
+ paramdecl += text + tail
+ return newLen
+ #
+ # isEnumRequired(elem) - return True if this <enum> element is
+ # required, False otherwise
+ # elem - <enum> element to test
+ def isEnumRequired(self, elem):
+ return (elem.get('extname') is None or
+ re.match(self.genOpts.addExtensions, elem.get('extname')) is not None or
+ self.genOpts.defaultExtensions == elem.get('supported'))
+ #
+ # makeCDecls - return C prototype and function pointer typedef for a
+ # command, as a two-element list of strings.
+ # cmd - Element containing a <command> tag
+ def makeCDecls(self, cmd):
+ """Generate C function pointer typedef for <command> Element"""
+ proto = cmd.find('proto')
+ params = cmd.findall('param')
+ # Begin accumulating prototype and typedef strings
+ pdecl = self.genOpts.apicall
+ tdecl = 'typedef '
+ #
+ # Insert the function return type/name.
+ # For prototypes, add APIENTRY macro before the name
+ # For typedefs, add (APIENTRY *<name>) around the name and
+ # use the PFN_cmdnameproc naming convention.
+ # Done by walking the tree for <proto> element by element.
+ # etree has elem.text followed by (elem[i], elem[i].tail)
+ # for each child element and any following text
+ # Leading text
+ pdecl += noneStr(proto.text)
+ tdecl += noneStr(proto.text)
+ # For each child element, if it's a <name> wrap in appropriate
+ # declaration. Otherwise append its contents and tail contents.
+ for elem in proto:
+ text = noneStr(elem.text)
+ tail = noneStr(elem.tail)
+ if (elem.tag == 'name'):
+ pdecl += self.makeProtoName(text, tail)
+ tdecl += self.makeTypedefName(text, tail)
+ else:
+ pdecl += text + tail
+ tdecl += text + tail
+ # Now add the parameter declaration list, which is identical
+ # for prototypes and typedefs. Concatenate all the text from
+ # a <param> node without the tags. No tree walking required
+ # since all tags are ignored.
+ # Uses: self.indentFuncProto
+ # self.indentFuncPointer
+ # self.alignFuncParam
+ # Might be able to doubly-nest the joins, e.g.
+ # ','.join(('_'.join([l[i] for i in range(0,len(l))])
+ n = len(params)
+ # Indented parameters
+ if n > 0:
+ indentdecl = '(\n'
+ for i in range(0,n):
+ paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam)
+ if (i < n - 1):
+ paramdecl += ',\n'
+ else:
+ paramdecl += ');'
+ indentdecl += paramdecl
+ else:
+ indentdecl = '(void);'
+ # Non-indented parameters
+ paramdecl = '('
+ if n > 0:
+ for i in range(0,n):
+ paramdecl += ''.join([t for t in params[i].itertext()])
+ if (i < n - 1):
+ paramdecl += ', '
+ else:
+ paramdecl += 'void'
+ paramdecl += ");";
+ return [ pdecl + indentdecl, tdecl + paramdecl ]
+ #
+ def newline(self):
+ write('', file=self.outFile)
+
+ def setRegistry(self, registry):
+ self.registry = registry
+ #
diff --git a/scripts/lvl_genvk.py b/scripts/lvl_genvk.py
new file mode 100644
index 00000000..8fa7c1bf
--- /dev/null
+++ b/scripts/lvl_genvk.py
@@ -0,0 +1,275 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013-2016 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 argparse, cProfile, pdb, string, sys, time
+from reg import *
+from generator import write
+
+#
+# LoaderAndValidationLayer Generator Additions
+from threading_generator import ThreadGeneratorOptions, ThreadOutputGenerator
+from parameter_validation_generator import ParamCheckerGeneratorOptions, ParamCheckerOutputGenerator
+from unique_objects_generator import UniqueObjectsGeneratorOptions, UniqueObjectsOutputGenerator
+
+# Simple timer functions
+startTime = None
+
+def startTimer(timeit):
+ global startTime
+ startTime = time.clock()
+
+def endTimer(timeit, msg):
+ global startTime
+ endTime = time.clock()
+ if (timeit):
+ write(msg, endTime - startTime, file=sys.stderr)
+ startTime = None
+
+# Turn a list of strings into a regexp string matching exactly those strings
+def makeREstring(list):
+ return '^(' + '|'.join(list) + ')$'
+
+# Returns a directory of [ generator function, generator options ] indexed
+# by specified short names. The generator options incorporate the following
+# parameters:
+#
+# extensions - list of extension names to include.
+# protect - True if re-inclusion protection should be added to headers
+# directory - path to directory in which to generate the target(s)
+def makeGenOpts(extensions = [], protect = True, directory = '.'):
+ global genOpts
+ genOpts = {}
+
+ # Descriptive names for various regexp patterns used to select
+ # versions and extensions
+ allVersions = allExtensions = '.*'
+ noVersions = noExtensions = None
+
+ addExtensions = makeREstring(extensions)
+ removeExtensions = makeREstring([])
+
+ # Copyright text prefixing all headers (list of strings).
+ prefixStrings = [
+ '/*',
+ '** Copyright (c) 2015-2016 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.',
+ '*/',
+ ''
+ ]
+
+ # Text specific to Vulkan headers
+ vkPrefixStrings = [
+ '/*',
+ '** This header is generated from the Khronos Vulkan XML API Registry.',
+ '**',
+ '*/',
+ ''
+ ]
+
+ # Defaults for generating re-inclusion protection wrappers (or not)
+ protectFile = protect
+ protectFeature = protect
+ protectProto = protect
+
+ #
+ # LoaderAndValidationLayer Generators
+ # Options for threading layer
+ genOpts['thread_check.h'] = [
+ ThreadOutputGenerator,
+ ThreadGeneratorOptions(
+ filename = 'thread_check.h',
+ directory = directory,
+ apiname = 'vulkan',
+ profile = None,
+ versions = allVersions,
+ emitversions = allVersions,
+ defaultExtensions = 'vulkan',
+ addExtensions = addExtensions,
+ removeExtensions = removeExtensions,
+ prefixText = prefixStrings + vkPrefixStrings,
+ protectFeature = False,
+ apicall = 'VKAPI_ATTR ',
+ apientry = 'VKAPI_CALL ',
+ apientryp = 'VKAPI_PTR *',
+ alignFuncParam = 48)
+ ]
+
+ # Options for parameter validation layer
+ genOpts['parameter_validation.h'] = [
+ ParamCheckerOutputGenerator,
+ ParamCheckerGeneratorOptions(
+ filename = 'parameter_validation.h',
+ directory = directory,
+ apiname = 'vulkan',
+ profile = None,
+ versions = allVersions,
+ emitversions = allVersions,
+ defaultExtensions = 'vulkan',
+ addExtensions = addExtensions,
+ removeExtensions = removeExtensions,
+ prefixText = prefixStrings + vkPrefixStrings,
+ protectFeature = False,
+ apicall = 'VKAPI_ATTR ',
+ apientry = 'VKAPI_CALL ',
+ apientryp = 'VKAPI_PTR *',
+ alignFuncParam = 48)
+ ]
+
+ # Options for unique objects layer
+ genOpts['unique_objects_wrappers.h'] = [
+ UniqueObjectsOutputGenerator,
+ UniqueObjectsGeneratorOptions(
+ filename = 'unique_objects_wrappers.h',
+ directory = directory,
+ apiname = 'vulkan',
+ profile = None,
+ versions = allVersions,
+ emitversions = allVersions,
+ defaultExtensions = 'vulkan',
+ addExtensions = addExtensions,
+ removeExtensions = removeExtensions,
+ prefixText = prefixStrings + vkPrefixStrings,
+ protectFeature = False,
+ apicall = 'VKAPI_ATTR ',
+ apientry = 'VKAPI_CALL ',
+ apientryp = 'VKAPI_PTR *',
+ alignFuncParam = 48)
+ ]
+
+# Generate a target based on the options in the matching genOpts{} object.
+# This is encapsulated in a function so it can be profiled and/or timed.
+# The args parameter is an parsed argument object containing the following
+# fields that are used:
+# target - target to generate
+# directory - directory to generate it in
+# protect - True if re-inclusion wrappers should be created
+# extensions - list of additional extensions to include in generated
+# interfaces
+def genTarget(args):
+ global genOpts
+
+ # Create generator options with specified parameters
+ makeGenOpts(extensions = args.extension,
+ protect = args.protect,
+ directory = args.directory)
+
+ if (args.target in genOpts.keys()):
+ createGenerator = genOpts[args.target][0]
+ options = genOpts[args.target][1]
+
+ write('* Building', options.filename, file=sys.stderr)
+
+ startTimer(args.time)
+ gen = createGenerator(errFile=errWarn,
+ warnFile=errWarn,
+ diagFile=diag)
+ reg.setGenerator(gen)
+ reg.apiGen(options)
+ write('* Generated', options.filename, file=sys.stderr)
+ endTimer(args.time, '* Time to generate ' + options.filename + ' =')
+ else:
+ write('No generator options for unknown target:',
+ args.target, file=sys.stderr)
+
+# -extension name - may be a single extension name, a a space-separated list
+# of names, or a regular expression.
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('-extension', action='append',
+ default=[],
+ help='Specify an extension or extensions to add to targets')
+ parser.add_argument('-debug', action='store_true',
+ help='Enable debugging')
+ parser.add_argument('-dump', action='store_true',
+ help='Enable dump to stderr')
+ parser.add_argument('-diagfile', action='store',
+ default=None,
+ help='Write diagnostics to specified file')
+ parser.add_argument('-errfile', action='store',
+ default=None,
+ help='Write errors and warnings to specified file instead of stderr')
+ parser.add_argument('-noprotect', dest='protect', action='store_false',
+ help='Disable inclusion protection in output headers')
+ parser.add_argument('-profile', action='store_true',
+ help='Enable profiling')
+ parser.add_argument('-registry', action='store',
+ default='vk.xml',
+ help='Use specified registry file instead of vk.xml')
+ parser.add_argument('-time', action='store_true',
+ help='Enable timing')
+ parser.add_argument('-validate', action='store_true',
+ help='Enable group validation')
+ parser.add_argument('-o', action='store', dest='directory',
+ default='.',
+ help='Create target and related files in specified directory')
+ parser.add_argument('target', metavar='target', nargs='?',
+ help='Specify target')
+
+ args = parser.parse_args()
+
+ # This splits arguments which are space-separated lists
+ args.extension = [name for arg in args.extension for name in arg.split()]
+
+ # Load & parse registry
+ reg = Registry()
+
+ startTimer(args.time)
+ tree = etree.parse(args.registry)
+ endTimer(args.time, '* Time to make ElementTree =')
+
+ startTimer(args.time)
+ reg.loadElementTree(tree)
+ endTimer(args.time, '* Time to parse ElementTree =')
+
+ if (args.validate):
+ reg.validateGroups()
+
+ if (args.dump):
+ write('* Dumping registry to regdump.txt', file=sys.stderr)
+ reg.dumpReg(filehandle = open('regdump.txt','w'))
+
+ # create error/warning & diagnostic files
+ if (args.errfile):
+ errWarn = open(args.errfile, 'w')
+ else:
+ errWarn = sys.stderr
+
+ if (args.diagfile):
+ diag = open(args.diagfile, 'w')
+ else:
+ diag = None
+
+ if (args.debug):
+ pdb.run('genTarget(args)')
+ elif (args.profile):
+ import cProfile, pstats
+ cProfile.run('genTarget(args)', 'profile.txt')
+ p = pstats.Stats('profile.txt')
+ p.strip_dirs().sort_stats('time').print_stats(50)
+ else:
+ genTarget(args)
diff --git a/scripts/parameter_validation_generator.py b/scripts/parameter_validation_generator.py
new file mode 100644
index 00000000..e0d215da
--- /dev/null
+++ b/scripts/parameter_validation_generator.py
@@ -0,0 +1,987 @@
+#!/usr/bin/python3 -i
+#
+# Copyright (c) 2015-2016 The Khronos Group Inc.
+# Copyright (c) 2015-2016 Valve Corporation
+# Copyright (c) 2015-2016 LunarG, Inc.
+# Copyright (c) 2015-2016 Google 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.
+#
+# Author: Dustin Graves <dustin@lunarg.com>
+
+import os,re,sys
+import xml.etree.ElementTree as etree
+from generator import *
+from collections import namedtuple
+
+
+# ParamCheckerGeneratorOptions - subclass of GeneratorOptions.
+#
+# Adds options used by ParamCheckerOutputGenerator object during Parameter
+# validation layer generation.
+#
+# Additional members
+# prefixText - list of strings to prefix generated header with
+# (usually a copyright statement + calling convention macros).
+# protectFile - True if multiple inclusion protection should be
+# generated (based on the filename) around the entire header.
+# protectFeature - True if #ifndef..#endif protection should be
+# generated around a feature interface in the header file.
+# genFuncPointers - True if function pointer typedefs should be
+# generated
+# protectProto - If conditional protection should be generated
+# around prototype declarations, set to either '#ifdef'
+# to require opt-in (#ifdef protectProtoStr) or '#ifndef'
+# to require opt-out (#ifndef protectProtoStr). Otherwise
+# set to None.
+# protectProtoStr - #ifdef/#ifndef symbol to use around prototype
+# declarations, if protectProto is set
+# apicall - string to use for the function declaration prefix,
+# such as APICALL on Windows.
+# apientry - string to use for the calling convention macro,
+# in typedefs, such as APIENTRY.
+# apientryp - string to use for the calling convention macro
+# in function pointer typedefs, such as APIENTRYP.
+# indentFuncProto - True if prototype declarations should put each
+# parameter on a separate line
+# indentFuncPointer - True if typedefed function pointers should put each
+# parameter on a separate line
+# alignFuncParam - if nonzero and parameters are being put on a
+# separate line, align parameter names at the specified column
+class ParamCheckerGeneratorOptions(GeneratorOptions):
+ def __init__(self,
+ filename = None,
+ directory = '.',
+ apiname = None,
+ profile = None,
+ versions = '.*',
+ emitversions = '.*',
+ defaultExtensions = None,
+ addExtensions = None,
+ removeExtensions = None,
+ sortProcedure = regSortFeatures,
+ prefixText = "",
+ genFuncPointers = True,
+ protectFile = True,
+ protectFeature = True,
+ protectProto = None,
+ protectProtoStr = None,
+ apicall = '',
+ apientry = '',
+ apientryp = '',
+ indentFuncProto = True,
+ indentFuncPointer = False,
+ alignFuncParam = 0):
+ GeneratorOptions.__init__(self, filename, directory, apiname, profile,
+ versions, emitversions, defaultExtensions,
+ addExtensions, removeExtensions, sortProcedure)
+ self.prefixText = prefixText
+ self.genFuncPointers = genFuncPointers
+ self.protectFile = protectFile
+ self.protectFeature = protectFeature
+ self.protectProto = protectProto
+ self.protectProtoStr = protectProtoStr
+ self.apicall = apicall
+ self.apientry = apientry
+ self.apientryp = apientryp
+ self.indentFuncProto = indentFuncProto
+ self.indentFuncPointer = indentFuncPointer
+ self.alignFuncParam = alignFuncParam
+
+# ParamCheckerOutputGenerator - subclass of OutputGenerator.
+# Generates param checker layer code.
+#
+# ---- methods ----
+# ParamCheckerOutputGenerator(errFile, warnFile, diagFile) - args as for
+# OutputGenerator. Defines additional internal state.
+# ---- methods overriding base class ----
+# beginFile(genOpts)
+# endFile()
+# beginFeature(interface, emit)
+# endFeature()
+# genType(typeinfo,name)
+# genStruct(typeinfo,name)
+# genGroup(groupinfo,name)
+# genEnum(enuminfo, name)
+# genCmd(cmdinfo)
+class ParamCheckerOutputGenerator(OutputGenerator):
+ """Generate ParamChecker code based on XML element attributes"""
+ # This is an ordered list of sections in the header file.
+ ALL_SECTIONS = ['command']
+ def __init__(self,
+ errFile = sys.stderr,
+ warnFile = sys.stderr,
+ diagFile = sys.stdout):
+ OutputGenerator.__init__(self, errFile, warnFile, diagFile)
+ self.INDENT_SPACES = 4
+ # Commands to ignore
+ self.blacklist = [
+ 'vkGetInstanceProcAddr',
+ 'vkGetDeviceProcAddr',
+ 'vkEnumerateInstanceLayerProperties',
+ 'vkEnumerateInstanceExtensionsProperties',
+ 'vkEnumerateDeviceLayerProperties',
+ 'vkEnumerateDeviceExtensionsProperties',
+ 'vkCreateDebugReportCallbackEXT',
+ 'vkDebugReportMessageEXT']
+ # Validation conditions for some special case struct members that are conditionally validated
+ self.structMemberValidationConditions = { 'VkPipelineColorBlendStateCreateInfo' : { 'logicOp' : '{}logicOpEnable == VK_TRUE' } }
+ # Header version
+ self.headerVersion = None
+ # Internal state - accumulators for different inner block text
+ self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
+ self.structNames = [] # List of Vulkan struct typenames
+ self.stypes = [] # Values from the VkStructureType enumeration
+ self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType
+ self.handleTypes = set() # Set of handle type names
+ self.commands = [] # List of CommandData records for all Vulkan commands
+ self.structMembers = [] # List of StructMemberData records for all Vulkan structs
+ self.validatedStructs = dict() # Map of structs type names to generated validation code for that struct type
+ self.enumRanges = dict() # Map of enum name to BEGIN/END range values
+ self.flags = set() # Map of flags typenames
+ self.flagBits = dict() # Map of flag bits typename to list of values
+ # Named tuples to store struct and command data
+ self.StructType = namedtuple('StructType', ['name', 'value'])
+ self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isstaticarray', 'isbool', 'israngedenum',
+ 'isconst', 'isoptional', 'iscount', 'noautovalidity', 'len', 'extstructs',
+ 'condition', 'cdecl'])
+ self.CommandData = namedtuple('CommandData', ['name', 'params', 'cdecl'])
+ self.StructMemberData = namedtuple('StructMemberData', ['name', 'members'])
+ #
+ def incIndent(self, indent):
+ inc = ' ' * self.INDENT_SPACES
+ if indent:
+ return indent + inc
+ return inc
+ #
+ def decIndent(self, indent):
+ if indent and (len(indent) > self.INDENT_SPACES):
+ return indent[:-self.INDENT_SPACES]
+ return ''
+ #
+ def beginFile(self, genOpts):
+ OutputGenerator.beginFile(self, genOpts)
+ # C-specific
+ #
+ # User-supplied prefix text, if any (list of strings)
+ if (genOpts.prefixText):
+ for s in genOpts.prefixText:
+ write(s, file=self.outFile)
+ #
+ # Multiple inclusion protection & C++ wrappers.
+ if (genOpts.protectFile and self.genOpts.filename):
+ headerSym = re.sub('\.h', '_H', os.path.basename(self.genOpts.filename)).upper()
+ write('#ifndef', headerSym, file=self.outFile)
+ write('#define', headerSym, '1', file=self.outFile)
+ self.newline()
+ #
+ # Headers
+ write('#include <string>', file=self.outFile)
+ self.newline()
+ write('#include "vulkan/vulkan.h"', file=self.outFile)
+ write('#include "vk_layer_extension_utils.h"', file=self.outFile)
+ write('#include "parameter_validation_utils.h"', file=self.outFile)
+ #
+ # Macros
+ self.newline()
+ write('#ifndef UNUSED_PARAMETER', file=self.outFile)
+ write('#define UNUSED_PARAMETER(x) (void)(x)', file=self.outFile)
+ write('#endif // UNUSED_PARAMETER', file=self.outFile)
+ #
+ # Namespace
+ self.newline()
+ write('namespace parameter_validation {', file = self.outFile)
+ def endFile(self):
+ # C-specific
+ self.newline()
+ # Namespace
+ write('} // namespace parameter_validation', file = self.outFile)
+ # Finish C++ wrapper and multiple inclusion protection
+ if (self.genOpts.protectFile and self.genOpts.filename):
+ self.newline()
+ write('#endif', file=self.outFile)
+ # Finish processing in superclass
+ OutputGenerator.endFile(self)
+ def beginFeature(self, interface, emit):
+ # Start processing in superclass
+ OutputGenerator.beginFeature(self, interface, emit)
+ # C-specific
+ # Accumulate includes, defines, types, enums, function pointer typedefs,
+ # end function prototypes separately for this feature. They're only
+ # printed in endFeature().
+ self.headerVersion = None
+ self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
+ self.structNames = []
+ self.stypes = []
+ self.structTypes = dict()
+ self.handleTypes = set()
+ self.commands = []
+ self.structMembers = []
+ self.validatedStructs = dict()
+ self.enumRanges = dict()
+ self.flags = set()
+ self.flagBits = dict()
+ def endFeature(self):
+ # C-specific
+ # Actually write the interface to the output file.
+ if (self.emit):
+ self.newline()
+ # If type declarations are needed by other features based on
+ # this one, it may be necessary to suppress the ExtraProtect,
+ # or move it below the 'for section...' loop.
+ if (self.featureExtraProtect != None):
+ write('#ifdef', self.featureExtraProtect, file=self.outFile)
+ # Generate the struct member checking code from the captured data
+ self.processStructMemberData()
+ # Generate the command parameter checking code from the captured data
+ self.processCmdData()
+ # Write the declaration for the HeaderVersion
+ if self.headerVersion:
+ write('const uint32_t GeneratedHeaderVersion = {};'.format(self.headerVersion), file=self.outFile)
+ self.newline()
+ # Write the declarations for the VkFlags values combining all flag bits
+ for flag in sorted(self.flags):
+ flagBits = flag.replace('Flags', 'FlagBits')
+ if flagBits in self.flagBits:
+ bits = self.flagBits[flagBits]
+ decl = 'const {} All{} = {}'.format(flag, flagBits, bits[0])
+ for bit in bits[1:]:
+ decl += '|' + bit
+ decl += ';'
+ write(decl, file=self.outFile)
+ self.newline()
+ # Write the parameter validation code to the file
+ if (self.sections['command']):
+ if (self.genOpts.protectProto):
+ write(self.genOpts.protectProto,
+ self.genOpts.protectProtoStr, file=self.outFile)
+ write('\n'.join(self.sections['command']), end='', file=self.outFile)
+ if (self.featureExtraProtect != None):
+ write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
+ else:
+ self.newline()
+ # Finish processing in superclass
+ OutputGenerator.endFeature(self)
+ #
+ # Append a definition to the specified section
+ def appendSection(self, section, text):
+ # self.sections[section].append('SECTION: ' + section + '\n')
+ self.sections[section].append(text)
+ #
+ # Type generation
+ def genType(self, typeinfo, name):
+ OutputGenerator.genType(self, typeinfo, name)
+ typeElem = typeinfo.elem
+ # If the type is a struct type, traverse the imbedded <member> tags
+ # generating a structure. Otherwise, emit the tag text.
+ category = typeElem.get('category')
+ if (category == 'struct' or category == 'union'):
+ self.structNames.append(name)
+ self.genStruct(typeinfo, name)
+ elif (category == 'handle'):
+ self.handleTypes.add(name)
+ elif (category == 'bitmask'):
+ self.flags.add(name)
+ elif (category == 'define'):
+ if name == 'VK_HEADER_VERSION':
+ nameElem = typeElem.find('name')
+ self.headerVersion = noneStr(nameElem.tail).strip()
+ #
+ # Struct parameter check generation.
+ # This is a special case of the <type> tag where the contents are
+ # interpreted as a set of <member> tags instead of freeform C
+ # C type declarations. The <member> tags are just like <param>
+ # tags - they are a declaration of a struct or union member.
+ # Only simple member declarations are supported (no nested
+ # structs etc.)
+ def genStruct(self, typeinfo, typeName):
+ OutputGenerator.genStruct(self, typeinfo, typeName)
+ conditions = self.structMemberValidationConditions[typeName] if typeName in self.structMemberValidationConditions else None
+ members = typeinfo.elem.findall('.//member')
+ #
+ # Iterate over members once to get length parameters for arrays
+ lens = set()
+ for member in members:
+ len = self.getLen(member)
+ if len:
+ lens.add(len)
+ #
+ # Generate member info
+ membersInfo = []
+ for member in members:
+ # Get the member's type and name
+ info = self.getTypeNameTuple(member)
+ type = info[0]
+ name = info[1]
+ stypeValue = ''
+ cdecl = self.makeCParamDecl(member, 0)
+ # Process VkStructureType
+ if type == 'VkStructureType':
+ # Extract the required struct type value from the comments
+ # embedded in the original text defining the 'typeinfo' element
+ rawXml = etree.tostring(typeinfo.elem).decode('ascii')
+ result = re.search(r'VK_STRUCTURE_TYPE_\w+', rawXml)
+ if result:
+ value = result.group(0)
+ else:
+ value = self.genVkStructureType(typeName)
+ # Store the required type value
+ self.structTypes[typeName] = self.StructType(name=name, value=value)
+ #
+ # Store pointer/array/string info
+ # Check for parameter name in lens set
+ iscount = False
+ if name in lens:
+ iscount = True
+ # The pNext members are not tagged as optional, but are treated as
+ # optional for parameter NULL checks. Static array members
+ # are also treated as optional to skip NULL pointer validation, as
+ # they won't be NULL.
+ isstaticarray = self.paramIsStaticArray(member)
+ isoptional = False
+ if self.paramIsOptional(member) or (name == 'pNext') or (isstaticarray):
+ isoptional = True
+ membersInfo.append(self.CommandParam(type=type, name=name,
+ ispointer=self.paramIsPointer(member),
+ isstaticarray=isstaticarray,
+ isbool=True if type == 'VkBool32' else False,
+ israngedenum=True if type in self.enumRanges else False,
+ isconst=True if 'const' in cdecl else False,
+ isoptional=isoptional,
+ iscount=iscount,
+ noautovalidity=True if member.attrib.get('noautovalidity') is not None else False,
+ len=self.getLen(member),
+ extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None,
+ condition=conditions[name] if conditions and name in conditions else None,
+ cdecl=cdecl))
+ self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo))
+ #
+ # Capture group (e.g. C "enum" type) info to be used for
+ # param check code generation.
+ # These are concatenated together with other types.
+ def genGroup(self, groupinfo, groupName):
+ OutputGenerator.genGroup(self, groupinfo, groupName)
+ groupElem = groupinfo.elem
+ #
+ # Store the sType values
+ if groupName == 'VkStructureType':
+ for elem in groupElem.findall('enum'):
+ self.stypes.append(elem.get('name'))
+ elif 'FlagBits' in groupName:
+ bits = []
+ for elem in groupElem.findall('enum'):
+ bits.append(elem.get('name'))
+ if bits:
+ self.flagBits[groupName] = bits
+ else:
+ # Determine if begin/end ranges are needed (we don't do this for VkStructureType, which has a more finely grained check)
+ expandName = re.sub(r'([0-9a-z_])([A-Z0-9][^A-Z0-9]?)',r'\1_\2',groupName).upper()
+ expandPrefix = expandName
+ expandSuffix = ''
+ expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName)
+ if expandSuffixMatch:
+ expandSuffix = '_' + expandSuffixMatch.group()
+ # Strip off the suffix from the prefix
+ expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
+ isEnum = ('FLAG_BITS' not in expandPrefix)
+ if isEnum:
+ self.enumRanges[groupName] = (expandPrefix + '_BEGIN_RANGE' + expandSuffix, expandPrefix + '_END_RANGE' + expandSuffix)
+ #
+ # Capture command parameter info to be used for param
+ # check code generation.
+ def genCmd(self, cmdinfo, name):
+ OutputGenerator.genCmd(self, cmdinfo, name)
+ if name not in self.blacklist:
+ params = cmdinfo.elem.findall('param')
+ # Get list of array lengths
+ lens = set()
+ for param in params:
+ len = self.getLen(param)
+ if len:
+ lens.add(len)
+ # Get param info
+ paramsInfo = []
+ for param in params:
+ paramInfo = self.getTypeNameTuple(param)
+ cdecl = self.makeCParamDecl(param, 0)
+ # Check for parameter name in lens set
+ iscount = False
+ if paramInfo[1] in lens:
+ iscount = True
+ paramsInfo.append(self.CommandParam(type=paramInfo[0], name=paramInfo[1],
+ ispointer=self.paramIsPointer(param),
+ isstaticarray=self.paramIsStaticArray(param),
+ isbool=True if paramInfo[0] == 'VkBool32' else False,
+ israngedenum=True if paramInfo[0] in self.enumRanges else False,
+ isconst=True if 'const' in cdecl else False,
+ isoptional=self.paramIsOptional(param),
+ iscount=iscount,
+ noautovalidity=True if param.attrib.get('noautovalidity') is not None else False,
+ len=self.getLen(param),
+ extstructs=None,
+ condition=None,
+ cdecl=cdecl))
+ self.commands.append(self.CommandData(name=name, params=paramsInfo, cdecl=self.makeCDecls(cmdinfo.elem)[0]))
+ #
+ # Check if the parameter passed in is a pointer
+ def paramIsPointer(self, param):
+ ispointer = 0
+ paramtype = param.find('type')
+ if (paramtype.tail is not None) and ('*' in paramtype.tail):
+ ispointer = paramtype.tail.count('*')
+ elif paramtype.text[:4] == 'PFN_':
+ # Treat function pointer typedefs as a pointer to a single value
+ ispointer = 1
+ return ispointer
+ #
+ # Check if the parameter passed in is a static array
+ def paramIsStaticArray(self, param):
+ isstaticarray = 0
+ paramname = param.find('name')
+ if (paramname.tail is not None) and ('[' in paramname.tail):
+ isstaticarray = paramname.tail.count('[')
+ return isstaticarray
+ #
+ # Check if the parameter passed in is optional
+ # Returns a list of Boolean values for comma separated len attributes (len='false,true')
+ def paramIsOptional(self, param):
+ # See if the handle is optional
+ isoptional = False
+ # Simple, if it's optional, return true
+ optString = param.attrib.get('optional')
+ if optString:
+ if optString == 'true':
+ isoptional = True
+ elif ',' in optString:
+ opts = []
+ for opt in optString.split(','):
+ val = opt.strip()
+ if val == 'true':
+ opts.append(True)
+ elif val == 'false':
+ opts.append(False)
+ else:
+ print('Unrecognized len attribute value',val)
+ isoptional = opts
+ return isoptional
+ #
+ # Check if the handle passed in is optional
+ # Uses the same logic as ValidityOutputGenerator.isHandleOptional
+ def isHandleOptional(self, param, lenParam):
+ # Simple, if it's optional, return true
+ if param.isoptional:
+ 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.noautovalidity:
+ 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 lenParam and lenParam.isoptional:
+ return True
+ return False
+ #
+ # Generate a VkStructureType based on a structure typename
+ def genVkStructureType(self, typename):
+ # Add underscore between lowercase then uppercase
+ value = re.sub('([a-z0-9])([A-Z])', r'\1_\2', typename)
+ # Change to uppercase
+ value = value.upper()
+ # Add STRUCTURE_TYPE_
+ return re.sub('VK_', 'VK_STRUCTURE_TYPE_', value)
+ #
+ # Get the cached VkStructureType value for the specified struct typename, or generate a VkStructureType
+ # value assuming the struct is defined by a different feature
+ def getStructType(self, typename):
+ value = None
+ if typename in self.structTypes:
+ value = self.structTypes[typename].value
+ else:
+ value = self.genVkStructureType(typename)
+ self.logMsg('diag', 'ParameterValidation: Generating {} for {} structure type that was not defined by the current feature'.format(value, typename))
+ return value
+ #
+ # Retrieve the value of the len tag
+ def getLen(self, param):
+ result = None
+ len = param.attrib.get('len')
+ if len and len != 'null-terminated':
+ # For string arrays, 'len' can look like 'count,null-terminated',
+ # indicating that we have a null terminated array of strings. We
+ # strip the null-terminated from the 'len' field and only return
+ # the parameter specifying the string count
+ if 'null-terminated' in len:
+ result = len.split(',')[0]
+ else:
+ result = len
+ result = str(result).replace('::', '->')
+ return result
+ #
+ # Retrieve the type and name for a parameter
+ def getTypeNameTuple(self, param):
+ type = ''
+ name = ''
+ for elem in param:
+ if elem.tag == 'type':
+ type = noneStr(elem.text)
+ elif elem.tag == 'name':
+ name = noneStr(elem.text)
+ return (type, name)
+ #
+ # Find a named parameter in a parameter list
+ def getParamByName(self, params, name):
+ for param in params:
+ if param.name == name:
+ return param
+ return None
+ #
+ # Extract length values from latexmath. Currently an inflexible solution that looks for specific
+ # patterns that are found in vk.xml. Will need to be updated when new patterns are introduced.
+ def parseLateXMath(self, source):
+ name = 'ERROR'
+ decoratedName = 'ERROR'
+ if 'mathit' in source:
+ # Matches expressions similar to 'latexmath:[$\lceil{\mathit{rasterizationSamples} \over 32}\rceil$]'
+ match = re.match(r'latexmath\s*\:\s*\[\s*\$\\l(\w+)\s*\{\s*\\mathit\s*\{\s*(\w+)\s*\}\s*\\over\s*(\d+)\s*\}\s*\\r(\w+)\$\s*\]', source)
+ if not match or match.group(1) != match.group(4):
+ raise 'Unrecognized latexmath expression'
+ name = match.group(2)
+ decoratedName = '{}({}/{})'.format(*match.group(1, 2, 3))
+ else:
+ # Matches expressions similar to 'latexmath : [$dataSize \over 4$]'
+ match = re.match(r'latexmath\s*\:\s*\[\s*\$\s*(\w+)\s*\\over\s*(\d+)\s*\$\s*\]', source)
+ name = match.group(1)
+ decoratedName = '{}/{}'.format(*match.group(1, 2))
+ return name, decoratedName
+ #
+ # Get the length paramater record for the specified parameter name
+ def getLenParam(self, params, name):
+ lenParam = None
+ if name:
+ if '->' in name:
+ # The count is obtained by dereferencing a member of a struct parameter
+ lenParam = self.CommandParam(name=name, iscount=True, ispointer=False, isbool=False, israngedenum=False, isconst=False,
+ isstaticarray=None, isoptional=False, type=None, noautovalidity=False, len=None, extstructs=None,
+ condition=None, cdecl=None)
+ elif 'latexmath' in name:
+ lenName, decoratedName = self.parseLateXMath(name)
+ lenParam = self.getParamByName(params, lenName)
+ # TODO: Zero-check the result produced by the equation?
+ # Copy the stored len parameter entry and overwrite the name with the processed latexmath equation
+ #param = self.getParamByName(params, lenName)
+ #lenParam = self.CommandParam(name=decoratedName, iscount=param.iscount, ispointer=param.ispointer,
+ # isoptional=param.isoptional, type=param.type, len=param.len,
+ # isstaticarray=param.isstaticarray, extstructs=param.extstructs,
+ # noautovalidity=True, condition=None, cdecl=param.cdecl)
+ else:
+ lenParam = self.getParamByName(params, name)
+ return lenParam
+ #
+ # Convert a vulkan.h command declaration into a parameter_validation.h definition
+ def getCmdDef(self, cmd):
+ #
+ # Strip the trailing ';' and split into individual lines
+ lines = cmd.cdecl[:-1].split('\n')
+ # Replace Vulkan prototype
+ lines[0] = 'static bool parameter_validation_' + cmd.name + '('
+ # Replace the first argument with debug_report_data, when the first
+ # argument is a handle (not vkCreateInstance)
+ reportData = ' debug_report_data*'.ljust(self.genOpts.alignFuncParam) + 'report_data,'
+ if cmd.name != 'vkCreateInstance':
+ lines[1] = reportData
+ else:
+ lines.insert(1, reportData)
+ return '\n'.join(lines)
+ #
+ # Generate the code to check for a NULL dereference before calling the
+ # validation function
+ def genCheckedLengthCall(self, name, exprs):
+ count = name.count('->')
+ if count:
+ checkedExpr = []
+ localIndent = ''
+ elements = name.split('->')
+ # Open the if expression blocks
+ for i in range(0, count):
+ checkedExpr.append(localIndent + 'if ({} != NULL) {{\n'.format('->'.join(elements[0:i+1])))
+ localIndent = self.incIndent(localIndent)
+ # Add the validation expression
+ for expr in exprs:
+ checkedExpr.append(localIndent + expr)
+ # Close the if blocks
+ for i in range(0, count):
+ localIndent = self.decIndent(localIndent)
+ checkedExpr.append(localIndent + '}\n')
+ return [checkedExpr]
+ # No if statements were required
+ return exprs
+ #
+ # Generate code to check for a specific condition before executing validation code
+ def genConditionalCall(self, prefix, condition, exprs):
+ checkedExpr = []
+ localIndent = ''
+ formattedCondition = condition.format(prefix)
+ checkedExpr.append(localIndent + 'if ({})\n'.format(formattedCondition))
+ checkedExpr.append(localIndent + '{\n')
+ localIndent = self.incIndent(localIndent)
+ for expr in exprs:
+ checkedExpr.append(localIndent + expr)
+ localIndent = self.decIndent(localIndent)
+ checkedExpr.append(localIndent + '}\n')
+ return [checkedExpr]
+ #
+ # Generate the sType check string
+ def makeStructTypeCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, lenPtrRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec):
+ checkExpr = []
+ stype = self.structTypes[value.type]
+ if lenValue:
+ # This is an array with a pointer to a count value
+ if lenValue.ispointer:
+ # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
+ checkExpr.append('skipCall |= validate_struct_type_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {}, {});\n'.format(
+ funcPrintName, lenPtrRequired, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, sv=stype.value, pf=prefix, **postProcSpec))
+ # This is an array with an integer count value
+ else:
+ checkExpr.append('skipCall |= validate_struct_type_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {});\n'.format(
+ funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, sv=stype.value, pf=prefix, **postProcSpec))
+ # This is an individual struct
+ else:
+ checkExpr.append('skipCall |= validate_struct_type(report_data, "{}", {ppp}"{}"{pps}, "{sv}", {}{vn}, {sv}, {});\n'.format(
+ funcPrintName, valuePrintName, prefix, valueRequired, vn=value.name, sv=stype.value, **postProcSpec))
+ return checkExpr
+ #
+ # Generate the handle check string
+ def makeHandleCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec):
+ checkExpr = []
+ if lenValue:
+ if lenValue.ispointer:
+ # This is assumed to be an output array with a pointer to a count value
+ raise('Unsupported parameter validation case: Output handle array elements are not NULL checked')
+ else:
+ # This is an array with an integer count value
+ checkExpr.append('skipCall |= validate_handle_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format(
+ funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec))
+ else:
+ # This is assumed to be an output handle pointer
+ raise('Unsupported parameter validation case: Output handles are not NULL checked')
+ return checkExpr
+ #
+ # Generate check string for an array of VkFlags values
+ def makeFlagsArrayCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec):
+ checkExpr = []
+ flagBitsName = value.type.replace('Flags', 'FlagBits')
+ if not flagBitsName in self.flagBits:
+ raise('Unsupported parameter validation case: array of reserved VkFlags')
+ else:
+ allFlags = 'All' + flagBitsName
+ checkExpr.append('skipCall |= validate_flags_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, "{}", {}, {pf}{}, {pf}{}, {}, {});\n'.format(funcPrintName, lenPrintName, valuePrintName, flagBitsName, allFlags, lenValue.name, value.name, lenValueRequired, valueRequired, pf=prefix, **postProcSpec))
+ return checkExpr
+ #
+ # Generate pNext check string
+ def makeStructNextCheck(self, prefix, value, funcPrintName, valuePrintName, postProcSpec):
+ checkExpr = []
+ # Generate an array of acceptable VkStructureType values for pNext
+ extStructCount = 0
+ extStructVar = 'NULL'
+ extStructNames = 'NULL'
+ if value.extstructs:
+ structs = value.extstructs.split(',')
+ checkExpr.append('const VkStructureType allowedStructs[] = {' + ', '.join([self.getStructType(s) for s in structs]) + '};\n')
+ extStructCount = 'ARRAY_SIZE(allowedStructs)'
+ extStructVar = 'allowedStructs'
+ extStructNames = '"' + ', '.join(structs) + '"'
+ checkExpr.append('skipCall |= validate_struct_pnext(report_data, "{}", {ppp}"{}"{pps}, {}, {}{}, {}, {}, GeneratedHeaderVersion);\n'.format(
+ funcPrintName, valuePrintName, extStructNames, prefix, value.name, extStructCount, extStructVar, **postProcSpec))
+ return checkExpr
+ #
+ # Generate the pointer check string
+ def makePointerCheck(self, prefix, value, lenValue, valueRequired, lenValueRequired, lenPtrRequired, funcPrintName, lenPrintName, valuePrintName, postProcSpec):
+ checkExpr = []
+ if lenValue:
+ # This is an array with a pointer to a count value
+ if lenValue.ispointer:
+ # If count and array parameters are optional, there will be no validation
+ if valueRequired == 'true' or lenPtrRequired == 'true' or lenValueRequired == 'true':
+ # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
+ checkExpr.append('skipCall |= validate_array(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {}, {});\n'.format(
+ funcPrintName, lenPtrRequired, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec))
+ # This is an array with an integer count value
+ else:
+ # If count and array parameters are optional, there will be no validation
+ if valueRequired == 'true' or lenValueRequired == 'true':
+ # Arrays of strings receive special processing
+ validationFuncName = 'validate_array' if value.type != 'char' else 'validate_string_array'
+ checkExpr.append('skipCall |= {}(report_data, "{}", {ppp}"{ldn}"{pps}, {ppp}"{dn}"{pps}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format(
+ validationFuncName, funcPrintName, lenValueRequired, valueRequired, ln=lenValue.name, ldn=lenPrintName, dn=valuePrintName, vn=value.name, pf=prefix, **postProcSpec))
+ if checkExpr:
+ if lenValue and ('->' in lenValue.name):
+ # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count
+ checkExpr = self.genCheckedLengthCall(lenValue.name, checkExpr)
+ # This is an individual struct that is not allowed to be NULL
+ elif not value.isoptional:
+ # Function pointers need a reinterpret_cast to void*
+ if value.type[:4] == 'PFN_':
+ checkExpr.append('skipCall |= validate_required_pointer(report_data, "{}", {ppp}"{}"{pps}, reinterpret_cast<const void*>({}{}));\n'.format(funcPrintName, valuePrintName, prefix, value.name, **postProcSpec))
+ else:
+ checkExpr.append('skipCall |= validate_required_pointer(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcPrintName, valuePrintName, prefix, value.name, **postProcSpec))
+ return checkExpr
+ #
+ # Process struct member validation code, performing name suibstitution if required
+ def processStructMemberCode(self, line, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec):
+ # Build format specifier list
+ kwargs = {}
+ if '{postProcPrefix}' in line:
+ # If we have a tuple that includes a format string and format parameters, need to use ParameterName class
+ if type(memberDisplayNamePrefix) is tuple:
+ kwargs['postProcPrefix'] = 'ParameterName('
+ else:
+ kwargs['postProcPrefix'] = postProcSpec['ppp']
+ if '{postProcSuffix}' in line:
+ # If we have a tuple that includes a format string and format parameters, need to use ParameterName class
+ if type(memberDisplayNamePrefix) is tuple:
+ kwargs['postProcSuffix'] = ', ParameterName::IndexVector{{ {}{} }})'.format(postProcSpec['ppi'], memberDisplayNamePrefix[1])
+ else:
+ kwargs['postProcSuffix'] = postProcSpec['pps']
+ if '{postProcInsert}' in line:
+ # If we have a tuple that includes a format string and format parameters, need to use ParameterName class
+ if type(memberDisplayNamePrefix) is tuple:
+ kwargs['postProcInsert'] = '{}{}, '.format(postProcSpec['ppi'], memberDisplayNamePrefix[1])
+ else:
+ kwargs['postProcInsert'] = postProcSpec['ppi']
+ if '{funcName}' in line:
+ kwargs['funcName'] = funcName
+ if '{valuePrefix}' in line:
+ kwargs['valuePrefix'] = memberNamePrefix
+ if '{displayNamePrefix}' in line:
+ # Check for a tuple that includes a format string and format parameters to be used with the ParameterName class
+ if type(memberDisplayNamePrefix) is tuple:
+ kwargs['displayNamePrefix'] = memberDisplayNamePrefix[0]
+ else:
+ kwargs['displayNamePrefix'] = memberDisplayNamePrefix
+
+ if kwargs:
+ # Need to escape the C++ curly braces
+ if 'IndexVector' in line:
+ line = line.replace('IndexVector{ ', 'IndexVector{{ ')
+ line = line.replace(' }),', ' }}),')
+ return line.format(**kwargs)
+ return line
+ #
+ # Process struct validation code for inclusion in function or parent struct validation code
+ def expandStructCode(self, lines, funcName, memberNamePrefix, memberDisplayNamePrefix, indent, output, postProcSpec):
+ for line in lines:
+ if output:
+ output[-1] += '\n'
+ if type(line) is list:
+ for sub in line:
+ output.append(self.processStructMemberCode(indent + sub, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec))
+ else:
+ output.append(self.processStructMemberCode(indent + line, funcName, memberNamePrefix, memberDisplayNamePrefix, postProcSpec))
+ return output
+ #
+ # Process struct pointer/array validation code, perfoeming name substitution if required
+ def expandStructPointerCode(self, prefix, value, lenValue, funcName, valueDisplayName, postProcSpec):
+ expr = []
+ expr.append('if ({}{} != NULL)\n'.format(prefix, value.name))
+ expr.append('{')
+ indent = self.incIndent(None)
+ if lenValue:
+ # Need to process all elements in the array
+ indexName = lenValue.name.replace('Count', 'Index')
+ expr[-1] += '\n'
+ expr.append(indent + 'for (uint32_t {iname} = 0; {iname} < {}{}; ++{iname})\n'.format(prefix, lenValue.name, iname=indexName))
+ expr.append(indent + '{')
+ indent = self.incIndent(indent)
+ # Prefix for value name to display in error message
+ memberNamePrefix = '{}{}[{}].'.format(prefix, value.name, indexName)
+ memberDisplayNamePrefix = ('{}[%i].'.format(valueDisplayName), indexName)
+ else:
+ memberNamePrefix = '{}{}->'.format(prefix, value.name)
+ memberDisplayNamePrefix = '{}->'.format(valueDisplayName)
+ #
+ # Expand the struct validation lines
+ expr = self.expandStructCode(self.validatedStructs[value.type], funcName, memberNamePrefix, memberDisplayNamePrefix, indent, expr, postProcSpec)
+ #
+ if lenValue:
+ # Close if and for scopes
+ indent = self.decIndent(indent)
+ expr.append(indent + '}\n')
+ expr.append('}\n')
+ return expr
+ #
+ # Generate the parameter checking code
+ def genFuncBody(self, funcName, values, valuePrefix, displayNamePrefix, structTypeName):
+ lines = [] # Generated lines of code
+ unused = [] # Unused variable names
+ for value in values:
+ usedLines = []
+ lenParam = None
+ #
+ # Prefix and suffix for post processing of parameter names for struct members. Arrays of structures need special processing to include the array index in the full parameter name.
+ postProcSpec = {}
+ postProcSpec['ppp'] = '' if not structTypeName else '{postProcPrefix}'
+ postProcSpec['pps'] = '' if not structTypeName else '{postProcSuffix}'
+ postProcSpec['ppi'] = '' if not structTypeName else '{postProcInsert}'
+ #
+ # Generate the full name of the value, which will be printed in the error message, by adding the variable prefix to the value name
+ valueDisplayName = '{}{}'.format(displayNamePrefix, value.name)
+ #
+ # Check for NULL pointers, ignore the inout count parameters that
+ # will be validated with their associated array
+ if (value.ispointer or value.isstaticarray) and not value.iscount:
+ #
+ # Parameters for function argument generation
+ req = 'true' # Paramerter cannot be NULL
+ cpReq = 'true' # Count pointer cannot be NULL
+ cvReq = 'true' # Count value cannot be 0
+ lenDisplayName = None # Name of length parameter to print with validation messages; parameter name with prefix applied
+ #
+ # Generate required/optional parameter strings for the pointer and count values
+ if value.isoptional:
+ req = 'false'
+ if value.len:
+ # The parameter is an array with an explicit count parameter
+ lenParam = self.getLenParam(values, value.len)
+ lenDisplayName = '{}{}'.format(displayNamePrefix, lenParam.name)
+ if lenParam.ispointer:
+ # Count parameters that are pointers are inout
+ if type(lenParam.isoptional) is list:
+ if lenParam.isoptional[0]:
+ cpReq = 'false'
+ if lenParam.isoptional[1]:
+ cvReq = 'false'
+ else:
+ if lenParam.isoptional:
+ cpReq = 'false'
+ else:
+ if lenParam.isoptional:
+ cvReq = 'false'
+ #
+ # The parameter will not be processes when tagged as 'noautovalidity'
+ # For the pointer to struct case, the struct pointer will not be validated, but any
+ # members not tagged as 'noatuvalidity' will be validated
+ if value.noautovalidity:
+ # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually
+ self.logMsg('diag', 'ParameterValidation: No validation for {} {}'.format(structTypeName if structTypeName else funcName, value.name))
+ else:
+ #
+ # If this is a pointer to a struct with an sType field, verify the type
+ if value.type in self.structTypes:
+ usedLines += self.makeStructTypeCheck(valuePrefix, value, lenParam, req, cvReq, cpReq, funcName, lenDisplayName, valueDisplayName, postProcSpec)
+ # If this is an input handle array that is not allowed to contain NULL handles, verify that none of the handles are VK_NULL_HANDLE
+ elif value.type in self.handleTypes and value.isconst and not self.isHandleOptional(value, lenParam):
+ usedLines += self.makeHandleCheck(valuePrefix, value, lenParam, req, cvReq, funcName, lenDisplayName, valueDisplayName, postProcSpec)
+ elif value.type in self.flags and value.isconst:
+ usedLines += self.makeFlagsArrayCheck(valuePrefix, value, lenParam, req, cvReq, funcName, lenDisplayName, valueDisplayName, postProcSpec)
+ elif value.isbool and value.isconst:
+ usedLines.append('skipCall |= validate_bool32_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, {pf}{}, {pf}{}, {}, {});\n'.format(funcName, lenDisplayName, valueDisplayName, lenParam.name, value.name, cvReq, req, pf=valuePrefix, **postProcSpec))
+ elif value.israngedenum and value.isconst:
+ enumRange = self.enumRanges[value.type]
+ usedLines.append('skipCall |= validate_ranged_enum_array(report_data, "{}", {ppp}"{}"{pps}, {ppp}"{}"{pps}, "{}", {}, {}, {pf}{}, {pf}{}, {}, {});\n'.format(funcName, lenDisplayName, valueDisplayName, value.type, enumRange[0], enumRange[1], lenParam.name, value.name, cvReq, req, pf=valuePrefix, **postProcSpec))
+ elif value.name == 'pNext':
+ # We need to ignore VkDeviceCreateInfo and VkInstanceCreateInfo, as the loader manipulates them in a way that is not documented in vk.xml
+ if not structTypeName in ['VkDeviceCreateInfo', 'VkInstanceCreateInfo']:
+ usedLines += self.makeStructNextCheck(valuePrefix, value, funcName, valueDisplayName, postProcSpec)
+ else:
+ usedLines += self.makePointerCheck(valuePrefix, value, lenParam, req, cvReq, cpReq, funcName, lenDisplayName, valueDisplayName, postProcSpec)
+ #
+ # If this is a pointer to a struct (input), see if it contains members that need to be checked
+ if value.type in self.validatedStructs and value.isconst:
+ usedLines.append(self.expandStructPointerCode(valuePrefix, value, lenParam, funcName, valueDisplayName, postProcSpec))
+ # Non-pointer types
+ else:
+ #
+ # The parameter will not be processes when tagged as 'noautovalidity'
+ # For the struct case, the struct type will not be validated, but any
+ # members not tagged as 'noatuvalidity' will be validated
+ if value.noautovalidity:
+ # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually
+ self.logMsg('diag', 'ParameterValidation: No validation for {} {}'.format(structTypeName if structTypeName else funcName, value.name))
+ else:
+ if value.type in self.structTypes:
+ stype = self.structTypes[value.type]
+ usedLines.append('skipCall |= validate_struct_type(report_data, "{}", {ppp}"{}"{pps}, "{sv}", &({}{vn}), {sv}, false);\n'.format(
+ funcName, valueDisplayName, valuePrefix, vn=value.name, sv=stype.value, **postProcSpec))
+ elif value.type in self.handleTypes:
+ if not self.isHandleOptional(value, None):
+ usedLines.append('skipCall |= validate_required_handle(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcName, valueDisplayName, valuePrefix, value.name, **postProcSpec))
+ elif value.type in self.flags:
+ flagBitsName = value.type.replace('Flags', 'FlagBits')
+ if not flagBitsName in self.flagBits:
+ usedLines.append('skipCall |= validate_reserved_flags(report_data, "{}", {ppp}"{}"{pps}, {pf}{});\n'.format(funcName, valueDisplayName, value.name, pf=valuePrefix, **postProcSpec))
+ else:
+ flagsRequired = 'false' if value.isoptional else 'true'
+ allFlagsName = 'All' + flagBitsName
+ usedLines.append('skipCall |= validate_flags(report_data, "{}", {ppp}"{}"{pps}, "{}", {}, {pf}{}, {});\n'.format(funcName, valueDisplayName, flagBitsName, allFlagsName, value.name, flagsRequired, pf=valuePrefix, **postProcSpec))
+ elif value.isbool:
+ usedLines.append('skipCall |= validate_bool32(report_data, "{}", {ppp}"{}"{pps}, {}{});\n'.format(funcName, valueDisplayName, valuePrefix, value.name, **postProcSpec))
+ elif value.israngedenum:
+ enumRange = self.enumRanges[value.type]
+ usedLines.append('skipCall |= validate_ranged_enum(report_data, "{}", {ppp}"{}"{pps}, "{}", {}, {}, {}{});\n'.format(funcName, valueDisplayName, value.type, enumRange[0], enumRange[1], valuePrefix, value.name, **postProcSpec))
+ #
+ # If this is a struct, see if it contains members that need to be checked
+ if value.type in self.validatedStructs:
+ memberNamePrefix = '{}{}.'.format(valuePrefix, value.name)
+ memberDisplayNamePrefix = '{}.'.format(valueDisplayName)
+ usedLines.append(self.expandStructCode(self.validatedStructs[value.type], funcName, memberNamePrefix, memberDisplayNamePrefix, '', [], postProcSpec))
+ #
+ # Append the parameter check to the function body for the current command
+ if usedLines:
+ # Apply special conditional checks
+ if value.condition:
+ usedLines = self.genConditionalCall(valuePrefix, value.condition, usedLines)
+ lines += usedLines
+ elif not value.iscount:
+ # If no expression was generated for this value, it is unreferenced by the validation function, unless
+ # it is an array count, which is indirectly referenced for array valiadation.
+ unused.append(value.name)
+ return lines, unused
+ #
+ # Generate the struct member check code from the captured data
+ def processStructMemberData(self):
+ indent = self.incIndent(None)
+ for struct in self.structMembers:
+ #
+ # The string returned by genFuncBody will be nested in an if check for a NULL pointer, so needs its indent incremented
+ lines, unused = self.genFuncBody('{funcName}', struct.members, '{valuePrefix}', '{displayNamePrefix}', struct.name)
+ if lines:
+ self.validatedStructs[struct.name] = lines
+ #
+ # Generate the command param check code from the captured data
+ def processCmdData(self):
+ indent = self.incIndent(None)
+ for command in self.commands:
+ # Skip first parameter if it is a dispatch handle (everything except vkCreateInstance)
+ startIndex = 0 if command.name == 'vkCreateInstance' else 1
+ lines, unused = self.genFuncBody(command.name, command.params[startIndex:], '', '', None)
+ if lines:
+ cmdDef = self.getCmdDef(command) + '\n'
+ cmdDef += '{\n'
+ # Process unused parameters, Ignoring the first dispatch handle parameter, which is not
+ # processed by parameter_validation (except for vkCreateInstance, which does not have a
+ # handle as its first parameter)
+ if unused:
+ for name in unused:
+ cmdDef += indent + 'UNUSED_PARAMETER({});\n'.format(name)
+ if len(unused) > 0:
+ cmdDef += '\n'
+ cmdDef += indent + 'bool skipCall = false;\n'
+ for line in lines:
+ cmdDef += '\n'
+ if type(line) is list:
+ for sub in line:
+ cmdDef += indent + sub
+ else:
+ cmdDef += indent + line
+ cmdDef += '\n'
+ cmdDef += indent + 'return skipCall;\n'
+ cmdDef += '}\n'
+ self.appendSection('command', cmdDef)
diff --git a/scripts/reg.py b/scripts/reg.py
new file mode 100755
index 00000000..98436a33
--- /dev/null
+++ b/scripts/reg.py
@@ -0,0 +1,813 @@
+#!/usr/bin/python3 -i
+#
+# Copyright (c) 2013-2016 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 io,os,re,string,sys,copy
+import xml.etree.ElementTree as etree
+
+# matchAPIProfile - returns whether an API and profile
+# being generated matches an element's profile
+# api - string naming the API to match
+# profile - string naming the profile to match
+# elem - Element which (may) have 'api' and 'profile'
+# attributes to match to.
+# If a tag is not present in the Element, the corresponding API
+# or profile always matches.
+# Otherwise, the tag must exactly match the API or profile.
+# Thus, if 'profile' = core:
+# <remove> with no attribute will match
+# <remove profile='core'> will match
+# <remove profile='compatibility'> will not match
+# Possible match conditions:
+# Requested Element
+# Profile Profile
+# --------- --------
+# None None Always matches
+# 'string' None Always matches
+# None 'string' Does not match. Can't generate multiple APIs
+# or profiles, so if an API/profile constraint
+# is present, it must be asked for explicitly.
+# 'string' 'string' Strings must match
+#
+# ** In the future, we will allow regexes for the attributes,
+# not just strings, so that api="^(gl|gles2)" will match. Even
+# this isn't really quite enough, we might prefer something
+# like "gl(core)|gles1(common-lite)".
+def matchAPIProfile(api, profile, elem):
+ """Match a requested API & profile name to a api & profile attributes of an Element"""
+ match = True
+ # Match 'api', if present
+ if ('api' in elem.attrib):
+ if (api == None):
+ raise UserWarning("No API requested, but 'api' attribute is present with value '" +
+ elem.get('api') + "'")
+ elif (api != elem.get('api')):
+ # Requested API doesn't match attribute
+ return False
+ if ('profile' in elem.attrib):
+ if (profile == None):
+ raise UserWarning("No profile requested, but 'profile' attribute is present with value '" +
+ elem.get('profile') + "'")
+ elif (profile != elem.get('profile')):
+ # Requested profile doesn't match attribute
+ return False
+ return True
+
+# BaseInfo - base class for information about a registry feature
+# (type/group/enum/command/API/extension).
+# required - should this feature be defined during header generation
+# (has it been removed by a profile or version)?
+# declared - has this feature been defined already?
+# elem - etree Element for this feature
+# resetState() - reset required/declared to initial values. Used
+# prior to generating a new API interface.
+class BaseInfo:
+ """Represents the state of a registry feature, used during API generation"""
+ def __init__(self, elem):
+ self.required = False
+ self.declared = False
+ self.elem = elem
+ def resetState(self):
+ self.required = False
+ self.declared = False
+
+# TypeInfo - registry information about a type. No additional state
+# beyond BaseInfo is required.
+class TypeInfo(BaseInfo):
+ """Represents the state of a registry type"""
+ def __init__(self, elem):
+ BaseInfo.__init__(self, elem)
+ self.additionalValidity = []
+ self.removedValidity = []
+ def resetState(self):
+ BaseInfo.resetState(self)
+ self.additionalValidity = []
+ self.removedValidity = []
+
+# GroupInfo - registry information about a group of related enums
+# in an <enums> block, generally corresponding to a C "enum" type.
+class GroupInfo(BaseInfo):
+ """Represents the state of a registry <enums> group"""
+ def __init__(self, elem):
+ BaseInfo.__init__(self, elem)
+
+# EnumInfo - registry information about an enum
+# type - numeric type of the value of the <enum> tag
+# ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 )
+class EnumInfo(BaseInfo):
+ """Represents the state of a registry enum"""
+ def __init__(self, elem):
+ BaseInfo.__init__(self, elem)
+ self.type = elem.get('type')
+ if (self.type == None):
+ self.type = ''
+
+# CmdInfo - registry information about a command
+class CmdInfo(BaseInfo):
+ """Represents the state of a registry command"""
+ def __init__(self, elem):
+ BaseInfo.__init__(self, elem)
+ self.additionalValidity = []
+ self.removedValidity = []
+ def resetState(self):
+ BaseInfo.resetState(self)
+ self.additionalValidity = []
+ self.removedValidity = []
+
+# FeatureInfo - registry information about an API <feature>
+# or <extension>
+# name - feature name string (e.g. 'VK_KHR_surface')
+# version - feature version number (e.g. 1.2). <extension>
+# features are unversioned and assigned version number 0.
+# ** This is confusingly taken from the 'number' attribute of <feature>.
+# Needs fixing.
+# number - extension number, used for ordering and for
+# assigning enumerant offsets. <feature> features do
+# not have extension numbers and are assigned number 0.
+# category - category, e.g. VERSION or khr/vendor tag
+# emit - has this feature been defined already?
+class FeatureInfo(BaseInfo):
+ """Represents the state of an API feature (version/extension)"""
+ def __init__(self, elem):
+ BaseInfo.__init__(self, elem)
+ self.name = elem.get('name')
+ # Determine element category (vendor). Only works
+ # for <extension> elements.
+ if (elem.tag == 'feature'):
+ self.category = 'VERSION'
+ self.version = elem.get('number')
+ self.number = "0"
+ self.supported = None
+ else:
+ self.category = self.name.split('_', 2)[1]
+ self.version = "0"
+ self.number = elem.get('number')
+ self.supported = elem.get('supported')
+ self.emit = False
+
+from generator import write, GeneratorOptions, OutputGenerator
+
+# Registry - object representing an API registry, loaded from an XML file
+# Members
+# tree - ElementTree containing the root <registry>
+# typedict - dictionary of TypeInfo objects keyed by type name
+# groupdict - dictionary of GroupInfo objects keyed by group name
+# enumdict - dictionary of EnumInfo objects keyed by enum name
+# cmddict - dictionary of CmdInfo objects keyed by command name
+# apidict - dictionary of <api> Elements keyed by API name
+# extensions - list of <extension> Elements
+# extdict - dictionary of <extension> Elements keyed by extension name
+# gen - OutputGenerator object used to write headers / messages
+# genOpts - GeneratorOptions object used to control which
+# fetures to write and how to format them
+# emitFeatures - True to actually emit features for a version / extension,
+# or False to just treat them as emitted
+# Public methods
+# loadElementTree(etree) - load registry from specified ElementTree
+# loadFile(filename) - load registry from XML file
+# setGenerator(gen) - OutputGenerator to use
+# parseTree() - parse the registry once loaded & create dictionaries
+# dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries
+# to specified file handle (default stdout). Truncates type /
+# enum / command elements to maxlen characters (default 80)
+# generator(g) - specify the output generator object
+# apiGen(apiname, genOpts) - generate API headers for the API type
+# and profile specified in genOpts, but only for the versions and
+# extensions specified there.
+# apiReset() - call between calls to apiGen() to reset internal state
+# Private methods
+# addElementInfo(elem,info,infoName,dictionary) - add feature info to dict
+# lookupElementInfo(fname,dictionary) - lookup feature info in dict
+class Registry:
+ """Represents an API registry loaded from XML"""
+ def __init__(self):
+ self.tree = None
+ self.typedict = {}
+ self.groupdict = {}
+ self.enumdict = {}
+ self.cmddict = {}
+ self.apidict = {}
+ self.extensions = []
+ self.extdict = {}
+ # A default output generator, so commands prior to apiGen can report
+ # errors via the generator object.
+ self.gen = OutputGenerator()
+ self.genOpts = None
+ self.emitFeatures = False
+ def loadElementTree(self, tree):
+ """Load ElementTree into a Registry object and parse it"""
+ self.tree = tree
+ self.parseTree()
+ def loadFile(self, file):
+ """Load an API registry XML file into a Registry object and parse it"""
+ self.tree = etree.parse(file)
+ self.parseTree()
+ def setGenerator(self, gen):
+ """Specify output generator object. None restores the default generator"""
+ self.gen = gen
+ self.gen.setRegistry(self)
+
+ # addElementInfo - add information about an element to the
+ # corresponding dictionary
+ # elem - <type>/<enums>/<enum>/<command>/<feature>/<extension> Element
+ # info - corresponding {Type|Group|Enum|Cmd|Feature}Info object
+ # infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension'
+ # dictionary - self.{type|group|enum|cmd|api|ext}dict
+ # If the Element has an 'api' attribute, the dictionary key is the
+ # tuple (name,api). If not, the key is the name. 'name' is an
+ # attribute of the Element
+ def addElementInfo(self, elem, info, infoName, dictionary):
+ if ('api' in elem.attrib):
+ key = (elem.get('name'),elem.get('api'))
+ else:
+ key = elem.get('name')
+ if key in dictionary:
+ self.gen.logMsg('warn', '*** Attempt to redefine',
+ infoName, 'with key:', key)
+ else:
+ dictionary[key] = info
+ #
+ # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name.
+ # If an object qualified by API name exists, use that.
+ # fname - name of type / enum / command
+ # dictionary - self.{type|enum|cmd}dict
+ def lookupElementInfo(self, fname, dictionary):
+ key = (fname, self.genOpts.apiname)
+ if (key in dictionary):
+ # self.gen.logMsg('diag', 'Found API-specific element for feature', fname)
+ return dictionary[key]
+ elif (fname in dictionary):
+ # self.gen.logMsg('diag', 'Found generic element for feature', fname)
+ return dictionary[fname]
+ else:
+ return None
+ def parseTree(self):
+ """Parse the registry Element, once created"""
+ # This must be the Element for the root <registry>
+ self.reg = self.tree.getroot()
+ #
+ # Create dictionary of registry types from toplevel <types> tags
+ # and add 'name' attribute to each <type> tag (where missing)
+ # based on its <name> element.
+ #
+ # There's usually one <types> block; more are OK
+ # Required <type> attributes: 'name' or nested <name> tag contents
+ self.typedict = {}
+ for type in self.reg.findall('types/type'):
+ # If the <type> doesn't already have a 'name' attribute, set
+ # it from contents of its <name> tag.
+ if (type.get('name') == None):
+ type.attrib['name'] = type.find('name').text
+ self.addElementInfo(type, TypeInfo(type), 'type', self.typedict)
+ #
+ # Create dictionary of registry enum groups from <enums> tags.
+ #
+ # Required <enums> attributes: 'name'. If no name is given, one is
+ # generated, but that group can't be identified and turned into an
+ # enum type definition - it's just a container for <enum> tags.
+ self.groupdict = {}
+ for group in self.reg.findall('enums'):
+ self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict)
+ #
+ # Create dictionary of registry enums from <enum> tags
+ #
+ # <enums> tags usually define different namespaces for the values
+ # defined in those tags, but the actual names all share the
+ # same dictionary.
+ # Required <enum> attributes: 'name', 'value'
+ # For containing <enums> which have type="enum" or type="bitmask",
+ # tag all contained <enum>s are required. This is a stopgap until
+ # a better scheme for tagging core and extension enums is created.
+ self.enumdict = {}
+ for enums in self.reg.findall('enums'):
+ required = (enums.get('type') != None)
+ for enum in enums.findall('enum'):
+ enumInfo = EnumInfo(enum)
+ enumInfo.required = required
+ self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
+ #
+ # Create dictionary of registry commands from <command> tags
+ # and add 'name' attribute to each <command> tag (where missing)
+ # based on its <proto><name> element.
+ #
+ # There's usually only one <commands> block; more are OK.
+ # Required <command> attributes: 'name' or <proto><name> tag contents
+ self.cmddict = {}
+ for cmd in self.reg.findall('commands/command'):
+ # If the <command> doesn't already have a 'name' attribute, set
+ # it from contents of its <proto><name> tag.
+ if (cmd.get('name') == None):
+ cmd.attrib['name'] = cmd.find('proto/name').text
+ ci = CmdInfo(cmd)
+ self.addElementInfo(cmd, ci, 'command', self.cmddict)
+ #
+ # Create dictionaries of API and extension interfaces
+ # from toplevel <api> and <extension> tags.
+ #
+ self.apidict = {}
+ for feature in self.reg.findall('feature'):
+ featureInfo = FeatureInfo(feature)
+ self.addElementInfo(feature, featureInfo, 'feature', self.apidict)
+ self.extensions = self.reg.findall('extensions/extension')
+ self.extdict = {}
+ for feature in self.extensions:
+ featureInfo = FeatureInfo(feature)
+ self.addElementInfo(feature, featureInfo, 'extension', self.extdict)
+
+ # Add additional enums defined only in <extension> tags
+ # to the corresponding core type.
+ # When seen here, the <enum> element, processed to contain the
+ # numeric enum value, is added to the corresponding <enums>
+ # element, as well as adding to the enum dictionary. It is
+ # *removed* from the <require> element it is introduced in.
+ # Not doing this will cause spurious genEnum()
+ # calls to be made in output generation, and it's easier
+ # to handle here than in genEnum().
+ #
+ # In lxml.etree, an Element can have only one parent, so the
+ # append() operation also removes the element. But in Python's
+ # ElementTree package, an Element can have multiple parents. So
+ # it must be explicitly removed from the <require> tag, leading
+ # to the nested loop traversal of <require>/<enum> elements
+ # below.
+ #
+ # This code also adds a 'extnumber' attribute containing the
+ # extension number, used for enumerant value calculation.
+ #
+ # For <enum> tags which are actually just constants, if there's
+ # no 'extends' tag but there is a 'value' or 'bitpos' tag, just
+ # add an EnumInfo record to the dictionary. That works because
+ # output generation of constants is purely dependency-based, and
+ # doesn't need to iterate through the XML tags.
+ #
+ # Something like this will need to be done for 'feature's up
+ # above, if we use the same mechanism for adding to the core
+ # API in 1.1.
+ #
+ for elem in feature.findall('require'):
+ for enum in elem.findall('enum'):
+ addEnumInfo = False
+ groupName = enum.get('extends')
+ if (groupName != None):
+ # self.gen.logMsg('diag', '*** Found extension enum',
+ # enum.get('name'))
+ # Add extension number attribute to the <enum> element
+ enum.attrib['extnumber'] = featureInfo.number
+ enum.attrib['extname'] = featureInfo.name
+ enum.attrib['supported'] = featureInfo.supported
+ # Look up the GroupInfo with matching groupName
+ if (groupName in self.groupdict.keys()):
+ # self.gen.logMsg('diag', '*** Matching group',
+ # groupName, 'found, adding element...')
+ gi = self.groupdict[groupName]
+ gi.elem.append(enum)
+ # Remove element from parent <require> tag
+ # This should be a no-op in lxml.etree
+ elem.remove(enum)
+ else:
+ self.gen.logMsg('warn', '*** NO matching group',
+ groupName, 'for enum', enum.get('name'), 'found.')
+ addEnumInfo = True
+ elif (enum.get('value') or enum.get('bitpos')):
+ # self.gen.logMsg('diag', '*** Adding extension constant "enum"',
+ # enum.get('name'))
+ addEnumInfo = True
+ if (addEnumInfo):
+ enumInfo = EnumInfo(enum)
+ self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
+ def dumpReg(self, maxlen = 40, filehandle = sys.stdout):
+ """Dump all the dictionaries constructed from the Registry object"""
+ write('***************************************', file=filehandle)
+ write(' ** Dumping Registry contents **', file=filehandle)
+ write('***************************************', file=filehandle)
+ write('// Types', file=filehandle)
+ for name in self.typedict:
+ tobj = self.typedict[name]
+ write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle)
+ write('// Groups', file=filehandle)
+ for name in self.groupdict:
+ gobj = self.groupdict[name]
+ write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle)
+ write('// Enums', file=filehandle)
+ for name in self.enumdict:
+ eobj = self.enumdict[name]
+ write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle)
+ write('// Commands', file=filehandle)
+ for name in self.cmddict:
+ cobj = self.cmddict[name]
+ write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle)
+ write('// APIs', file=filehandle)
+ for key in self.apidict:
+ write(' API Version ', key, '->',
+ etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle)
+ write('// Extensions', file=filehandle)
+ for key in self.extdict:
+ write(' Extension', key, '->',
+ etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle)
+ # write('***************************************', file=filehandle)
+ # write(' ** Dumping XML ElementTree **', file=filehandle)
+ # write('***************************************', file=filehandle)
+ # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle)
+ #
+ # typename - name of type
+ # required - boolean (to tag features as required or not)
+ def markTypeRequired(self, typename, required):
+ """Require (along with its dependencies) or remove (but not its dependencies) a type"""
+ self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required)
+ # Get TypeInfo object for <type> tag corresponding to typename
+ type = self.lookupElementInfo(typename, self.typedict)
+ if (type != None):
+ if (required):
+ # Tag type dependencies in 'required' attributes as
+ # required. This DOES NOT un-tag dependencies in a <remove>
+ # tag. See comments in markRequired() below for the reason.
+ if ('requires' in type.elem.attrib):
+ depType = type.elem.get('requires')
+ self.gen.logMsg('diag', '*** Generating dependent type',
+ depType, 'for type', typename)
+ self.markTypeRequired(depType, required)
+ # Tag types used in defining this type (e.g. in nested
+ # <type> tags)
+ # Look for <type> in entire <command> tree,
+ # not just immediate children
+ for subtype in type.elem.findall('.//type'):
+ self.gen.logMsg('diag', '*** markRequired: type requires dependent <type>', subtype.text)
+ self.markTypeRequired(subtype.text, required)
+ # Tag enums used in defining this type, for example in
+ # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member>
+ for subenum in type.elem.findall('.//enum'):
+ self.gen.logMsg('diag', '*** markRequired: type requires dependent <enum>', subenum.text)
+ self.markEnumRequired(subenum.text, required)
+ type.required = required
+ else:
+ self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED')
+ #
+ # enumname - name of enum
+ # required - boolean (to tag features as required or not)
+ def markEnumRequired(self, enumname, required):
+ self.gen.logMsg('diag', '*** tagging enum:', enumname, '-> required =', required)
+ enum = self.lookupElementInfo(enumname, self.enumdict)
+ if (enum != None):
+ enum.required = required
+ else:
+ self.gen.logMsg('warn', '*** enum:', enumname , 'IS NOT DEFINED')
+ #
+ # features - Element for <require> or <remove> tag
+ # required - boolean (to tag features as required or not)
+ def markRequired(self, features, required):
+ """Require or remove features specified in the Element"""
+ self.gen.logMsg('diag', '*** markRequired (features = <too long to print>, required =', required, ')')
+ # Loop over types, enums, and commands in the tag
+ # @@ It would be possible to respect 'api' and 'profile' attributes
+ # in individual features, but that's not done yet.
+ for typeElem in features.findall('type'):
+ self.markTypeRequired(typeElem.get('name'), required)
+ for enumElem in features.findall('enum'):
+ self.markEnumRequired(enumElem.get('name'), required)
+ for cmdElem in features.findall('command'):
+ name = cmdElem.get('name')
+ self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required)
+ cmd = self.lookupElementInfo(name, self.cmddict)
+ if (cmd != None):
+ cmd.required = required
+ # Tag all parameter types of this command as required.
+ # This DOES NOT remove types of commands in a <remove>
+ # tag, because many other commands may use the same type.
+ # We could be more clever and reference count types,
+ # instead of using a boolean.
+ if (required):
+ # Look for <type> in entire <command> tree,
+ # not just immediate children
+ for type in cmd.elem.findall('.//type'):
+ self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', type.text)
+ self.markTypeRequired(type.text, required)
+ else:
+ self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED')
+ #
+ # interface - Element for <version> or <extension>, containing
+ # <require> and <remove> tags
+ # api - string specifying API name being generated
+ # profile - string specifying API profile being generated
+ def requireAndRemoveFeatures(self, interface, api, profile):
+ """Process <recquire> and <remove> tags for a <version> or <extension>"""
+ # <require> marks things that are required by this version/profile
+ for feature in interface.findall('require'):
+ if (matchAPIProfile(api, profile, feature)):
+ self.markRequired(feature,True)
+ # <remove> marks things that are removed by this version/profile
+ for feature in interface.findall('remove'):
+ if (matchAPIProfile(api, profile, feature)):
+ self.markRequired(feature,False)
+
+ def assignAdditionalValidity(self, interface, api, profile):
+ #
+ # Loop over all usage inside all <require> tags.
+ for feature in interface.findall('require'):
+ if (matchAPIProfile(api, profile, feature)):
+ for v in feature.findall('usage'):
+ if v.get('command'):
+ self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v))
+ if v.get('struct'):
+ self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v))
+
+ #
+ # Loop over all usage inside all <remove> tags.
+ for feature in interface.findall('remove'):
+ if (matchAPIProfile(api, profile, feature)):
+ for v in feature.findall('usage'):
+ if v.get('command'):
+ self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v))
+ if v.get('struct'):
+ self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v))
+
+ #
+ # generateFeature - generate a single type / enum group / enum / command,
+ # and all its dependencies as needed.
+ # fname - name of feature (<type>/<enum>/<command>)
+ # ftype - type of feature, 'type' | 'enum' | 'command'
+ # dictionary - of *Info objects - self.{type|enum|cmd}dict
+ def generateFeature(self, fname, ftype, dictionary):
+ f = self.lookupElementInfo(fname, dictionary)
+ if (f == None):
+ # No such feature. This is an error, but reported earlier
+ self.gen.logMsg('diag', '*** No entry found for feature', fname,
+ 'returning!')
+ return
+ #
+ # If feature isn't required, or has already been declared, return
+ if (not f.required):
+ self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)')
+ return
+ if (f.declared):
+ self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)')
+ return
+ # Always mark feature declared, as though actually emitted
+ f.declared = True
+ #
+ # Pull in dependent declaration(s) of the feature.
+ # For types, there may be one type in the 'required' attribute of
+ # the element, as well as many in imbedded <type> and <enum> tags
+ # within the element.
+ # For commands, there may be many in <type> tags within the element.
+ # For enums, no dependencies are allowed (though perhaps if you
+ # have a uint64 enum, it should require that type).
+ genProc = None
+ if (ftype == 'type'):
+ genProc = self.gen.genType
+ if ('requires' in f.elem.attrib):
+ depname = f.elem.get('requires')
+ self.gen.logMsg('diag', '*** Generating required dependent type',
+ depname)
+ self.generateFeature(depname, 'type', self.typedict)
+ for subtype in f.elem.findall('.//type'):
+ self.gen.logMsg('diag', '*** Generating required dependent <type>',
+ subtype.text)
+ self.generateFeature(subtype.text, 'type', self.typedict)
+ for subtype in f.elem.findall('.//enum'):
+ self.gen.logMsg('diag', '*** Generating required dependent <enum>',
+ subtype.text)
+ self.generateFeature(subtype.text, 'enum', self.enumdict)
+ # If the type is an enum group, look up the corresponding
+ # group in the group dictionary and generate that instead.
+ if (f.elem.get('category') == 'enum'):
+ self.gen.logMsg('diag', '*** Type', fname, 'is an enum group, so generate that instead')
+ group = self.lookupElementInfo(fname, self.groupdict)
+ if (group == None):
+ # Unless this is tested for, it's probably fatal to call below
+ genProc = None
+ self.logMsg('warn', '*** NO MATCHING ENUM GROUP FOUND!!!')
+ else:
+ genProc = self.gen.genGroup
+ f = group
+ elif (ftype == 'command'):
+ genProc = self.gen.genCmd
+ for type in f.elem.findall('.//type'):
+ depname = type.text
+ self.gen.logMsg('diag', '*** Generating required parameter type',
+ depname)
+ self.generateFeature(depname, 'type', self.typedict)
+ elif (ftype == 'enum'):
+ genProc = self.gen.genEnum
+ # Actually generate the type only if emitting declarations
+ if self.emitFeatures:
+ self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname)
+ genProc(f, fname)
+ else:
+ self.gen.logMsg('diag', '*** Skipping', ftype, fname,
+ '(not emitting this feature)')
+ #
+ # generateRequiredInterface - generate all interfaces required
+ # by an API version or extension
+ # interface - Element for <version> or <extension>
+ def generateRequiredInterface(self, interface):
+ """Generate required C interface for specified API version/extension"""
+
+ #
+ # Loop over all features inside all <require> tags.
+ for features in interface.findall('require'):
+ for t in features.findall('type'):
+ self.generateFeature(t.get('name'), 'type', self.typedict)
+ for e in features.findall('enum'):
+ self.generateFeature(e.get('name'), 'enum', self.enumdict)
+ for c in features.findall('command'):
+ self.generateFeature(c.get('name'), 'command', self.cmddict)
+
+ #
+ # apiGen(genOpts) - generate interface for specified versions
+ # genOpts - GeneratorOptions object with parameters used
+ # by the Generator object.
+ def apiGen(self, genOpts):
+ """Generate interfaces for the specified API type and range of versions"""
+ #
+ self.gen.logMsg('diag', '*******************************************')
+ self.gen.logMsg('diag', ' Registry.apiGen file:', genOpts.filename,
+ 'api:', genOpts.apiname,
+ 'profile:', genOpts.profile)
+ self.gen.logMsg('diag', '*******************************************')
+ #
+ self.genOpts = genOpts
+ # Reset required/declared flags for all features
+ self.apiReset()
+ #
+ # Compile regexps used to select versions & extensions
+ regVersions = re.compile(self.genOpts.versions)
+ regEmitVersions = re.compile(self.genOpts.emitversions)
+ regAddExtensions = re.compile(self.genOpts.addExtensions)
+ regRemoveExtensions = re.compile(self.genOpts.removeExtensions)
+ #
+ # Get all matching API versions & add to list of FeatureInfo
+ features = []
+ apiMatch = False
+ for key in self.apidict:
+ fi = self.apidict[key]
+ api = fi.elem.get('api')
+ if (api == self.genOpts.apiname):
+ apiMatch = True
+ if (regVersions.match(fi.version)):
+ # Matches API & version #s being generated. Mark for
+ # emission and add to the features[] list .
+ # @@ Could use 'declared' instead of 'emit'?
+ fi.emit = (regEmitVersions.match(fi.version) != None)
+ features.append(fi)
+ if (not fi.emit):
+ self.gen.logMsg('diag', '*** NOT tagging feature api =', api,
+ 'name =', fi.name, 'version =', fi.version,
+ 'for emission (does not match emitversions pattern)')
+ else:
+ self.gen.logMsg('diag', '*** NOT including feature api =', api,
+ 'name =', fi.name, 'version =', fi.version,
+ '(does not match requested versions)')
+ else:
+ self.gen.logMsg('diag', '*** NOT including feature api =', api,
+ 'name =', fi.name,
+ '(does not match requested API)')
+ if (not apiMatch):
+ self.gen.logMsg('warn', '*** No matching API versions found!')
+ #
+ # Get all matching extensions, in order by their extension number,
+ # and add to the list of features.
+ # Start with extensions tagged with 'api' pattern matching the API
+ # being generated. Add extensions matching the pattern specified in
+ # regExtensions, then remove extensions matching the pattern
+ # specified in regRemoveExtensions
+ for (extName,ei) in sorted(self.extdict.items(),key = lambda x : x[1].number):
+ extName = ei.name
+ include = False
+ #
+ # Include extension if defaultExtensions is not None and if the
+ # 'supported' attribute matches defaultExtensions. The regexp in
+ # 'supported' must exactly match defaultExtensions, so bracket
+ # it with ^(pat)$.
+ pat = '^(' + ei.elem.get('supported') + ')$'
+ if (self.genOpts.defaultExtensions and
+ re.match(pat, self.genOpts.defaultExtensions)):
+ self.gen.logMsg('diag', '*** Including extension',
+ extName, "(defaultExtensions matches the 'supported' attribute)")
+ include = True
+ #
+ # Include additional extensions if the extension name matches
+ # the regexp specified in the generator options. This allows
+ # forcing extensions into an interface even if they're not
+ # tagged appropriately in the registry.
+ if (regAddExtensions.match(extName) != None):
+ self.gen.logMsg('diag', '*** Including extension',
+ extName, '(matches explicitly requested extensions to add)')
+ include = True
+ # Remove extensions if the name matches the regexp specified
+ # in generator options. This allows forcing removal of
+ # extensions from an interface even if they're tagged that
+ # way in the registry.
+ if (regRemoveExtensions.match(extName) != None):
+ self.gen.logMsg('diag', '*** Removing extension',
+ extName, '(matches explicitly requested extensions to remove)')
+ include = False
+ #
+ # If the extension is to be included, add it to the
+ # extension features list.
+ if (include):
+ ei.emit = True
+ features.append(ei)
+ else:
+ self.gen.logMsg('diag', '*** NOT including extension',
+ extName, '(does not match api attribute or explicitly requested extensions)')
+ #
+ # Sort the extension features list, if a sort procedure is defined
+ if (self.genOpts.sortProcedure):
+ self.genOpts.sortProcedure(features)
+ #
+ # Pass 1: loop over requested API versions and extensions tagging
+ # types/commands/features as required (in an <require> block) or no
+ # longer required (in an <remove> block). It is possible to remove
+ # a feature in one version and restore it later by requiring it in
+ # a later version.
+ # If a profile other than 'None' is being generated, it must
+ # match the profile attribute (if any) of the <require> and
+ # <remove> tags.
+ self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************')
+ for f in features:
+ self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for',
+ f.name)
+ self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile)
+ self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile)
+ #
+ # Pass 2: loop over specified API versions and extensions printing
+ # declarations for required things which haven't already been
+ # generated.
+ self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************')
+ self.gen.beginFile(self.genOpts)
+ for f in features:
+ self.gen.logMsg('diag', '*** PASS 2: Generating interface for',
+ f.name)
+ emit = self.emitFeatures = f.emit
+ if (not emit):
+ self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature',
+ f.elem.get('name'), 'because it is not tagged for emission')
+ # Generate the interface (or just tag its elements as having been
+ # emitted, if they haven't been).
+ self.gen.beginFeature(f.elem, emit)
+ self.generateRequiredInterface(f.elem)
+ self.gen.endFeature()
+ self.gen.endFile()
+ #
+ # apiReset - use between apiGen() calls to reset internal state
+ #
+ def apiReset(self):
+ """Reset type/enum/command dictionaries before generating another API"""
+ for type in self.typedict:
+ self.typedict[type].resetState()
+ for enum in self.enumdict:
+ self.enumdict[enum].resetState()
+ for cmd in self.cmddict:
+ self.cmddict[cmd].resetState()
+ for cmd in self.apidict:
+ self.apidict[cmd].resetState()
+ #
+ # validateGroups - check that group= attributes match actual groups
+ #
+ def validateGroups(self):
+ """Validate group= attributes on <param> and <proto> tags"""
+ # Keep track of group names not in <group> tags
+ badGroup = {}
+ self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***')
+ for cmd in self.reg.findall('commands/command'):
+ proto = cmd.find('proto')
+ funcname = cmd.find('proto/name').text
+ if ('group' in proto.attrib.keys()):
+ group = proto.get('group')
+ # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group)
+ if (group not in self.groupdict.keys()):
+ # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group)
+ if (group not in badGroup.keys()):
+ badGroup[group] = 1
+ else:
+ badGroup[group] = badGroup[group] + 1
+ for param in cmd.findall('param'):
+ pname = param.find('name')
+ if (pname != None):
+ pname = pname.text
+ else:
+ pname = type.get('name')
+ if ('group' in param.attrib.keys()):
+ group = param.get('group')
+ if (group not in self.groupdict.keys()):
+ # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group)
+ if (group not in badGroup.keys()):
+ badGroup[group] = 1
+ else:
+ badGroup[group] = badGroup[group] + 1
+ if (len(badGroup.keys()) > 0):
+ self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***')
+ for key in sorted(badGroup.keys()):
+ self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times')
diff --git a/scripts/threading_generator.py b/scripts/threading_generator.py
new file mode 100644
index 00000000..0d0df12a
--- /dev/null
+++ b/scripts/threading_generator.py
@@ -0,0 +1,467 @@
+#!/usr/bin/python3 -i
+#
+# Copyright (c) 2015-2016 The Khronos Group Inc.
+# Copyright (c) 2015-2016 Valve Corporation
+# Copyright (c) 2015-2016 LunarG, Inc.
+# Copyright (c) 2015-2016 Google 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.
+#
+# Author: Mike Stroyan <stroyan@google.com>
+
+import os,re,sys
+from generator import *
+
+# ThreadGeneratorOptions - subclass of GeneratorOptions.
+#
+# Adds options used by ThreadOutputGenerator objects during threading
+# layer generation.
+#
+# Additional members
+# prefixText - list of strings to prefix generated header with
+# (usually a copyright statement + calling convention macros).
+# protectFile - True if multiple inclusion protection should be
+# generated (based on the filename) around the entire header.
+# protectFeature - True if #ifndef..#endif protection should be
+# generated around a feature interface in the header file.
+# genFuncPointers - True if function pointer typedefs should be
+# generated
+# protectProto - If conditional protection should be generated
+# around prototype declarations, set to either '#ifdef'
+# to require opt-in (#ifdef protectProtoStr) or '#ifndef'
+# to require opt-out (#ifndef protectProtoStr). Otherwise
+# set to None.
+# protectProtoStr - #ifdef/#ifndef symbol to use around prototype
+# declarations, if protectProto is set
+# apicall - string to use for the function declaration prefix,
+# such as APICALL on Windows.
+# apientry - string to use for the calling convention macro,
+# in typedefs, such as APIENTRY.
+# apientryp - string to use for the calling convention macro
+# in function pointer typedefs, such as APIENTRYP.
+# indentFuncProto - True if prototype declarations should put each
+# parameter on a separate line
+# indentFuncPointer - True if typedefed function pointers should put each
+# parameter on a separate line
+# alignFuncParam - if nonzero and parameters are being put on a
+# separate line, align parameter names at the specified column
+class ThreadGeneratorOptions(GeneratorOptions):
+ def __init__(self,
+ filename = None,
+ directory = '.',
+ apiname = None,
+ profile = None,
+ versions = '.*',
+ emitversions = '.*',
+ defaultExtensions = None,
+ addExtensions = None,
+ removeExtensions = None,
+ sortProcedure = regSortFeatures,
+ prefixText = "",
+ genFuncPointers = True,
+ protectFile = True,
+ protectFeature = True,
+ protectProto = None,
+ protectProtoStr = None,
+ apicall = '',
+ apientry = '',
+ apientryp = '',
+ indentFuncProto = True,
+ indentFuncPointer = False,
+ alignFuncParam = 0):
+ GeneratorOptions.__init__(self, filename, directory, apiname, profile,
+ versions, emitversions, defaultExtensions,
+ addExtensions, removeExtensions, sortProcedure)
+ self.prefixText = prefixText
+ self.genFuncPointers = genFuncPointers
+ self.protectFile = protectFile
+ self.protectFeature = protectFeature
+ self.protectProto = protectProto
+ self.protectProtoStr = protectProtoStr
+ self.apicall = apicall
+ self.apientry = apientry
+ self.apientryp = apientryp
+ self.indentFuncProto = indentFuncProto
+ self.indentFuncPointer = indentFuncPointer
+ self.alignFuncParam = alignFuncParam
+
+# ThreadOutputGenerator - subclass of OutputGenerator.
+# Generates Thread checking framework
+#
+# ---- methods ----
+# ThreadOutputGenerator(errFile, warnFile, diagFile) - args as for
+# OutputGenerator. Defines additional internal state.
+# ---- methods overriding base class ----
+# beginFile(genOpts)
+# endFile()
+# beginFeature(interface, emit)
+# endFeature()
+# genType(typeinfo,name)
+# genStruct(typeinfo,name)
+# genGroup(groupinfo,name)
+# genEnum(enuminfo, name)
+# genCmd(cmdinfo)
+class ThreadOutputGenerator(OutputGenerator):
+ """Generate specified API interfaces in a specific style, such as a C header"""
+ # This is an ordered list of sections in the header file.
+ TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
+ 'group', 'bitmask', 'funcpointer', 'struct']
+ ALL_SECTIONS = TYPE_SECTIONS + ['command']
+ def __init__(self,
+ errFile = sys.stderr,
+ warnFile = sys.stderr,
+ diagFile = sys.stdout):
+ OutputGenerator.__init__(self, errFile, warnFile, diagFile)
+ # Internal state - accumulators for different inner block text
+ self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
+ self.intercepts = []
+
+ # Check if the parameter passed in is a pointer to an array
+ def paramIsArray(self, param):
+ return param.attrib.get('len') is not None
+
+ # Check if the parameter passed in is a pointer
+ def paramIsPointer(self, param):
+ ispointer = False
+ for elem in param:
+ #write('paramIsPointer '+elem.text, file=sys.stderr)
+ #write('elem.tag '+elem.tag, file=sys.stderr)
+ #if (elem.tail is None):
+ # write('elem.tail is None', file=sys.stderr)
+ #else:
+ # write('elem.tail '+elem.tail, file=sys.stderr)
+ if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail:
+ ispointer = True
+ # write('is pointer', file=sys.stderr)
+ return ispointer
+ def makeThreadUseBlock(self, cmd, functionprefix):
+ """Generate C function pointer typedef for <command> Element"""
+ paramdecl = ''
+ thread_check_dispatchable_objects = [
+ "VkCommandBuffer",
+ "VkDevice",
+ "VkInstance",
+ "VkQueue",
+ ]
+ thread_check_nondispatchable_objects = [
+ "VkBuffer",
+ "VkBufferView",
+ "VkCommandPool",
+ "VkDescriptorPool",
+ "VkDescriptorSetLayout",
+ "VkDeviceMemory",
+ "VkEvent",
+ "VkFence",
+ "VkFramebuffer",
+ "VkImage",
+ "VkImageView",
+ "VkPipeline",
+ "VkPipelineCache",
+ "VkPipelineLayout",
+ "VkQueryPool",
+ "VkRenderPass",
+ "VkSampler",
+ "VkSemaphore",
+ "VkShaderModule",
+ ]
+
+ # Find and add any parameters that are thread unsafe
+ params = cmd.findall('param')
+ for param in params:
+ paramname = param.find('name')
+ if False: # self.paramIsPointer(param):
+ paramdecl += ' // not watching use of pointer ' + paramname.text + '\n'
+ else:
+ externsync = param.attrib.get('externsync')
+ if externsync == 'true':
+ if self.paramIsArray(param):
+ paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n'
+ paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + '[index]);\n'
+ paramdecl += ' }\n'
+ else:
+ paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + ');\n'
+ elif (param.attrib.get('externsync')):
+ if self.paramIsArray(param):
+ # Externsync can list pointers to arrays of members to synchronize
+ paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n'
+ for member in externsync.split(","):
+ # Replace first empty [] in member name with index
+ element = member.replace('[]','[index]',1)
+ if '[]' in element:
+ # Replace any second empty [] in element name with
+ # inner array index based on mapping array names like
+ # "pSomeThings[]" to "someThingCount" array size.
+ # This could be more robust by mapping a param member
+ # name to a struct type and "len" attribute.
+ limit = element[0:element.find('s[]')] + 'Count'
+ dotp = limit.rfind('.p')
+ limit = limit[0:dotp+1] + limit[dotp+2:dotp+3].lower() + limit[dotp+3:]
+ paramdecl += ' for(uint32_t index2=0;index2<'+limit+';index2++)\n'
+ element = element.replace('[]','[index2]')
+ paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + element + ');\n'
+ paramdecl += ' }\n'
+ else:
+ # externsync can list members to synchronize
+ for member in externsync.split(","):
+ member = str(member).replace("::", "->")
+ paramdecl += ' ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n'
+ else:
+ paramtype = param.find('type')
+ if paramtype is not None:
+ paramtype = paramtype.text
+ else:
+ paramtype = 'None'
+ if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects:
+ if self.paramIsArray(param) and ('pPipelines' != paramname.text):
+ paramdecl += ' for (uint32_t index=0;index<' + param.attrib.get('len') + ';index++) {\n'
+ paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n'
+ paramdecl += ' }\n'
+ elif not self.paramIsPointer(param):
+ # Pointer params are often being created.
+ # They are not being read from.
+ paramdecl += ' ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n'
+ explicitexternsyncparams = cmd.findall("param[@externsync]")
+ if (explicitexternsyncparams is not None):
+ for param in explicitexternsyncparams:
+ externsyncattrib = param.attrib.get('externsync')
+ paramname = param.find('name')
+ paramdecl += ' // Host access to '
+ if externsyncattrib == 'true':
+ if self.paramIsArray(param):
+ paramdecl += 'each member of ' + paramname.text
+ elif self.paramIsPointer(param):
+ paramdecl += 'the object referenced by ' + paramname.text
+ else:
+ paramdecl += paramname.text
+ else:
+ paramdecl += externsyncattrib
+ paramdecl += ' must be externally synchronized\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 += elem.text
+ paramdecl += ' must be externally synchronized between host accesses\n'
+
+ if (paramdecl == ''):
+ return None
+ else:
+ return paramdecl
+ def beginFile(self, genOpts):
+ OutputGenerator.beginFile(self, genOpts)
+ # C-specific
+ #
+ # Multiple inclusion protection & C++ namespace.
+ if (genOpts.protectFile and self.genOpts.filename):
+ headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
+ write('#ifndef', headerSym, file=self.outFile)
+ write('#define', headerSym, '1', file=self.outFile)
+ self.newline()
+ write('namespace threading {', file=self.outFile)
+ self.newline()
+ #
+ # User-supplied prefix text, if any (list of strings)
+ if (genOpts.prefixText):
+ for s in genOpts.prefixText:
+ write(s, file=self.outFile)
+ def endFile(self):
+ # C-specific
+ # Finish C++ namespace and multiple inclusion protection
+ self.newline()
+ # record intercepted procedures
+ write('// intercepts', file=self.outFile)
+ write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile)
+ write('\n'.join(self.intercepts), file=self.outFile)
+ write('};\n', file=self.outFile)
+ self.newline()
+ write('} // namespace threading', file=self.outFile)
+ if (self.genOpts.protectFile and self.genOpts.filename):
+ self.newline()
+ write('#endif', file=self.outFile)
+ # Finish processing in superclass
+ OutputGenerator.endFile(self)
+ def beginFeature(self, interface, emit):
+ #write('// starting beginFeature', file=self.outFile)
+ # Start processing in superclass
+ OutputGenerator.beginFeature(self, interface, emit)
+ # C-specific
+ # Accumulate includes, defines, types, enums, function pointer typedefs,
+ # end function prototypes separately for this feature. They're only
+ # printed in endFeature().
+ self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
+ #write('// ending beginFeature', file=self.outFile)
+ def endFeature(self):
+ # C-specific
+ # Actually write the interface to the output file.
+ #write('// starting endFeature', file=self.outFile)
+ if (self.emit):
+ self.newline()
+ if (self.genOpts.protectFeature):
+ write('#ifndef', self.featureName, file=self.outFile)
+ # If type declarations are needed by other features based on
+ # this one, it may be necessary to suppress the ExtraProtect,
+ # or move it below the 'for section...' loop.
+ #write('// endFeature looking at self.featureExtraProtect', file=self.outFile)
+ if (self.featureExtraProtect != None):
+ write('#ifdef', self.featureExtraProtect, file=self.outFile)
+ #write('#define', self.featureName, '1', file=self.outFile)
+ for section in self.TYPE_SECTIONS:
+ #write('// endFeature writing section'+section, file=self.outFile)
+ contents = self.sections[section]
+ if contents:
+ write('\n'.join(contents), file=self.outFile)
+ self.newline()
+ #write('// endFeature looking at self.sections[command]', file=self.outFile)
+ if (self.sections['command']):
+ write('\n'.join(self.sections['command']), end='', file=self.outFile)
+ self.newline()
+ if (self.featureExtraProtect != None):
+ write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
+ if (self.genOpts.protectFeature):
+ write('#endif /*', self.featureName, '*/', file=self.outFile)
+ # Finish processing in superclass
+ OutputGenerator.endFeature(self)
+ #write('// ending endFeature', file=self.outFile)
+ #
+ # Append a definition to the specified section
+ def appendSection(self, section, text):
+ # self.sections[section].append('SECTION: ' + section + '\n')
+ self.sections[section].append(text)
+ #
+ # Type generation
+ def genType(self, typeinfo, name):
+ pass
+ #
+ # Struct (e.g. C "struct" type) generation.
+ # This is a special case of the <type> tag where the contents are
+ # interpreted as a set of <member> tags instead of freeform C
+ # C type declarations. The <member> tags are just like <param>
+ # tags - they are a declaration of a struct or union member.
+ # Only simple member declarations are supported (no nested
+ # structs etc.)
+ def genStruct(self, typeinfo, typeName):
+ OutputGenerator.genStruct(self, typeinfo, typeName)
+ body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
+ # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
+ for member in typeinfo.elem.findall('.//member'):
+ body += self.makeCParamDecl(member, self.genOpts.alignFuncParam)
+ body += ';\n'
+ body += '} ' + typeName + ';\n'
+ self.appendSection('struct', body)
+ #
+ # Group (e.g. C "enum" type) generation.
+ # These are concatenated together with other types.
+ def genGroup(self, groupinfo, groupName):
+ pass
+ # Enumerant generation
+ # <enum> tags may specify their values in several ways, but are usually
+ # just integers.
+ def genEnum(self, enuminfo, name):
+ pass
+ #
+ # Command generation
+ def genCmd(self, cmdinfo, name):
+ # Commands shadowed by interface functions and are not implemented
+ interface_functions = [
+ 'vkEnumerateInstanceLayerProperties',
+ 'vkEnumerateInstanceExtensionProperties',
+ 'vkEnumerateDeviceLayerProperties',
+ ]
+ if name in interface_functions:
+ return
+ special_functions = [
+ 'vkGetDeviceProcAddr',
+ 'vkGetInstanceProcAddr',
+ 'vkCreateDevice',
+ 'vkDestroyDevice',
+ 'vkCreateInstance',
+ 'vkDestroyInstance',
+ 'vkAllocateCommandBuffers',
+ 'vkFreeCommandBuffers',
+ 'vkCreateDebugReportCallbackEXT',
+ 'vkDestroyDebugReportCallbackEXT',
+ ]
+ if name in special_functions:
+ decls = self.makeCDecls(cmdinfo.elem)
+ self.appendSection('command', '')
+ self.appendSection('command', '// declare only')
+ self.appendSection('command', decls[0])
+ self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name[2:]) ]
+ return
+ if "KHR" in name:
+ self.appendSection('command', '// TODO - not wrapping KHR function ' + name)
+ return
+ if ("DebugMarker" in name) and ("EXT" in name):
+ self.appendSection('command', '// TODO - not wrapping EXT function ' + name)
+ return
+ # Determine first if this function needs to be intercepted
+ startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start')
+ if startthreadsafety is None:
+ return
+ finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish')
+ # record that the function will be intercepted
+ if (self.featureExtraProtect != None):
+ self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ]
+ self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name[2:]) ]
+ if (self.featureExtraProtect != None):
+ self.intercepts += [ '#endif' ]
+
+ OutputGenerator.genCmd(self, cmdinfo, name)
+ #
+ decls = self.makeCDecls(cmdinfo.elem)
+ self.appendSection('command', '')
+ self.appendSection('command', decls[0][:-1])
+ self.appendSection('command', '{')
+ # setup common to call wrappers
+ # first parameter is always dispatchable
+ dispatchable_type = cmdinfo.elem.find('param/type').text
+ dispatchable_name = cmdinfo.elem.find('param/name').text
+ self.appendSection('command', ' dispatch_key key = get_dispatch_key('+dispatchable_name+');')
+ self.appendSection('command', ' layer_data *my_data = get_my_data_ptr(key, layer_data_map);')
+ if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]:
+ self.appendSection('command', ' VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;')
+ else:
+ self.appendSection('command', ' VkLayerDispatchTable *pTable = my_data->device_dispatch_table;')
+ # Declare result variable, if any.
+ resulttype = cmdinfo.elem.find('proto/type')
+ if (resulttype != None and resulttype.text == 'void'):
+ resulttype = None
+ if (resulttype != None):
+ self.appendSection('command', ' ' + resulttype.text + ' result;')
+ assignresult = 'result = '
+ else:
+ assignresult = ''
+
+ self.appendSection('command', ' bool threadChecks = startMultiThread();')
+ self.appendSection('command', ' if (threadChecks) {')
+ self.appendSection('command', " "+"\n ".join(str(startthreadsafety).rstrip().split("\n")))
+ self.appendSection('command', ' }')
+ params = cmdinfo.elem.findall('param/name')
+ paramstext = ','.join([str(param.text) for param in params])
+ API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1)
+ self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');')
+ self.appendSection('command', ' if (threadChecks) {')
+ self.appendSection('command', " "+"\n ".join(str(finishthreadsafety).rstrip().split("\n")))
+ self.appendSection('command', ' } else {')
+ self.appendSection('command', ' finishMultiThread();')
+ self.appendSection('command', ' }')
+ # Return result variable, if any.
+ if (resulttype != None):
+ self.appendSection('command', ' return result;')
+ self.appendSection('command', '}')
+ #
+ # override makeProtoName to drop the "vk" prefix
+ def makeProtoName(self, name, tail):
+ return self.genOpts.apientry + name[2:] + tail
diff --git a/scripts/unique_objects_generator.py b/scripts/unique_objects_generator.py
new file mode 100644
index 00000000..cdd2808c
--- /dev/null
+++ b/scripts/unique_objects_generator.py
@@ -0,0 +1,760 @@
+#!/usr/bin/python3 -i
+#
+# Copyright (c) 2015-2016 The Khronos Group Inc.
+# Copyright (c) 2015-2016 Valve Corporation
+# Copyright (c) 2015-2016 LunarG, Inc.
+# Copyright (c) 2015-2016 Google 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.
+#
+# Author: Tobin Ehlis <tobine@google.com>
+# Author: Mark Lobodzinski <mark@lunarg.com>
+
+import os,re,sys
+import xml.etree.ElementTree as etree
+from generator import *
+from collections import namedtuple
+
+# UniqueObjectsGeneratorOptions - subclass of GeneratorOptions.
+#
+# Adds options used by UniqueObjectsOutputGenerator objects during
+# unique objects layer generation.
+#
+# Additional members
+# prefixText - list of strings to prefix generated header with
+# (usually a copyright statement + calling convention macros).
+# protectFile - True if multiple inclusion protection should be
+# generated (based on the filename) around the entire header.
+# protectFeature - True if #ifndef..#endif protection should be
+# generated around a feature interface in the header file.
+# genFuncPointers - True if function pointer typedefs should be
+# generated
+# protectProto - If conditional protection should be generated
+# around prototype declarations, set to either '#ifdef'
+# to require opt-in (#ifdef protectProtoStr) or '#ifndef'
+# to require opt-out (#ifndef protectProtoStr). Otherwise
+# set to None.
+# protectProtoStr - #ifdef/#ifndef symbol to use around prototype
+# declarations, if protectProto is set
+# apicall - string to use for the function declaration prefix,
+# such as APICALL on Windows.
+# apientry - string to use for the calling convention macro,
+# in typedefs, such as APIENTRY.
+# apientryp - string to use for the calling convention macro
+# in function pointer typedefs, such as APIENTRYP.
+# indentFuncProto - True if prototype declarations should put each
+# parameter on a separate line
+# indentFuncPointer - True if typedefed function pointers should put each
+# parameter on a separate line
+# alignFuncParam - if nonzero and parameters are being put on a
+# separate line, align parameter names at the specified column
+class UniqueObjectsGeneratorOptions(GeneratorOptions):
+ def __init__(self,
+ filename = None,
+ directory = '.',
+ apiname = None,
+ profile = None,
+ versions = '.*',
+ emitversions = '.*',
+ defaultExtensions = None,
+ addExtensions = None,
+ removeExtensions = None,
+ sortProcedure = regSortFeatures,
+ prefixText = "",
+ genFuncPointers = True,
+ protectFile = True,
+ protectFeature = True,
+ protectProto = None,
+ protectProtoStr = None,
+ apicall = '',
+ apientry = '',
+ apientryp = '',
+ indentFuncProto = True,
+ indentFuncPointer = False,
+ alignFuncParam = 0):
+ GeneratorOptions.__init__(self, filename, directory, apiname, profile,
+ versions, emitversions, defaultExtensions,
+ addExtensions, removeExtensions, sortProcedure)
+ self.prefixText = prefixText
+ self.genFuncPointers = genFuncPointers
+ self.protectFile = protectFile
+ self.protectFeature = protectFeature
+ self.protectProto = protectProto
+ self.protectProtoStr = protectProtoStr
+ self.apicall = apicall
+ self.apientry = apientry
+ self.apientryp = apientryp
+ self.indentFuncProto = indentFuncProto
+ self.indentFuncPointer = indentFuncPointer
+ self.alignFuncParam = alignFuncParam
+
+# UniqueObjectsOutputGenerator - subclass of OutputGenerator.
+# Generates unique objects layer non-dispatchable handle-wrapping code.
+#
+# ---- methods ----
+# UniqueObjectsOutputGenerator(errFile, warnFile, diagFile) - args as for OutputGenerator. Defines additional internal state.
+# ---- methods overriding base class ----
+# beginFile(genOpts)
+# endFile()
+# beginFeature(interface, emit)
+# endFeature()
+# genCmd(cmdinfo)
+# genStruct()
+# genType()
+class UniqueObjectsOutputGenerator(OutputGenerator):
+ """Generate UniqueObjects code based on XML element attributes"""
+ # This is an ordered list of sections in the header file.
+ ALL_SECTIONS = ['command']
+ def __init__(self,
+ errFile = sys.stderr,
+ warnFile = sys.stderr,
+ diagFile = sys.stdout):
+ OutputGenerator.__init__(self, errFile, warnFile, diagFile)
+ self.INDENT_SPACES = 4
+ # Commands to ignore
+ self.intercepts = []
+ # Commands which are not autogenerated but still intercepted
+ self.no_autogen_list = [
+ 'vkGetDeviceProcAddr',
+ 'vkGetInstanceProcAddr',
+ 'vkCreateInstance',
+ 'vkDestroyInstance',
+ 'vkCreateDevice',
+ 'vkDestroyDevice',
+ 'vkAllocateMemory',
+ 'vkCreateComputePipelines',
+ 'vkCreateGraphicsPipelines',
+ 'vkCreateSwapchainKHR',
+ 'vkGetSwapchainImagesKHR',
+ 'vkEnumerateInstanceLayerProperties',
+ 'vkEnumerateDeviceLayerProperties',
+ 'vkEnumerateInstanceExtensionProperties',
+ ]
+ # Commands shadowed by interface functions and are not implemented
+ self.interface_functions = [
+ 'vkGetPhysicalDeviceDisplayPropertiesKHR',
+ 'vkGetPhysicalDeviceDisplayPlanePropertiesKHR',
+ 'vkGetDisplayPlaneSupportedDisplaysKHR',
+ 'vkGetDisplayModePropertiesKHR',
+ # DebugReport APIs are hooked, but handled separately in the source file
+ 'vkCreateDebugReportCallbackEXT',
+ 'vkDestroyDebugReportCallbackEXT',
+ 'vkDebugReportMessageEXT',
+ ]
+ self.headerVersion = None
+ # Internal state - accumulators for different inner block text
+ self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
+ self.structNames = [] # List of Vulkan struct typenames
+ self.structTypes = dict() # Map of Vulkan struct typename to required VkStructureType
+ self.handleTypes = set() # Set of handle type names
+ self.commands = [] # List of CommandData records for all Vulkan commands
+ self.structMembers = [] # List of StructMemberData records for all Vulkan structs
+ self.flags = set() # Map of flags typenames
+ # Named tuples to store struct and command data
+ self.StructType = namedtuple('StructType', ['name', 'value'])
+ self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isconst', 'iscount', 'len', 'extstructs', 'cdecl', 'islocal', 'iscreate', 'isdestroy'])
+ self.CommandData = namedtuple('CommandData', ['name', 'return_type', 'params', 'cdecl'])
+ self.StructMemberData = namedtuple('StructMemberData', ['name', 'members'])
+ #
+ def incIndent(self, indent):
+ inc = ' ' * self.INDENT_SPACES
+ if indent:
+ return indent + inc
+ return inc
+ #
+ def decIndent(self, indent):
+ if indent and (len(indent) > self.INDENT_SPACES):
+ return indent[:-self.INDENT_SPACES]
+ return ''
+ #
+ # Override makeProtoName to drop the "vk" prefix
+ def makeProtoName(self, name, tail):
+ return self.genOpts.apientry + name[2:] + tail
+ #
+ # Check if the parameter passed in is a pointer to an array
+ def paramIsArray(self, param):
+ return param.attrib.get('len') is not None
+ #
+ def beginFile(self, genOpts):
+ OutputGenerator.beginFile(self, genOpts)
+ # User-supplied prefix text, if any (list of strings)
+ if (genOpts.prefixText):
+ for s in genOpts.prefixText:
+ write(s, file=self.outFile)
+ # Namespace
+ self.newline()
+ write('namespace unique_objects {', file = self.outFile)
+ #
+ def endFile(self):
+ self.newline()
+ # Record intercepted procedures
+ write('// intercepts', file=self.outFile)
+ write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile)
+ write('\n'.join(self.intercepts), file=self.outFile)
+ write('};\n', file=self.outFile)
+ self.newline()
+ write('} // namespace unique_objects', file=self.outFile)
+ # Finish processing in superclass
+ OutputGenerator.endFile(self)
+ #
+ def beginFeature(self, interface, emit):
+ # Start processing in superclass
+ OutputGenerator.beginFeature(self, interface, emit)
+ self.headerVersion = None
+ self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
+ self.structNames = []
+ self.structTypes = dict()
+ self.handleTypes = set()
+ self.commands = []
+ self.structMembers = []
+ self.cmdMembers = []
+ self.flags = set()
+ self.StructMemberData = namedtuple('StructMemberData', ['name', 'members'])
+ self.CmdMemberData = namedtuple('CmdMemberData', ['name', 'members'])
+ #
+ def endFeature(self):
+ # Actually write the interface to the output file.
+ if (self.emit):
+ self.newline()
+ if (self.featureExtraProtect != None):
+ write('#ifdef', self.featureExtraProtect, file=self.outFile)
+ # Write the unique_objects code to the file
+ if (self.sections['command']):
+ if (self.genOpts.protectProto):
+ write(self.genOpts.protectProto,
+ self.genOpts.protectProtoStr, file=self.outFile)
+ write('\n'.join(self.sections['command']), end='', file=self.outFile)
+ if (self.featureExtraProtect != None):
+ write('\n#endif //', self.featureExtraProtect, file=self.outFile)
+ else:
+ self.newline()
+ # Finish processing in superclass
+ OutputGenerator.endFeature(self)
+ #
+ def genType(self, typeinfo, name):
+ OutputGenerator.genType(self, typeinfo, name)
+ typeElem = typeinfo.elem
+ # If the type is a struct type, traverse the imbedded <member> tags generating a structure.
+ # Otherwise, emit the tag text.
+ category = typeElem.get('category')
+ if (category == 'struct' or category == 'union'):
+ self.structNames.append(name)
+ self.genStruct(typeinfo, name)
+ #
+ # Append a definition to the specified section
+ def appendSection(self, section, text):
+ # self.sections[section].append('SECTION: ' + section + '\n')
+ self.sections[section].append(text)
+ #
+ # Check if the parameter passed in is a pointer
+ def paramIsPointer(self, param):
+ ispointer = False
+ for elem in param:
+ if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail:
+ ispointer = True
+ return ispointer
+ #
+ # 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')
+ #
+ # Check if a parent object is dispatchable or not
+ def isHandleTypeNonDispatchable(self, handletype):
+ handle = self.registry.tree.find("types/type/[name='" + handletype + "'][@category='handle']")
+ if handle is not None and handle.find('type').text == 'VK_DEFINE_NON_DISPATCHABLE_HANDLE':
+ return True
+ else:
+ return False
+ #
+ # Retrieve the type and name for a parameter
+ def getTypeNameTuple(self, param):
+ type = ''
+ name = ''
+ for elem in param:
+ if elem.tag == 'type':
+ type = noneStr(elem.text)
+ elif elem.tag == 'name':
+ name = noneStr(elem.text)
+ return (type, name)
+ #
+ # Retrieve the value of the len tag
+ def getLen(self, param):
+ result = None
+ len = param.attrib.get('len')
+ if len and len != 'null-terminated':
+ # For string arrays, 'len' can look like 'count,null-terminated', indicating that we
+ # have a null terminated array of strings. We strip the null-terminated from the
+ # 'len' field and only return the parameter specifying the string count
+ if 'null-terminated' in len:
+ result = len.split(',')[0]
+ else:
+ result = len
+ # Spec has now notation for len attributes, using :: instead of platform specific pointer symbol
+ result = str(result).replace('::', '->')
+ return result
+ #
+ # Generate a VkStructureType based on a structure typename
+ def genVkStructureType(self, typename):
+ # Add underscore between lowercase then uppercase
+ value = re.sub('([a-z0-9])([A-Z])', r'\1_\2', typename)
+ # Change to uppercase
+ value = value.upper()
+ # Add STRUCTURE_TYPE_
+ return re.sub('VK_', 'VK_STRUCTURE_TYPE_', value)
+ #
+ # Struct parameter check generation.
+ # This is a special case of the <type> tag where the contents are interpreted as a set of
+ # <member> tags instead of freeform C type declarations. The <member> tags are just like
+ # <param> tags - they are a declaration of a struct or union member. Only simple member
+ # declarations are supported (no nested structs etc.)
+ def genStruct(self, typeinfo, typeName):
+ OutputGenerator.genStruct(self, typeinfo, typeName)
+ members = typeinfo.elem.findall('.//member')
+ # Iterate over members once to get length parameters for arrays
+ lens = set()
+ for member in members:
+ len = self.getLen(member)
+ if len:
+ lens.add(len)
+ # Generate member info
+ membersInfo = []
+ for member in members:
+ # Get the member's type and name
+ info = self.getTypeNameTuple(member)
+ type = info[0]
+ name = info[1]
+ cdecl = self.makeCParamDecl(member, 0)
+ # Process VkStructureType
+ if type == 'VkStructureType':
+ # Extract the required struct type value from the comments
+ # embedded in the original text defining the 'typeinfo' element
+ rawXml = etree.tostring(typeinfo.elem).decode('ascii')
+ result = re.search(r'VK_STRUCTURE_TYPE_\w+', rawXml)
+ if result:
+ value = result.group(0)
+ else:
+ value = self.genVkStructureType(typeName)
+ # Store the required type value
+ self.structTypes[typeName] = self.StructType(name=name, value=value)
+ # Store pointer/array/string info
+ membersInfo.append(self.CommandParam(type=type,
+ name=name,
+ ispointer=self.paramIsPointer(member),
+ isconst=True if 'const' in cdecl else False,
+ iscount=True if name in lens else False,
+ len=self.getLen(member),
+ extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None,
+ cdecl=cdecl,
+ islocal=False,
+ iscreate=False,
+ isdestroy=False))
+ self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo))
+ #
+ # Insert a lock_guard line
+ def lock_guard(self, indent):
+ return '%sstd::lock_guard<std::mutex> lock(global_lock);\n' % indent
+ #
+ # Determine if a struct has an NDO as a member or an embedded member
+ def struct_contains_ndo(self, struct_item):
+ struct_member_dict = dict(self.structMembers)
+ struct_members = struct_member_dict[struct_item]
+
+ for member in struct_members:
+ if self.isHandleTypeNonDispatchable(member.type):
+ return True
+ elif member.type in struct_member_dict:
+ if self.struct_contains_ndo(member.type) == True:
+ return True
+ return False
+ #
+ # Return list of struct members which contain, or which sub-structures contain
+ # an NDO in a given list of parameters or members
+ def getParmeterStructsWithNdos(self, item_list):
+ struct_list = set()
+ for item in item_list:
+ paramtype = item.find('type')
+ typecategory = self.getTypeCategory(paramtype.text)
+ if typecategory == 'struct':
+ if self.struct_contains_ndo(paramtype.text) == True:
+ struct_list.add(item)
+ return struct_list
+ #
+ # Return list of non-dispatchable objects from a given list of parameters or members
+ def getNdosInParameterList(self, item_list, create_func):
+ ndo_list = set()
+ if create_func == True:
+ member_list = item_list[0:-1]
+ else:
+ member_list = item_list
+ for item in member_list:
+ if self.isHandleTypeNonDispatchable(paramtype.text):
+ ndo_list.add(item)
+ return ndo_list
+ #
+ # Generate source for creating a non-dispatchable object
+ def generate_create_ndo_code(self, indent, proto, params, cmd_info):
+ create_ndo_code = ''
+ if True in [create_txt in proto.text for create_txt in ['Create', 'Allocate']]:
+ handle_type = params[-1].find('type')
+ if self.isHandleTypeNonDispatchable(handle_type.text):
+ # Check for special case where multiple handles are returned
+ ndo_array = False
+ if cmd_info[-1].len is not None:
+ ndo_array = True;
+ handle_name = params[-1].find('name')
+ create_ndo_code += '%sif (VK_SUCCESS == result) {\n' % (indent)
+ indent = self.incIndent(indent)
+ create_ndo_code += '%sstd::lock_guard<std::mutex> lock(global_lock);\n' % (indent)
+ ndo_dest = '*%s' % handle_name.text
+ if ndo_array == True:
+ create_ndo_code += '%sfor (uint32_t index0 = 0; index0 < %s; index0++) {\n' % (indent, cmd_info[-1].len)
+ indent = self.incIndent(indent)
+ ndo_dest = '%s[index0]' % cmd_info[-1].name
+ create_ndo_code += '%suint64_t unique_id = global_unique_id++;\n' % (indent)
+ create_ndo_code += '%sdev_data->unique_id_mapping[unique_id] = reinterpret_cast<uint64_t &>(%s);\n' % (indent, ndo_dest)
+ create_ndo_code += '%s%s = reinterpret_cast<%s&>(unique_id);\n' % (indent, ndo_dest, handle_type.text)
+ if ndo_array == True:
+ indent = self.decIndent(indent)
+ create_ndo_code += '%s}\n' % indent
+ indent = self.decIndent(indent)
+ create_ndo_code += '%s}\n' % (indent)
+ return create_ndo_code
+ #
+ # Generate source for destroying a non-dispatchable object
+ def generate_destroy_ndo_code(self, indent, proto, cmd_info):
+ destroy_ndo_code = ''
+ ndo_array = False
+ if True in [destroy_txt in proto.text for destroy_txt in ['Destroy', 'Free']]:
+ # Check for special case where multiple handles are returned
+ if cmd_info[-1].len is not None:
+ ndo_array = True;
+ param = -1
+ else:
+ param = -2
+ if self.isHandleTypeNonDispatchable(cmd_info[param].type) == True:
+ if ndo_array == True:
+ # This API is freeing an array of handles. Remove them from the unique_id map.
+ destroy_ndo_code += '%sif ((VK_SUCCESS == result) && (%s)) {\n' % (indent, cmd_info[param].name)
+ indent = self.incIndent(indent)
+ destroy_ndo_code += '%sstd::unique_lock<std::mutex> lock(global_lock);\n' % (indent)
+ destroy_ndo_code += '%sfor (uint32_t index0 = 0; index0 < %s; index0++) {\n' % (indent, cmd_info[param].len)
+ indent = self.incIndent(indent)
+ destroy_ndo_code += '%s%s handle = %s[index0];\n' % (indent, cmd_info[param].type, cmd_info[param].name)
+ destroy_ndo_code += '%suint64_t unique_id = reinterpret_cast<uint64_t &>(handle);\n' % (indent)
+ destroy_ndo_code += '%sdev_data->unique_id_mapping.erase(unique_id);\n' % (indent)
+ indent = self.decIndent(indent);
+ destroy_ndo_code += '%s}\n' % indent
+ indent = self.decIndent(indent);
+ destroy_ndo_code += '%s}\n' % indent
+ else:
+ # Remove a single handle from the map
+ destroy_ndo_code += '%sstd::unique_lock<std::mutex> lock(global_lock);\n' % (indent)
+ destroy_ndo_code += '%suint64_t %s_id = reinterpret_cast<uint64_t &>(%s);\n' % (indent, cmd_info[param].name, cmd_info[param].name)
+ destroy_ndo_code += '%s%s = (%s)dev_data->unique_id_mapping[%s_id];\n' % (indent, cmd_info[param].name, cmd_info[param].type, cmd_info[param].name)
+ destroy_ndo_code += '%sdev_data->unique_id_mapping.erase(%s_id);\n' % (indent, cmd_info[param].name)
+ destroy_ndo_code += '%slock.unlock();\n' % (indent)
+ return ndo_array, destroy_ndo_code
+
+ #
+ # Clean up local declarations
+ def cleanUpLocalDeclarations(self, indent, prefix, name, len):
+ cleanup = '%sif (local_%s%s)\n' % (indent, prefix, name)
+ if len is not None:
+ cleanup += '%s delete[] local_%s%s;\n' % (indent, prefix, name)
+ else:
+ cleanup += '%s delete local_%s%s;\n' % (indent, prefix, name)
+ return cleanup
+ #
+ # Output UO code for a single NDO (ndo_count is NULL) or a counted list of NDOs
+ def outputNDOs(self, ndo_type, ndo_name, ndo_count, prefix, index, indent, destroy_func, destroy_array, top_level):
+ decl_code = ''
+ pre_call_code = ''
+ post_call_code = ''
+ if ndo_count is not None:
+ if top_level == True:
+ decl_code += '%s%s *local_%s%s = NULL;\n' % (indent, ndo_type, prefix, ndo_name)
+ pre_call_code += '%s if (%s%s) {\n' % (indent, prefix, ndo_name)
+ indent = self.incIndent(indent)
+ if top_level == True:
+ pre_call_code += '%s local_%s%s = new %s[%s];\n' % (indent, prefix, ndo_name, ndo_type, ndo_count)
+ pre_call_code += '%s for (uint32_t %s = 0; %s < %s; ++%s) {\n' % (indent, index, index, ndo_count, index)
+ indent = self.incIndent(indent)
+ pre_call_code += '%s local_%s%s[%s] = (%s)dev_data->unique_id_mapping[reinterpret_cast<const uint64_t &>(%s[%s])];\n' % (indent, prefix, ndo_name, index, ndo_type, ndo_name, index)
+ else:
+ pre_call_code += '%s for (uint32_t %s = 0; %s < %s; ++%s) {\n' % (indent, index, index, ndo_count, index)
+ indent = self.incIndent(indent)
+ pre_call_code += '%s %s%s[%s] = (%s)dev_data->unique_id_mapping[reinterpret_cast<const uint64_t &>(%s%s[%s])];\n' % (indent, prefix, ndo_name, index, ndo_type, prefix, ndo_name, index)
+ indent = self.decIndent(indent)
+ pre_call_code += '%s }\n' % indent
+ indent = self.decIndent(indent)
+ pre_call_code += '%s }\n' % indent
+ if top_level == True:
+ post_call_code += '%sif (local_%s%s)\n' % (indent, prefix, ndo_name)
+ indent = self.incIndent(indent)
+ post_call_code += '%sdelete[] local_%s;\n' % (indent, ndo_name)
+ else:
+ if top_level == True:
+ if (destroy_func == False) or (destroy_array == True): #### LUGMAL This line needs to be skipped for destroy_ndo and not destroy_array
+ pre_call_code += '%s %s = (%s)dev_data->unique_id_mapping[reinterpret_cast<uint64_t &>(%s)];\n' % (indent, ndo_name, ndo_type, ndo_name)
+ else:
+ # Make temp copy of this var with the 'local' removed. It may be better to not pass in 'local_'
+ # as part of the string and explicitly print it
+ fix = str(prefix).strip('local_');
+ pre_call_code += '%s if (%s%s) {\n' % (indent, fix, ndo_name)
+ indent = self.incIndent(indent)
+ pre_call_code += '%s %s%s = (%s)dev_data->unique_id_mapping[reinterpret_cast<const uint64_t &>(%s%s)];\n' % (indent, prefix, ndo_name, ndo_type, fix, ndo_name)
+ indent = self.decIndent(indent)
+ pre_call_code += '%s }\n' % indent
+ return decl_code, pre_call_code, post_call_code
+ #
+ # first_level_param indicates if elements are passed directly into the function else they're below a ptr/struct
+ # create_func means that this is API creates or allocates NDOs
+ # destroy_func indicates that this API destroys or frees NDOs
+ # destroy_array means that the destroy_func operated on an array of NDOs
+ def uniquify_members(self, members, indent, prefix, array_index, create_func, destroy_func, destroy_array, first_level_param):
+ decls = ''
+ pre_code = ''
+ post_code = ''
+ struct_member_dict = dict(self.structMembers)
+ index = 'index%s' % str(array_index)
+ array_index += 1
+ # Process any NDOs in this structure and recurse for any sub-structs in this struct
+ for member in members:
+ # Handle NDOs
+ if self.isHandleTypeNonDispatchable(member.type) == True:
+ count_name = member.len
+ if (count_name is not None):
+ if first_level_param == False:
+ count_name = '%s%s' % (prefix, member.len)
+
+ if (first_level_param == False) or (create_func == False):
+ (tmp_decl, tmp_pre, tmp_post) = self.outputNDOs(member.type, member.name, count_name, prefix, index, indent, destroy_func, destroy_array, first_level_param)
+ decls += tmp_decl
+ pre_code += tmp_pre
+ post_code += tmp_post
+ # Handle Structs that contain NDOs at some level
+ elif member.type in struct_member_dict:
+ # All structs at first level will have an NDO
+ if self.struct_contains_ndo(member.type) == True:
+ struct_info = struct_member_dict[member.type]
+ # Struct Array
+ if member.len is not None:
+ # Update struct prefix
+ if first_level_param == True:
+ new_prefix = 'local_%s' % member.name
+ # Declare safe_VarType for struct
+ decls += '%ssafe_%s *%s = NULL;\n' % (indent, member.type, new_prefix)
+ else:
+ new_prefix = '%s%s' % (prefix, member.name)
+ pre_code += '%s if (%s%s) {\n' % (indent, prefix, member.name)
+ indent = self.incIndent(indent)
+ if first_level_param == True:
+ pre_code += '%s %s = new safe_%s[%s];\n' % (indent, new_prefix, member.type, member.len)
+ pre_code += '%s for (uint32_t %s = 0; %s < %s%s; ++%s) {\n' % (indent, index, index, prefix, member.len, index)
+ indent = self.incIndent(indent)
+ if first_level_param == True:
+ pre_code += '%s %s[%s].initialize(&%s[%s]);\n' % (indent, new_prefix, index, member.name, index)
+ local_prefix = '%s[%s].' % (new_prefix, index)
+ # Process sub-structs in this struct
+ (tmp_decl, tmp_pre, tmp_post) = self.uniquify_members(struct_info, indent, local_prefix, array_index, create_func, destroy_func, destroy_array, False)
+ decls += tmp_decl
+ pre_code += tmp_pre
+ post_code += tmp_post
+ indent = self.decIndent(indent)
+ pre_code += '%s }\n' % indent
+ indent = self.decIndent(indent)
+ pre_code += '%s }\n' % indent
+ if first_level_param == True:
+ post_code += self.cleanUpLocalDeclarations(indent, prefix, member.name, member.len)
+ # Single Struct
+ else:
+ # Update struct prefix
+ if first_level_param == True:
+ new_prefix = 'local_%s->' % member.name
+ decls += '%ssafe_%s *local_%s%s = NULL;\n' % (indent, member.type, prefix, member.name)
+ else:
+ new_prefix = '%s%s->' % (prefix, member.name)
+ # Declare safe_VarType for struct
+ pre_code += '%s if (%s%s) {\n' % (indent, prefix, member.name)
+ indent = self.incIndent(indent)
+ if first_level_param == True:
+ pre_code += '%s local_%s%s = new safe_%s(%s);\n' % (indent, prefix, member.name, member.type, member.name)
+ # Process sub-structs in this struct
+ (tmp_decl, tmp_pre, tmp_post) = self.uniquify_members(struct_info, indent, new_prefix, array_index, create_func, destroy_func, destroy_array, False)
+ decls += tmp_decl
+ pre_code += tmp_pre
+ post_code += tmp_post
+ indent = self.decIndent(indent)
+ pre_code += '%s }\n' % indent
+ if first_level_param == True:
+ post_code += self.cleanUpLocalDeclarations(indent, prefix, member.name, member.len)
+ return decls, pre_code, post_code
+ #
+ # For a particular API, generate the non-dispatchable-object wrapping/unwrapping code
+ def generate_wrapping_code(self, cmd):
+ indent = ' '
+ proto = cmd.find('proto/name')
+ params = cmd.findall('param')
+ if proto.text is not None:
+ cmd_member_dict = dict(self.cmdMembers)
+ cmd_info = cmd_member_dict[proto.text]
+ # Handle ndo create/allocate operations
+ if cmd_info[0].iscreate:
+ create_ndo_code = self.generate_create_ndo_code(indent, proto, params, cmd_info)
+ else:
+ create_ndo_code = ''
+ # Handle ndo destroy/free operations
+ if cmd_info[0].isdestroy:
+ (destroy_array, destroy_ndo_code) = self.generate_destroy_ndo_code(indent, proto, cmd_info)
+ else:
+ destroy_array = False
+ destroy_ndo_code = ''
+ paramdecl = ''
+ param_pre_code = ''
+ param_post_code = ''
+ create_func = True if create_ndo_code else False
+ destroy_func = True if destroy_ndo_code else False
+ (paramdecl, param_pre_code, param_post_code) = self.uniquify_members(cmd_info, indent, '', 0, create_func, destroy_func, destroy_array, True)
+ param_post_code += create_ndo_code
+ if destroy_ndo_code:
+ if destroy_array == True:
+ param_post_code += destroy_ndo_code
+ else:
+ param_pre_code += destroy_ndo_code
+ if param_pre_code:
+ if (not destroy_func) or (destroy_array):
+ param_pre_code = '%s{\n%s%s%s%s}\n' % (' ', indent, self.lock_guard(indent), param_pre_code, indent)
+ return paramdecl, param_pre_code, param_post_code
+ #
+ # Capture command parameter info needed to wrap NDOs as well as handling some boilerplate code
+ def genCmd(self, cmdinfo, cmdname):
+ if cmdname in self.interface_functions:
+ return
+ if cmdname in self.no_autogen_list:
+ decls = self.makeCDecls(cmdinfo.elem)
+ self.appendSection('command', '')
+ self.appendSection('command', '// Declare only')
+ self.appendSection('command', decls[0])
+ self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (cmdname,cmdname[2:]) ]
+ return
+ # Add struct-member type information to command parameter information
+ OutputGenerator.genCmd(self, cmdinfo, cmdname)
+ members = cmdinfo.elem.findall('.//param')
+ # Iterate over members once to get length parameters for arrays
+ lens = set()
+ for member in members:
+ len = self.getLen(member)
+ if len:
+ lens.add(len)
+ struct_member_dict = dict(self.structMembers)
+ # Generate member info
+ membersInfo = []
+ for member in members:
+ # Get type and name of member
+ info = self.getTypeNameTuple(member)
+ type = info[0]
+ name = info[1]
+ cdecl = self.makeCParamDecl(member, 0)
+ # Check for parameter name in lens set
+ iscount = True if name in lens else False
+ len = self.getLen(member)
+ isconst = True if 'const' in cdecl else False
+ ispointer = self.paramIsPointer(member)
+ # Mark param as local if it is an array of NDOs
+ islocal = False;
+ if self.isHandleTypeNonDispatchable(type) == True:
+ if (len is not None) and (isconst == True):
+ islocal = True
+ # Or if it's a struct that contains an NDO
+ elif type in struct_member_dict:
+ if self.struct_contains_ndo(type) == True:
+ islocal = True
+
+ isdestroy = True if True in [destroy_txt in cmdname for destroy_txt in ['Destroy', 'Free']] else False
+ iscreate = True if True in [create_txt in cmdname for create_txt in ['Create', 'Allocate']] else False
+
+ membersInfo.append(self.CommandParam(type=type,
+ name=name,
+ ispointer=ispointer,
+ isconst=isconst,
+ iscount=iscount,
+ len=len,
+ extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None,
+ cdecl=cdecl,
+ islocal=islocal,
+ iscreate=iscreate,
+ isdestroy=isdestroy))
+ self.cmdMembers.append(self.CmdMemberData(name=cmdname, members=membersInfo))
+ # Generate NDO wrapping/unwrapping code for all parameters
+ (api_decls, api_pre, api_post) = self.generate_wrapping_code(cmdinfo.elem)
+ # If API doesn't contain an NDO's, don't fool with it
+ if not api_decls and not api_pre and not api_post:
+ return
+ # Record that the function will be intercepted
+ if (self.featureExtraProtect != None):
+ self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ]
+ self.intercepts += [ ' {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (cmdname,cmdname[2:]) ]
+ if (self.featureExtraProtect != None):
+ self.intercepts += [ '#endif' ]
+ decls = self.makeCDecls(cmdinfo.elem)
+ self.appendSection('command', '')
+ self.appendSection('command', decls[0][:-1])
+ self.appendSection('command', '{')
+ # Setup common to call wrappers, first parameter is always dispatchable
+ dispatchable_type = cmdinfo.elem.find('param/type').text
+ dispatchable_name = cmdinfo.elem.find('param/name').text
+ # Generate local instance/pdev/device data lookup
+ self.appendSection('command', ' layer_data *dev_data = get_my_data_ptr(get_dispatch_key('+dispatchable_name+'), layer_data_map);')
+ # Handle return values, if any
+ resulttype = cmdinfo.elem.find('proto/type')
+ if (resulttype != None and resulttype.text == 'void'):
+ resulttype = None
+ if (resulttype != None):
+ assignresult = resulttype.text + ' result = '
+ else:
+ assignresult = ''
+ # Pre-pend declarations and pre-api-call codegen
+ if api_decls:
+ self.appendSection('command', "\n".join(str(api_decls).rstrip().split("\n")))
+ if api_pre:
+ self.appendSection('command', "\n".join(str(api_pre).rstrip().split("\n")))
+ # Generate the API call itself
+ # Gather the parameter items
+ params = cmdinfo.elem.findall('param/name')
+ # Pull out the text for each of the parameters, separate them by commas in a list
+ paramstext = ', '.join([str(param.text) for param in params])
+ # If any of these paramters has been replaced by a local var, fix up the list
+ cmd_member_dict = dict(self.cmdMembers)
+ params = cmd_member_dict[cmdname]
+ for param in params:
+ if param.islocal == True:
+ if param.ispointer == True:
+ paramstext = paramstext.replace(param.name, '(%s %s*)local_%s' % ('const', param.type, param.name))
+ else:
+ paramstext = paramstext.replace(param.name, '(%s %s)local_%s' % ('const', param.type, param.name))
+ # Use correct dispatch table
+ if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]:
+ API = cmdinfo.elem.attrib.get('name').replace('vk','dev_data->instance_dispatch_table->',1)
+ else:
+ API = cmdinfo.elem.attrib.get('name').replace('vk','dev_data->device_dispatch_table->',1)
+ # Put all this together for the final down-chain call
+ self.appendSection('command', ' ' + assignresult + API + '(' + paramstext + ');')
+ # And add the post-API-call codegen
+ self.appendSection('command', "\n".join(str(api_post).rstrip().split("\n")))
+ # Handle the return result variable, if any
+ if (resulttype != None):
+ self.appendSection('command', ' return result;')
+ self.appendSection('command', '}')