blob: 0de982d59731171e156104193476460c817e2824 [file] [log] [blame]
#!/usr/bin/python3
#
# Copyright (c) 2019 Collabora, Ltd.
#
# SPDX-License-Identifier: Apache-2.0
#
# Author(s): Ryan Pavlik <[email protected]>
#
# Purpose: This script checks some "business logic" in the XML registry.
import argparse
import re
import sys
from pathlib import Path
from collections import defaultdict, deque, namedtuple
from check_spec_links import VulkanEntityDatabase as OrigEntityDatabase
from reg import Registry
from spec_tools.consistency_tools import XMLChecker
from spec_tools.util import findNamedElem, getElemName, getElemType
from apiconventions import APIConventions
from parse_dependency import dependencyNames
# These are extensions which do not follow the usual naming conventions,
# specifying the alternate convention they follow
EXTENSION_ENUM_NAME_SPELLING_CHANGE = {
'VK_EXT_swapchain_colorspace': 'VK_EXT_SWAPCHAIN_COLOR_SPACE',
}
# These are extensions whose names *look* like they end in version numbers,
# but do not
EXTENSION_NAME_VERSION_EXCEPTIONS = (
'VK_AMD_gpu_shader_int16',
'VK_EXT_index_type_uint8',
'VK_EXT_shader_image_atomic_int64',
'VK_KHR_video_decode_h264',
'VK_KHR_video_decode_h265',
'VK_EXT_video_encode_h264',
'VK_EXT_video_encode_h265',
'VK_KHR_external_fence_win32',
'VK_KHR_external_memory_win32',
'VK_KHR_external_semaphore_win32',
'VK_KHR_shader_atomic_int64',
'VK_KHR_shader_float16_int8',
'VK_KHR_spirv_1_4',
'VK_NV_external_memory_win32',
'VK_RESERVED_do_not_use_146',
'VK_RESERVED_do_not_use_94',
)
# These are APIs which can be required by an extension despite not having
# suffixes matching the vendor ID of that extension.
# Most are external types.
# We could make this an (extension name, api name) set to be more specific.
EXTENSION_API_NAME_EXCEPTIONS = {
'AHardwareBuffer',
'ANativeWindow',
'CAMetalLayer',
'IOSurfaceRef',
'MTLBuffer_id',
'MTLCommandQueue_id',
'MTLDevice_id',
'MTLSharedEvent_id',
'MTLTexture_id',
'VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE',
'VkFlags64',
'VkPipelineCacheCreateFlagBits',
'VkPipelineColorBlendStateCreateFlagBits',
'VkPipelineDepthStencilStateCreateFlagBits',
'VkPipelineLayoutCreateFlagBits',
}
# These are APIs which contain _RESERVED_ intentionally
EXTENSION_NAME_RESERVED_EXCEPTIONS = {
'VK_STRUCTURE_TYPE_PRIVATE_VENDOR_INFO_RESERVED_OFFSET_0_NV'
}
# Exceptions to pointer parameter naming rules
# Keyed by (entity name, type, name).
CHECK_PARAM_POINTER_NAME_EXCEPTIONS = {
('vkGetDrmDisplayEXT', 'VkDisplayKHR', 'display') : None,
}
# Exceptions to pNext member requiring an optional attribute
CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS = (
'VkVideoEncodeInfoKHR',
'VkVideoEncodeRateControlLayerInfoKHR',
)
# Exceptions to VK_INCOMPLETE being required for, and only applicable to, array
# enumeration functions
CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS = (
'vkGetDeviceFaultInfoEXT',
'vkEnumerateDeviceLayerProperties',
)
# Exceptions to unknown structure type constants.
# This is most likely an error in this script, not the XML.
# It does not understand Vulkan SC (alternate 'api') types.
CHECK_TYPE_STYPE_EXCEPTIONS = (
'VK_STRUCTURE_TYPE_PERFORMANCE_QUERY_RESERVATION_INFO_KHR',
'VK_STRUCTURE_TYPE_PIPELINE_POOL_SIZE',
'VK_STRUCTURE_TYPE_FAULT_DATA',
'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_SC_1_0_FEATURES',
'VK_STRUCTURE_TYPE_DEVICE_OBJECT_RESERVATION_CREATE_INFO',
'VK_STRUCTURE_TYPE_PIPELINE_OFFLINE_CREATE_INFO',
'VK_STRUCTURE_TYPE_FAULT_CALLBACK_INFO',
'VK_STRUCTURE_TYPE_COMMAND_POOL_MEMORY_RESERVATION_CREATE_INFO',
'VK_STRUCTURE_TYPE_DEVICE_SEMAPHORE_SCI_SYNC_POOL_RESERVATION_CREATE_INFO_NV',
'VK_STRUCTURE_TYPE_COMMAND_POOL_MEMORY_CONSUMPTION',
'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_SC_1_0_PROPERTIES',
)
def get_extension_commands(reg):
extension_cmds = set()
for ext in reg.extensions:
for cmd in ext.findall('./require/command[@name]'):
extension_cmds.add(cmd.get('name'))
return extension_cmds
def get_enum_value_names(reg, enum_type):
names = set()
result_elem = reg.groupdict[enum_type].elem
for val in result_elem.findall('./enum[@name]'):
names.add(val.get('name'))
return names
# Regular expression matching an extension name ending in a (possible) version number
EXTNAME_RE = re.compile(r'(?P<base>(\w+[A-Za-z]))(?P<version>\d+)')
DESTROY_PREFIX = 'vkDestroy'
TYPEENUM = 'VkStructureType'
SPECIFICATION_DIR = Path(__file__).parent.parent
REVISION_RE = re.compile(r' *[*] Revision (?P<num>[1-9][0-9]*),.*')
def get_extension_source(extname):
fn = f'{extname}.adoc'
return str(SPECIFICATION_DIR / 'appendices' / fn)
class EntityDatabase(OrigEntityDatabase):
def __init__(self, args):
"""Retain command-line arguments for later use in makeRegistry"""
self.args = args
super().__init__()
# Override base class method to not exclude 'disabled' extensions
def getExclusionSet(self):
"""Return a set of "support=" attribute strings that should not be included in the database.
Called only during construction."""
return set(())
def makeRegistry(self):
try:
import lxml.etree as etree
HAS_LXML = True
except ImportError:
HAS_LXML = False
if not HAS_LXML:
return super().makeRegistry()
if len(self.args.files) > 0:
registryFile = self.args.files[0]
else:
registryFile = str(SPECIFICATION_DIR / 'xml/vk.xml')
registry = Registry()
registry.filename = registryFile
registry.loadElementTree(etree.parse(registryFile))
return registry
class Checker(XMLChecker):
def __init__(self, args):
manual_types_to_codes = {
# These are hard-coded "manual" return codes:
# the codes of the value (string, list, or tuple)
# are available for a command if-and-only-if
# the key type is passed as an input.
'VkFormat': 'VK_ERROR_FORMAT_NOT_SUPPORTED'
}
forward_only = {
# Like the above, but these are only valid in the
# "type implies return code" direction
}
reverse_only = {
# like the above, but these are only valid in the
# "return code implies type or its descendant" direction
# "XrDuration": "XR_TIMEOUT_EXPIRED"
}
# Some return codes are related in that only one of a set
# may be returned by a command
# (eg. XR_ERROR_SESSION_RUNNING and XR_ERROR_SESSION_NOT_RUNNING)
self.exclusive_return_code_sets = tuple(
# set(("XR_ERROR_SESSION_NOT_RUNNING", "XR_ERROR_SESSION_RUNNING")),
)
# This is used to report collisions.
conventions = APIConventions()
db = EntityDatabase(args)
self.extension_cmds = get_extension_commands(db.registry)
self.return_codes = get_enum_value_names(db.registry, 'VkResult')
self.structure_types = get_enum_value_names(db.registry, TYPEENUM)
# Dict of entity name to a list of messages to suppress. (Exclude any context data and "Warning:"/"Error:")
# Keys are entity names, values are tuples or lists of message text to suppress.
suppressions = {}
# Structures explicitly allowed to have 'limittype' attributes
self.allowedStructs = set((
'VkFormatProperties',
'VkFormatProperties2',
'VkPhysicalDeviceProperties',
'VkPhysicalDeviceProperties2',
'VkPhysicalDeviceLimits',
'VkQueueFamilyProperties',
'VkQueueFamilyProperties2',
'VkSparseImageFormatProperties',
'VkSparseImageFormatProperties2',
))
# Substructures of allowed structures. This can be found by looking
# at tags, but there are so few cases that it is hardwired for now.
self.nestedStructs = set((
'VkPhysicalDeviceLimits',
'VkPhysicalDeviceSparseProperties',
'VkPhysicalDeviceProperties',
'VkQueueFamilyProperties',
'VkSparseImageFormatProperties',
))
# Structures all of whose (non pNext/sType) members are required to
# have 'limittype' attributes, as are their descendants
self.requiredStructs = set((
'VkPhysicalDeviceProperties',
'VkPhysicalDeviceProperties2',
'VkPhysicalDeviceLimits',
'VkSparseImageFormatProperties',
'VkSparseImageFormatProperties2',
))
# Structures which have already have their limittype attributes validated
self.validatedLimittype = set()
# Initialize superclass
super().__init__(entity_db=db, conventions=conventions,
manual_types_to_codes=manual_types_to_codes,
forward_only_types_to_codes=forward_only,
reverse_only_types_to_codes=reverse_only,
suppressions=suppressions,
display_warnings=args.warn)
def check(self):
"""Extends base class behavior with additional checks"""
# This test is not run on a per-structure basis, but loops over
# specific structures
self.check_type_required_limittype()
super().check()
def check_command(self, name, info):
"""Extends base class behavior with additional checks"""
if name[0:5] == 'vkCmd':
if info.elem.get('tasks') is None:
self.record_error(f'{name} is a vkCmd* command, but is missing a "tasks" attribute')
super().check_command(name, info)
def check_command_return_codes_basic(self, name, info,
successcodes, errorcodes):
"""Check a command's return codes for consistency.
Called on every command."""
# Check that all extension commands can return the code associated
# with trying to use an extension that was not enabled.
# if name in self.extension_cmds and UNSUPPORTED not in errorcodes:
# self.record_error('Missing expected return code',
# UNSUPPORTED,
# 'implied due to being an extension command')
codes = successcodes.union(errorcodes)
# Check that all return codes are recognized.
unrecognized = codes - self.return_codes
if unrecognized:
self.record_error('Unrecognized return code(s):',
unrecognized)
elem = info.elem
params = [(getElemName(elt), elt) for elt in elem.findall('param')]
def is_count_output(name, elt):
# Must end with Count or Size,
# not be const,
# and be a pointer (detected by naming convention)
return (name.endswith('Count') or name.endswith('Size')) \
and (elt.tail is None or 'const' not in elt.tail) \
and (name.startswith('p'))
countParams = [elt
for name, elt in params
if is_count_output(name, elt)]
if countParams:
assert(len(countParams) == 1)
if 'VK_INCOMPLETE' not in successcodes:
message = "Apparent enumeration of an array without VK_INCOMPLETE in successcodes for command {}.".format(name)
if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS:
self.record_warning('(Allowed exception)', message)
else:
self.record_error(message)
elif 'VK_INCOMPLETE' in successcodes:
message = "VK_INCOMPLETE in successcodes of command {} that is apparently not an array enumeration.".format(name)
if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS:
self.record_warning('(Allowed exception)', message)
else:
self.record_error(message)
def check_param(self, param):
"""Check a member of a struct or a param of a function.
Called from check_params."""
super().check_param(param)
if not self.is_api_type(param):
return
param_text = ''.join(param.itertext())
param_name = getElemName(param)
# Make sure the number of leading 'p' matches the pointer count.
pointercount = param.find('type').tail
if pointercount:
pointercount = pointercount.count('*')
if pointercount:
prefix = 'p' * pointercount
if not param_name.startswith(prefix):
param_type = param.find('type').text
message = "Apparently incorrect pointer-related name prefix for {} - expected it to start with '{}'".format(
param_text, prefix)
if (self.entity, param_type, param_name) in CHECK_PARAM_POINTER_NAME_EXCEPTIONS:
self.record_warning('(Allowed exception)', message, elem=param)
else:
self.record_error(message, elem=param)
# Make sure no members have optional="false" attributes
optional = param.get('optional')
if optional == 'false':
message = f'{self.entity}.{param_name} member has disallowed \'optional="false"\' attribute (remove this attribute)'
self.record_error(message, elem=param)
# Make sure pNext members have optional="true" attributes
if param_name == self.conventions.nextpointer_member_name:
optional = param.get('optional')
if optional is None or optional != 'true':
message = f'{self.entity}.pNext member is missing \'optional="true"\' attribute'
if self.entity in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS:
self.record_warning('(Allowed exception)', message, elem=param)
else:
self.record_error(message, elem=param)
def check_type_stype(self, name, info, type_elts):
"""Check a struct type's sType member"""
if len(type_elts) > 1:
self.record_error(
'Have more than one member of type', TYPEENUM)
else:
type_elt = type_elts[0]
val = type_elt.get('values')
if val and val not in self.structure_types:
message = f'{self.entity} has unknown structure type constant {val}'
if val in CHECK_TYPE_STYPE_EXCEPTIONS:
self.record_warning('(Allowed exception)', message)
else:
self.record_error(message)
def check_type_pnext(self, name, info):
"""Check a struct type's pNext member, if present"""
next_name = self.conventions.nextpointer_member_name
next_member = findNamedElem(info.elem.findall('member'), next_name)
if next_member is not None:
# Ensure that the 'optional' attribute is set to 'true'
optional = next_member.get('optional')
if optional is None or optional != 'true':
message = f'{name}.{next_name} member is missing \'optional="true"\' attribute'
if name in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS:
self.record_warning('(Allowed exception)', message)
else:
self.record_error(message)
def __isLimittypeStruct(self, name, info, allowedStructs):
"""Check if a type element is a structure allowed to have 'limittype' attributes
name - name of a structure
info - corresponding TypeInfo object
allowedStructs - set of struct names explicitly allowed"""
# Is this an explicitly allowed struct?
if name in allowedStructs:
return True
# Is this a struct extending an explicitly allowed struct?
extends = info.elem.get('structextends')
if extends is not None:
# See if any name in the structextends attribute is an allowed
# struct
if len(set(extends.split(',')) & allowedStructs) > 0:
return True
return False
def __validateStructLimittypes(self, name, info, requiredLimittype):
"""Validate 'limittype' attributes for a single struct.
info - TypeInfo for a struct <type>
requiredLimittype - True if members *must* have a limittype"""
# Do not re-check structures
if name in self.validatedLimittype:
return {}
self.validatedLimittype.add(name)
limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid'])
badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[]))
validLimittypes = { 'min', 'max', 'pot', 'mul', 'bits', 'bitmask', 'range', 'struct', 'exact', 'noauto' }
for member in info.getMembers():
memberName = member.findtext('name')
if memberName in ['sType', 'pNext']:
continue
limittype = member.get('limittype')
if limittype is None:
# Do not tag this as missing if it is not required
if requiredLimittype:
badFields[info.elem.get('name')].missing.append(memberName)
elif limittype == 'struct':
typeName = member.findtext('type')
memberType = self.reg.typedict[typeName]
badFields.update(self.__validateStructLimittypes(typeName, memberType, requiredLimittype))
else:
for value in limittype.split(','):
if value not in validLimittypes:
badFields[info.elem.get('name')].invalid.append(memberName)
return badFields
def check_type_disallowed_limittype(self, name, info):
"""Check if a struct type's members cannot have the 'limittype' attribute"""
# If not allowed to have limittypes, verify this for each member
if not self.__isLimittypeStruct(name, info, self.allowedStructs.union(self.nestedStructs)):
for member in info.getMembers():
if member.get('limittype') is not None:
memname = member.findtext('name')
self.record_error(f'{name} member {memname} has disallowed limittype attribute')
def check_type_optional_value(self, name, info):
"""Check if a struct type's members have disallowed 'optional' attribute values"""
for member in info.getMembers():
# Make sure no members have optional="false" attributes
optional = member.get('optional')
if optional == 'false':
memname = member.findtext('name')
message = f'{name} member {memname} has disallowed \'optional="false"\' attribute (remove this attribute)'
self.record_error(message, elem=member)
def check_type_required_limittype(self):
"""Check struct type members which must have the 'limittype' attribute
Called from check."""
for name in self.allowedStructs:
# Assume that only extending structs of structs explicitly
# requiring limittypes also require them
requiredLimittype = (name in self.requiredStructs)
info = self.reg.typedict[name]
self.set_error_context(entity=name, elem=info.elem)
badFields = self.__validateStructLimittypes(name, info, requiredLimittype)
for extendingStructName in self.reg.validextensionstructs[name]:
extendingStruct = self.reg.typedict[extendingStructName]
badFields.update(self.__validateStructLimittypes(extendingStructName, extendingStruct, requiredLimittype))
if badFields:
for key in sorted(badFields.keys()):
diags = badFields[key]
if diags.missing:
self.record_error(f'{name} missing limittype for members {", ".join(badFields[key].missing)}')
if diags.invalid:
self.record_error(f'{name} has invalid limittype for members {", ".join(badFields[key].invalid)}')
def check_type(self, name, info, category):
"""Check a type's XML data for consistency.
Called from check."""
if category == 'struct':
type_elts = [elt
for elt in info.elem.findall('member')
if getElemType(elt) == TYPEENUM]
if type_elts:
self.check_type_stype(name, info, type_elts)
self.check_type_pnext(name, info)
# Check for disallowed limittypes on all structures
self.check_type_disallowed_limittype(name, info)
# Check for disallowed 'optional' values
self.check_type_optional_value(name, info)
elif category == 'bitmask':
if 'Flags' in name:
expected_require = name.replace('Flags', 'FlagBits')
require = info.elem.get('require')
if require is not None and expected_require != require:
self.record_error('Unexpected require attribute value:',
'got', require,
'but expected', expected_require)
super().check_type(name, info, category)
def check_suffixes(self, name, info, supported, name_exceptions):
"""Check suffixes of new APIs required by an extension, which should
match the author ID of the extension.
Called from check_extension.
name - extension name
info - extdict entry for name
supported - True if extension supported by API being checked
name_exceptions - set of API names to not check, in addition to
the global exception list."""
def has_suffix(apiname, author):
return apiname[-len(author):] == author
def has_any_suffixes(apiname, authors):
for author in authors:
if has_suffix(apiname, author):
return True
return False
def check_names(elems, author, alt_authors, name_exceptions):
"""Check names in a list of elements for consistency
elems - list of elements to check
author - author ID of the <extension> tag
alt_authors - set of other allowed author IDs
name_exceptions - additional set of allowed exceptions"""
for elem in elems:
apiname = elem.get('name', 'NO NAME ATTRIBUTE')
suffix = apiname[-len(author):]
if (not has_suffix(apiname, author) and
apiname not in EXTENSION_API_NAME_EXCEPTIONS and
apiname not in name_exceptions):
msg = f'Extension {name} <{elem.tag}> {apiname} does not have expected suffix {author}'
# Explicit 'aliased' deprecations not matching the
# naming rules are allowed, but warned.
if has_any_suffixes(apiname, alt_authors):
self.record_warning('Allowed alternate author ID:', msg)
elif not supported:
self.record_warning('Allowed inconsistency for disabled extension:', msg)
elif elem.get('deprecated') == 'aliased':
self.record_warning('Allowed aliasing deprecation:', msg)
else:
msg += '\n\
This may be due to an extension interaction not having the correct <require depends="">\n\
Other exceptions can be added to xml_consistency.py:EXTENSION_API_NAME_EXCEPTIONS'
self.record_error(msg)
elem = info.elem
self.set_error_context(entity=name, elem=elem)
# Extract author ID from the extension name.
author = name.split('_')[1]
# Loop over each <require> tag checking the API name suffixes in
# that tag for consistency.
# Names in tags whose 'depends' attribute includes extensions with
# different author IDs may be suffixed with those IDs.
for req_elem in elem.findall('./require'):
depends = req_elem.get('depends', '')
alt_authors = set()
if len(depends) > 0:
for name in dependencyNames(depends):
# Skip core versions
if name[0:11] != 'VK_VERSION_':
# Extract author ID from extension name
id = name.split('_')[1]
alt_authors.add(id)
check_names(req_elem.findall('./command'), author, alt_authors, name_exceptions)
check_names(req_elem.findall('./type'), author, alt_authors, name_exceptions)
check_names(req_elem.findall('./enum'), author, alt_authors, name_exceptions)
def check_extension(self, name, info, supported):
"""Check an extension's XML data for consistency.
Called from check."""
elem = info.elem
enums = elem.findall('./require/enum[@name]')
# If extension name is not on the exception list and matches the
# versioned-extension pattern, map the extension name to the version
# name with the version as a separate word. Otherwise just map it to
# the upper-case version of the extension name.
matches = EXTNAME_RE.fullmatch(name)
ext_versioned_name = False
if name in EXTENSION_ENUM_NAME_SPELLING_CHANGE:
ext_enum_name = EXTENSION_ENUM_NAME_SPELLING_CHANGE.get(name)
elif matches is None or name in EXTENSION_NAME_VERSION_EXCEPTIONS:
# This is the usual case, either a name that does not look
# versioned, or one that does but is on the exception list.
ext_enum_name = name.upper()
else:
# This is a versioned extension name.
# Treat the version number as a separate word.
base = matches.group('base')
version = matches.group('version')
ext_enum_name = base.upper() + '_' + version
# Keep track of this case
ext_versioned_name = True
# Look for the expected SPEC_VERSION token name
version_name = f'{ext_enum_name}_SPEC_VERSION'
version_elem = findNamedElem(enums, version_name)
if version_elem is None:
# Did not find a SPEC_VERSION enum matching the extension name
if ext_versioned_name:
suffix = '\n\
Make sure that trailing version numbers in extension names are treated\n\
as separate words in extension enumerant names. If this is an extension\n\
whose name ends in a number which is not a version, such as "...h264"\n\
or "...int16", add it to EXTENSION_NAME_VERSION_EXCEPTIONS in\n\
scripts/xml_consistency.py.'
else:
suffix = ''
self.record_error(f'Missing version enum {version_name}{suffix}')
elif supported:
# Skip unsupported / disabled extensions for these checks
fn = get_extension_source(name)
revisions = []
with open(fn, 'r', encoding='utf-8') as fp:
for line in fp:
line = line.rstrip()
match = REVISION_RE.match(line)
if match:
revisions.append(int(match.group('num')))
ver_from_xml = version_elem.get('value')
if revisions:
ver_from_text = str(max(revisions))
if ver_from_xml != ver_from_text:
self.record_error('Version enum mismatch: spec text indicates', ver_from_text,
'but XML says', ver_from_xml)
else:
if ver_from_xml == '1':
self.record_warning(
"Cannot find version history in spec text - make sure it has lines starting exactly like ' * Revision 1, ....'",
filename=fn)
else:
self.record_warning("Cannot find version history in spec text, but XML reports a non-1 version number", ver_from_xml,
" - make sure the spec text has lines starting exactly like ' * Revision 1, ....'",
filename=fn)
for enum in enums:
enum_name = enum.get('name')
if '_RESERVED_' in enum_name and enum_name not in EXTENSION_NAME_RESERVED_EXCEPTIONS:
self.record_error(enum_name, 'should not contain _RESERVED_ for a supported extension.\n\
If this is intentional, add it to EXTENSION_NAME_RESERVED_EXCEPTIONS in scripts/xml_consistency.py.')
name_define = f'{ext_enum_name}_EXTENSION_NAME'
name_elem = findNamedElem(enums, name_define)
if name_elem is None:
self.record_error('Missing name enum', name_define)
else:
# Note: etree handles the XML entities here and turns &quot; back into "
expected_name = f'"{name}"'
name_val = name_elem.get('value')
if name_val != expected_name:
self.record_error('Incorrect name enum: expected', expected_name,
'got', name_val)
self.check_suffixes(name, info, supported, { version_name, name_define })
# More general checks
super().check_extension(name, info, supported)
def check_format(self):
"""Check an extension's XML data for consistency.
Called from check."""
astc3d_formats = [
'VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT',
'VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT',
'VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT',
'VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT'
]
# Need to build list of formats from rest of <enums>
enum_formats = []
for enum in self.reg.groupdict['VkFormat'].elem:
if enum.get('alias') is None and enum.get('name') != 'VK_FORMAT_UNDEFINED':
enum_formats.append(enum.get('name'))
found_formats = []
for name, info in self.reg.formatsdict.items():
found_formats.append(name)
self.set_error_context(entity=name, elem=info.elem)
if name not in enum_formats:
self.record_error('The <format> has no matching <enum> for', name)
# Check never just 1 plane
plane_elems = info.elem.findall('plane')
if len(plane_elems) == 1:
self.record_error('The <format> has only 1 <plane> for', name)
valid_chroma = ['420', '422', '444']
if info.elem.get('chroma') and info.elem.get('chroma') not in valid_chroma:
self.record_error('The <format> has chroma is not a valid value for', name)
# The formatsgenerator.py assumes only 1 <spirvimageformat> tag.
# If this changes in the future, remove this warning and update generator script
spirv_image_format = info.elem.findall('spirvimageformat')
if len(spirv_image_format) > 1:
self.record_error('More than 1 <spirvimageformat> but formatsgenerator.py is not updated, for format', name)
# Re-loop to check the other way if the <format> is missing
for enum in self.reg.groupdict['VkFormat'].elem:
name = enum.get('name')
if enum.get('alias') is None and name != 'VK_FORMAT_UNDEFINED':
if name not in found_formats and name not in astc3d_formats:
self.set_error_context(entity=name, elem=enum)
self.record_error('The <enum> has no matching <format> for ', name)
super().check_format()
# This should be called from check() but as a first pass, do it here
# Check for invalid version names in e.g.
# <enable version="VK_VERSION_1_2"/>
# Could also consistency check struct / extension tags here
for capname in self.reg.spirvcapdict:
for elem in self.reg.spirvcapdict[capname].elem.findall('enable'):
version = elem.get('version')
if version is not None and version not in self.reg.apidict:
self.set_error_context(entity=capname, elem=elem)
self.record_error(f'<spirvcapability> {capname} enabled by a nonexistent version {version}')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-warn', action='store_true',
help='Enable display of warning messages')
parser.add_argument('files', metavar='filename', nargs='*',
help='XML filename to check')
args = parser.parse_args()
ckr = Checker(args)
ckr.check()
if ckr.fail:
sys.exit(1)