early-access version 2251

This commit is contained in:
pineappleEA
2021-11-28 11:31:20 +01:00
parent a657cf53a1
commit faf5b13e48
58 changed files with 171227 additions and 121316 deletions

View File

@@ -43,7 +43,10 @@ def enquote(s):
"""Return string argument with surrounding quotes,
for serialization into Python code."""
if s:
return "'{}'".format(s)
if isinstance(s, str):
return "'{}'".format(s)
else:
return s
return None
@@ -119,8 +122,11 @@ class GeneratorOptions:
removeExtensions=None,
emitExtensions=None,
emitSpirv=None,
emitFormats=None,
reparentEnums=True,
sortProcedure=regSortFeatures):
sortProcedure=regSortFeatures,
requireCommandAliases=False,
):
"""Constructor.
Arguments:
@@ -152,6 +158,8 @@ class GeneratorOptions:
to None.
- emitSpirv - regex matching names of extensions and capabilities
to actually emit interfaces for.
- emitFormats - regex matching names of formats to actually emit
interfaces for.
- reparentEnums - move <enum> elements which extend an enumerated
type from <feature> or <extension> elements to the target <enums>
element. This is required for almost all purposes, but the
@@ -217,6 +225,10 @@ class GeneratorOptions:
"""regex matching names of extensions and capabilities
to actually emit interfaces for."""
self.emitFormats = self.emptyRegex(emitFormats)
"""regex matching names of formats
to actually emit interfaces for."""
self.reparentEnums = reparentEnums
"""boolean specifying whether to remove <enum> elements from
<feature> or <extension> when extending an <enums> type."""
@@ -230,6 +242,10 @@ class GeneratorOptions:
self.codeGenerator = False
"""True if this generator makes compilable code"""
self.requireCommandAliases = requireCommandAliases
"""True if alias= attributes of <command> tags are transitively
required."""
def emptyRegex(self, pat):
"""Substitute a regular expression which matches no version
or extension names for None or the empty string."""
@@ -257,6 +273,17 @@ class OutputGenerator:
'basetype': 'basetypes',
}
def breakName(self, name, msg):
"""Break into debugger if this is a special name"""
# List of string names to break on
bad = (
)
if name in bad and True:
print('breakName {}: {}'.format(name, msg))
pdb.set_trace()
def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
"""Constructor
@@ -553,7 +580,7 @@ class OutputGenerator:
# Work around this by chasing the aliases to get the actual value.
while numVal is None:
alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']")
(numVal, strVal) = self.enumToValue(alias, True)
(numVal, strVal) = self.enumToValue(alias, True, bitwidth, True)
decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
if numVal is not None:
@@ -778,7 +805,6 @@ class OutputGenerator:
self.warnFile.flush()
if self.diagFile:
self.diagFile.flush()
self.outFile.flush()
if self.outFile != sys.stdout and self.outFile != sys.stderr:
self.outFile.close()
@@ -887,6 +913,14 @@ class OutputGenerator:
Extend to generate as desired in your derived class."""
return
def genFormat(self, format, formatinfo, alias):
"""Generate interface for a format element.
- formatinfo - FormatInfo
Extend to generate as desired in your derived class."""
return
def makeProtoName(self, name, tail):
"""Turn a `<proto>` `<name>` into C-language prototype
and typedef declarations for that name.
@@ -939,6 +973,9 @@ class OutputGenerator:
# Clear prefix for subsequent iterations
prefix = ''
paramdecl = paramdecl + prefix
if aligncol == 0:
# Squeeze out multiple spaces other than the indentation
paramdecl = indent + ' '.join(paramdecl.split())

View File

@@ -20,6 +20,7 @@ from generator import write
from spirvcapgenerator import SpirvCapabilityOutputGenerator
from hostsyncgenerator import HostSynchronizationOutputGenerator
from pygenerator import PyOutputGenerator
from rubygenerator import RubyOutputGenerator
from reflib import logDiag, logWarn, setLogFile
from reg import Registry
from validitygenerator import ValidityOutputGenerator
@@ -77,6 +78,9 @@ def makeGenOpts(args):
# SPIR-V capabilities / features to emit (list of extensions & capabilities)
emitSpirv = args.emitSpirv
# Vulkan Formats to emit
emitFormats = args.emitFormats
# Features to include (list of features)
features = args.feature
@@ -97,13 +101,14 @@ def makeGenOpts(args):
# Descriptive names for various regexp patterns used to select
# versions and extensions
allSpirv = allFeatures = allExtensions = r'.*'
allFormats = allSpirv = allFeatures = allExtensions = r'.*'
# Turn lists of names/patterns into matching regular expressions
addExtensionsPat = makeREstring(extensions, None)
removeExtensionsPat = makeREstring(removeExtensions, None)
emitExtensionsPat = makeREstring(emitExtensions, allExtensions)
emitSpirvPat = makeREstring(emitSpirv, allSpirv)
emitFormatsPat = makeREstring(emitFormats, allFormats)
featuresPat = makeREstring(features, allFeatures)
# Copyright text prefixing all headers (list of strings).
@@ -182,7 +187,32 @@ def makeGenOpts(args):
reparentEnums = False)
]
# Ruby representation of API information, used by scripts that
# don't need to load the full XML.
genOpts['api.rb'] = [
RubyOutputGenerator,
DocGeneratorOptions(
conventions = conventions,
filename = 'api.rb',
directory = directory,
genpath = None,
apiname = 'vulkan',
profile = None,
versions = featuresPat,
emitversions = featuresPat,
defaultExtensions = None,
addExtensions = addExtensionsPat,
removeExtensions = removeExtensionsPat,
emitExtensions = emitExtensionsPat,
reparentEnums = False)
]
# API validity files for spec
#
# requireCommandAliases is set to True because we need validity files
# for the command something is promoted to even when the promoted-to
# feature is not included. This avoids wordy includes of validity files.
genOpts['validinc'] = [
ValidityOutputGenerator,
DocGeneratorOptions(
@@ -197,7 +227,9 @@ def makeGenOpts(args):
defaultExtensions = None,
addExtensions = addExtensionsPat,
removeExtensions = removeExtensionsPat,
emitExtensions = emitExtensionsPat)
emitExtensions = emitExtensionsPat,
requireCommandAliases = True,
)
]
# API host sync table files for spec
@@ -307,6 +339,7 @@ def makeGenOpts(args):
'VK_EXT_video_decode_h264',
'VK_EXT_video_decode_h265',
'VK_EXT_video_encode_h264',
'VK_EXT_video_encode_h265',
]
betaSuppressExtensions = []
@@ -314,10 +347,13 @@ def makeGenOpts(args):
platforms = [
[ 'vulkan_android.h', [ 'VK_KHR_android_surface',
'VK_ANDROID_external_memory_android_hardware_buffer'
], commonSuppressExtensions ],
], commonSuppressExtensions +
[ 'VK_KHR_format_feature_flags2',
] ],
[ 'vulkan_fuchsia.h', [ 'VK_FUCHSIA_imagepipe_surface',
'VK_FUCHSIA_external_memory',
'VK_FUCHSIA_external_semaphore' ], commonSuppressExtensions ],
'VK_FUCHSIA_external_semaphore',
'VK_FUCHSIA_buffer_collection' ], commonSuppressExtensions ],
[ 'vulkan_ggp.h', [ 'VK_GGP_stream_descriptor_surface',
'VK_GGP_frame_token' ], commonSuppressExtensions ],
[ 'vulkan_ios.h', [ 'VK_MVK_ios_surface' ], commonSuppressExtensions ],
@@ -546,6 +582,8 @@ def genTarget(args):
logDiag('* options.addExtensions =', options.addExtensions)
logDiag('* options.removeExtensions =', options.removeExtensions)
logDiag('* options.emitExtensions =', options.emitExtensions)
logDiag('* options.emitSpirv =', options.emitSpirv)
logDiag('* options.emitFormats =', options.emitFormats)
gen = createGenerator(errFile=errWarn,
warnFile=errWarn,
@@ -578,6 +616,9 @@ if __name__ == '__main__':
parser.add_argument('-emitSpirv', action='append',
default=[],
help='Specify a SPIR-V extension or capability to emit in targets')
parser.add_argument('-emitFormats', action='append',
default=[],
help='Specify Vulkan Formats to emit in targets')
parser.add_argument('-feature', action='append',
default=[],
help='Specify a core API feature name or names to add to targets')
@@ -601,7 +642,7 @@ if __name__ == '__main__':
parser.add_argument('-time', action='store_true',
help='Enable timing')
parser.add_argument('-validate', action='store_true',
help='Enable XML group validation')
help='Validate the registry properties and exit')
parser.add_argument('-genpath', action='store', default='gen',
help='Path to generated files')
parser.add_argument('-o', action='store', dest='directory',
@@ -635,8 +676,14 @@ if __name__ == '__main__':
else:
diag = None
# Create the API generator & generator options
(gen, options) = genTarget(args)
if args.time:
# Log diagnostics and warnings
setLogFile(setDiag = True, setWarn = True, filename = '-')
(gen, options) = (None, None)
if not args.validate:
# Create the API generator & generator options
(gen, options) = genTarget(args)
# Create the registry object with the specified generator and generator
# options. The options are set before XML loading as they may affect it.
@@ -653,7 +700,8 @@ if __name__ == '__main__':
endTimer(args.time, '* Time to parse ElementTree =')
if args.validate:
reg.validateGroups()
success = reg.validateRegistry()
sys.exit(0 if success else 1)
if args.dump:
logDiag('* Dumping registry to regdump.txt')

View File

@@ -260,6 +260,12 @@ class SpirvInfo(BaseInfo):
def __init__(self, elem):
BaseInfo.__init__(self, elem)
class FormatInfo(BaseInfo):
"""Registry information about an API <format>."""
def __init__(self, elem):
BaseInfo.__init__(self, elem)
class Registry:
"""Object representing an API registry, loaded from an XML file."""
@@ -311,6 +317,9 @@ class Registry:
self.spirvcapdict = {}
"dictionary of FeatureInfo objects for `<spirvcapability>` elements keyed by spirv capability name"
self.formatsdict = {}
"dictionary of FeatureInfo objects for `<format>` elements keyed by VkFormat name"
self.emitFeatures = False
"""True to actually emit features for a version / extension,
or False to just treat them as emitted"""
@@ -356,10 +365,10 @@ class Registry:
Intended for internal use only.
- elem - `<type>`/`<enums>`/`<enum>`/`<command>`/`<feature>`/`<extension>`/`<spirvextension>`/`<spirvcapability>` Element
- elem - `<type>`/`<enums>`/`<enum>`/`<command>`/`<feature>`/`<extension>`/`<spirvextension>`/`<spirvcapability>`/`<format>` Element
- info - corresponding {Type|Group|Enum|Cmd|Feature|Spirv}Info object
- infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' / 'spirvextension' / 'spirvcapability'
- dictionary - self.{type|group|enum|cmd|api|ext|spirvext|spirvcap}dict
- infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' / 'spirvextension' / 'spirvcapability' / 'format'
- dictionary - self.{type|group|enum|cmd|api|ext|format|spirvext|spirvcap}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
@@ -612,6 +621,10 @@ class Registry:
spirvInfo = SpirvInfo(spirv)
self.addElementInfo(spirv, spirvInfo, 'spirvcapability', self.spirvcapdict)
for format in self.reg.findall('formats/format'):
formatInfo = FormatInfo(format)
self.addElementInfo(format, formatInfo, 'format', self.formatsdict)
def dumpReg(self, maxlen=120, filehandle=sys.stdout):
"""Dump all the dictionaries constructed from the Registry object.
@@ -651,6 +664,10 @@ class Registry:
for key in self.spirvcapdict:
write(' SPIR-V Capability', key, '->',
etree.tostring(self.spirvcapdict[key].elem)[0:maxlen], file=filehandle)
write('// VkFormat', file=filehandle)
for key in self.formatsdict:
write(' VkFormat', key, '->',
etree.tostring(self.formatsdict[key].elem)[0:maxlen], file=filehandle)
def markTypeRequired(self, typename, required):
"""Require (along with its dependencies) or remove (but not its dependencies) a type.
@@ -762,12 +779,23 @@ class Registry:
cmd = self.lookupElementInfo(cmdname, self.cmddict)
if cmd is not None:
cmd.required = required
# Tag command dependencies in 'alias' attribute as required
depname = cmd.elem.get('alias')
if depname:
self.gen.logMsg('diag', 'Generating dependent command',
depname, 'for alias', cmdname)
self.markCmdRequired(depname, required)
#
# This is usually not done, because command 'aliases' are not
# actual C language aliases like type and enum aliases. Instead
# they are just duplicates of the function signature of the
# alias. This means that there is no dependency of a command
# alias on what it aliases. One exception is validity includes,
# where the spec markup needs the promoted-to validity include
# even if only the promoted-from command is being built.
if self.genOpts.requireCommandAliases:
depname = cmd.elem.get('alias')
if depname:
self.gen.logMsg('diag', 'Generating dependent command',
depname, 'for alias', cmdname)
self.markCmdRequired(depname, 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.
@@ -842,8 +870,13 @@ class Registry:
- require - `<require>` block from the registry
- tag - tag to look for in the require block"""
if alias and require.findall(tag + "[@name='" + alias + "']"):
return True
# For the time being, the code below is bypassed. It has the effect
# of excluding "spelling aliases" created to comply with the style
# guide, but this leaves references out of the specification and
# causes broken internal links.
#
# if alias and require.findall(tag + "[@name='" + alias + "']"):
# return True
return False
@@ -902,6 +935,9 @@ class Registry:
if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]:
self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = []
self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename)
else:
self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename))
for enumElem in require.findall('enum'):
enumname = enumElem.get('name')
@@ -916,16 +952,18 @@ class Registry:
if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]:
self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = []
self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname)
else:
self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename))
for cmdElem in require.findall('command'):
# Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
alias = self.getAlias(cmdElem, self.cmddict)
if not self.checkForCorrectionAliases(alias, require, 'command'):
if not required_key in self.gen.featureDictionary[featurename]['command']:
self.gen.featureDictionary[featurename]['command'][required_key] = []
self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name'))
else:
self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename))
def requireAndRemoveFeatures(self, interface, featurename, api, profile):
"""Process `<require>` and `<remove>` tags for a `<version>` or `<extension>`.
@@ -935,10 +973,12 @@ class Registry:
- featurename - name of the feature
- api - string specifying API name being generated
- profile - string specifying API profile being generated"""
# <require> marks things that are required by this version/profile
for feature in interface.findall('require'):
if matchAPIProfile(api, profile, feature):
self.markRequired(featurename, feature, True)
# <remove> marks things that are removed by this version/profile
for feature in interface.findall('remove'):
if matchAPIProfile(api, profile, feature):
@@ -1167,6 +1207,45 @@ class Registry:
genProc = self.gen.genSpirv
genProc(spirv, name, alias)
def stripUnsupportedAPIs(self, dictionary, attribute, supportedDictionary):
"""Strip unsupported APIs from attributes of APIs.
dictionary - *Info dictionary of APIs to be updated
attribute - attribute name to look for in each API
supportedDictionary - dictionary in which to look for supported
API elements in the attribute"""
for key in dictionary:
eleminfo = dictionary[key]
attribstring = eleminfo.elem.get(attribute)
if attribstring is not None:
apis = []
stripped = False
for api in attribstring.split(','):
##print('Checking API {} referenced by {}'.format(api, key))
if supportedDictionary[api].required:
apis.append(api)
else:
stripped = True
##print('\t**STRIPPING API {} from {}'.format(api, key))
# Update the attribute after stripping stuff.
# Could sort apis before joining, but it's not a clear win
if stripped:
eleminfo.elem.set(attribute, ','.join(apis))
def generateFormat(self, format, dictionary):
if format is None:
self.gen.logMsg('diag', 'No entry found for format element',
'returning!')
return
name = format.elem.get('name')
# No known alias for VkFormat elements
alias = None
if format.emit:
genProc = self.gen.genFormat
genProc(format, name, alias)
def apiGen(self):
"""Generate interface for specified versions using the current
generator and generator options"""
@@ -1177,8 +1256,13 @@ class Registry:
'profile:', self.genOpts.profile)
self.gen.logMsg('diag', '*******************************************')
# Reset required/declared flags for all features
self.apiReset()
# Could reset required/declared flags for all features here.
# This has been removed as never used. The initial motivation was
# the idea of calling apiGen() repeatedly for different targets, but
# this has never been done. The 20% or so build-time speedup that
# might result is not worth the effort to make it actually work.
#
#@@ self.apiReset()
# Compile regexps used to select versions & extensions
regVersions = re.compile(self.genOpts.versions)
@@ -1187,6 +1271,7 @@ class Registry:
regRemoveExtensions = re.compile(self.genOpts.removeExtensions)
regEmitExtensions = re.compile(self.genOpts.emitExtensions)
regEmitSpirv = re.compile(self.genOpts.emitSpirv)
regEmitFormats = re.compile(self.genOpts.emitFormats)
# Get all matching API feature names & add to list of FeatureInfo
# Note we used to select on feature version attributes, not names.
@@ -1295,6 +1380,12 @@ class Registry:
si.emit = (regEmitSpirv.match(key) is not None)
spirvcaps.append(si)
formats = []
for key in self.formatsdict:
si = self.formatsdict[key]
si.emit = (regEmitFormats.match(key) is not None)
formats.append(si)
# Sort the features list, if a sort procedure is defined
if self.genOpts.sortProcedure:
self.genOpts.sortProcedure(features)
@@ -1316,6 +1407,21 @@ class Registry:
self.requireAndRemoveFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile)
self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile)
# Now, strip references to APIs that are not required.
# At present such references may occur in:
# Structs in <type category="struct"> 'structextends' attributes
# Enums in <command> 'successcodes' and 'errorcodes' attributes
self.stripUnsupportedAPIs(self.typedict, 'structextends', self.typedict)
self.stripUnsupportedAPIs(self.cmddict, 'successcodes', self.enumdict)
self.stripUnsupportedAPIs(self.cmddict, 'errorcodes', self.enumdict)
# @@May need to strip <spirvcapability> / <spirvextension> <enable>
# tags of these forms:
# <enable version="VK_API_VERSION_1_0"/>
# <enable struct="VkPhysicalDeviceFeatures" feature="geometryShader" requires="VK_VERSION_1_0"/>
# <enable extension="VK_KHR_shader_draw_parameters"/>
# <enable property="VkPhysicalDeviceVulkan12Properties" member="shaderDenormPreserveFloat16" value="VK_TRUE" requires="VK_VERSION_1_2,VK_KHR_shader_float_controls"/>
# Pass 2: loop over specified API versions and extensions printing
# declarations for required things which haven't already been
# generated.
@@ -1338,6 +1444,8 @@ class Registry:
self.generateSpirv(s, self.spirvextdict)
for s in spirvcaps:
self.generateSpirv(s, self.spirvcapdict)
for s in formats:
self.generateFormat(s, self.formatsdict)
self.gen.endFile()
def apiReset(self):
@@ -1353,39 +1461,44 @@ class Registry:
for cmd in self.apidict:
self.apidict[cmd].resetState()
def validateGroups(self):
"""Validate `group=` attributes on `<param>` and `<proto>` tags.
def __validateStructLimittypes(self, struct):
"""Validate 'limittype' attributes for a single struct."""
limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid'])
badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[]))
validLimittypes = { 'min', 'max', 'bitmask', 'range', 'struct', 'noauto' }
for member in struct.getMembers():
memberName = member.findtext('name')
if memberName in ['sType', 'pNext']:
continue
limittype = member.get('limittype')
if not limittype:
badFields[struct.elem.get('name')].missing.append(memberName)
elif limittype == 'struct':
typeName = member.findtext('type')
memberType = self.typedict[typeName]
badFields.update(self.__validateStructLimittypes(memberType))
elif limittype not in validLimittypes:
badFields[struct.elem.get('name')].invalid.append(memberName)
return badFields
Check that `group=` attributes match actual groups"""
# 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
group = proto.get('group')
if group is not None and group not in self.groupdict:
# self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group)
if group not in badGroup:
badGroup[group] = 1
else:
badGroup[group] = badGroup[group] + 1
def __validateLimittype(self):
"""Validate 'limittype' attributes."""
badFields = self.__validateStructLimittypes(self.typedict['VkPhysicalDeviceProperties2'])
for featStructName in self.validextensionstructs['VkPhysicalDeviceProperties2']:
featStruct = self.typedict[featStructName]
badFields.update(self.__validateStructLimittypes(featStruct))
for param in cmd.findall('param'):
pname = param.find('name')
if pname is not None:
pname = pname.text
else:
pname = param.get('name')
group = param.get('group')
if group is not None and group not in self.groupdict:
# self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group)
if group not in badGroup:
badGroup[group] = 1
else:
badGroup[group] = badGroup[group] + 1
if badFields:
self.gen.logMsg('diag', 'SUMMARY OF FIELDS WITH INCORRECT LIMITTYPES')
for key in sorted(badFields.keys()):
diags = badFields[key]
if diags.missing:
self.gen.logMsg('diag', ' ', key, 'missing limittype:', ', '.join(badFields[key].missing))
if diags.invalid:
self.gen.logMsg('diag', ' ', key, 'invalid limittype:', ', '.join(badFields[key].invalid))
return False
return True
if badGroup:
self.gen.logMsg('diag', 'SUMMARY OF UNRECOGNIZED GROUPS')
for key in sorted(badGroup.keys()):
self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times')
def validateRegistry(self):
"""Validate properties of the registry."""
return self.__validateLimittype()

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ SPECIAL_WORDS = set((
'Int64', # VkPhysicalDeviceShaderAtomicInt64FeaturesKHR
'Int8', # VkPhysicalDeviceShaderFloat16Int8FeaturesKHR
'MacOS', # VkMacOSSurfaceCreateInfoMVK
'RGBA10X6', # VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT
'Uint8', # VkPhysicalDeviceIndexTypeUint8FeaturesEXT
'Win32', # VkWin32SurfaceCreateInfoKHR
))