diff options
| author | Mark Lobodzinski <mark@lunarg.com> | 2016-11-18 14:58:57 -0700 |
|---|---|---|
| committer | Mark Lobodzinski <mark@lunarg.com> | 2016-11-21 12:06:12 -0700 |
| commit | 0fcd1a7ef1f62557daa83f390bbaf4ab40465ddd (patch) | |
| tree | fbc04ab17ad75f249de7fcb8dccf0f588f674e85 /scripts | |
| parent | 775df73e1d1a4f216b531e9f11c109fe32266801 (diff) | |
| download | usermoji-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-x | scripts/generator.py | 498 | ||||
| -rw-r--r-- | scripts/lvl_genvk.py | 275 | ||||
| -rw-r--r-- | scripts/parameter_validation_generator.py | 987 | ||||
| -rwxr-xr-x | scripts/reg.py | 813 | ||||
| -rw-r--r-- | scripts/threading_generator.py | 467 | ||||
| -rw-r--r-- | scripts/unique_objects_generator.py | 760 |
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', '}') |
