Mercurial > games > semicongine
diff fuhtark_test/Vulkan-Headers-1.4.334/registry/base_generator.py @ 1501:f40d9d814c08 default tip main
did: correct vulkan-api generator
| author | sam <sam@basx.dev> |
|---|---|
| date | Wed, 26 Nov 2025 23:34:29 +0700 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fuhtark_test/Vulkan-Headers-1.4.334/registry/base_generator.py Wed Nov 26 23:34:29 2025 +0700 @@ -0,0 +1,1170 @@ +#!/usr/bin/env python3 -i +# +# Copyright 2023-2025 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +import pickle +import os +import tempfile +import copy +from vulkan_object import (VulkanObject, + Extension, Version, Legacy, Handle, Param, CommandScope, Command, + EnumField, Enum, Flag, Bitmask, ExternSync, Flags, Member, Struct, + Constant, FormatComponent, FormatPlane, Format, FeatureRequirement, + SyncSupport, SyncEquivalent, SyncStage, SyncAccess, SyncPipelineStage, SyncPipeline, + SpirvEnables, Spirv, + VideoCodec, VideoFormat, VideoProfiles, VideoProfileMember, VideoRequiredCapabilities, + VideoStd, VideoStdHeader) + +# These live in the Vulkan-Docs repo, but are pulled in via the +# Vulkan-Headers/registry folder +from generator import OutputGenerator, GeneratorOptions, write +from vkconventions import VulkanConventions +from reg import Registry +from xml.etree import ElementTree + +# An API style convention object +vulkanConventions = VulkanConventions() + +# Helpers to keep things cleaner +def splitIfGet(elem, name): + return elem.get(name).split(',') if elem.get(name) is not None and elem.get(name) != '' else [] + +def textIfFind(elem, name): + return elem.find(name).text if elem.find(name) is not None else None + +def intIfGet(elem, name): + return None if elem.get(name) is None else int(elem.get(name), 0) + +def boolGet(elem, name) -> bool: + return elem.get(name) is not None and elem.get(name) == "true" + +def externSyncGet(elem): + value = elem.get('externsync') + if value is None: + return (ExternSync.NONE, None) + if value == 'true': + return (ExternSync.ALWAYS, None) + if value == 'maybe': + return (ExternSync.MAYBE, None) + + # There are no cases where multiple members of the param are marked as + # externsync. Supporting that with maybe: requires more than + # ExternSync.SUBTYPE_MAYBE (which is only one bit of information), which is + # not currently done as there are no users. + # + # If this assert is hit, please consider simplifying the design such that + # externsync can move to the struct itself and so external synchronization + # requirements do not depend on the context. + assert ',' not in value + + if value.startswith('maybe:'): + return (ExternSync.SUBTYPE_MAYBE, value.removeprefix('maybe:')) + return (ExternSync.SUBTYPE, value) + +# Shared object used by Sync elements that do not have ones +maxSyncSupport = SyncSupport(None, None, True) +maxSyncEquivalent = SyncEquivalent(None, None, True) + +# Helpers to set GeneratorOptions options globally +def SetOutputFileName(fileName: str) -> None: + global globalFileName + globalFileName = fileName + +def SetOutputDirectory(directory: str) -> None: + global globalDirectory + globalDirectory = directory + +def SetTargetApiName(apiname: str) -> None: + global globalApiName + globalApiName = apiname + +def SetMergedApiNames(names: str) -> None: + global mergedApiNames + mergedApiNames = names + +cachingEnabled = False +def EnableCaching() -> None: + global cachingEnabled + cachingEnabled = True + +# This class is a container for any source code, data, or other behavior that is necessary to +# customize the generator script for a specific target API variant (e.g. Vulkan SC). As such, +# all of these API-specific interfaces and their use in the generator script are part of the +# contract between this repository and its downstream users. Changing or removing any of these +# interfaces or their use in the generator script will have downstream effects and thus +# should be avoided unless absolutely necessary. +class APISpecific: + # Version object factory method + @staticmethod + def createApiVersion(targetApiName: str, name: str, featureRequirement) -> Version: + match targetApiName: + + # Vulkan SC specific API version creation + case 'vulkansc': + nameApi = name.replace('VK_', 'VK_API_') + nameApi = nameApi.replace('VKSC_', 'VKSC_API_') + nameString = f'"{name}"' + return Version(name, nameString, nameApi, featureRequirement) + + # Vulkan specific API version creation + case 'vulkan': + nameApi = name.replace('VK_', 'VK_API_') + nameString = f'"{name}"' + return Version(name, nameString, nameApi, featureRequirement) + + # TODO - Currently genType in reg.py does not provide a good way to get this string to apply the C-macro + # We do our best to emulate the answer here the way the spec/headers will with goal to have a proper fix before these assumptions break + @staticmethod + def createHeaderVersion(targetApiName: str, vk: VulkanObject) -> str: + match targetApiName: + case 'vulkan': + major_version = 1 + minor_version = 4 + case 'vulkansc': + major_version = 1 + minor_version = 0 + return f'{major_version}.{minor_version}.{vk.headerVersion}' + + +# This Generator Option is used across all generators. +# After years of use, it has shown that most the options are unified across each generator (file) +# as it is easier to modify things per-file that need the difference +class BaseGeneratorOptions(GeneratorOptions): + def __init__(self, + customFileName = None, + customDirectory = None, + customApiName = None, + videoXmlPath = None): + GeneratorOptions.__init__(self, + conventions = vulkanConventions, + filename = customFileName if customFileName else globalFileName, + directory = customDirectory if customDirectory else globalDirectory, + apiname = customApiName if customApiName else globalApiName, + mergeApiNames = mergedApiNames, + defaultExtensions = customApiName if customApiName else globalApiName, + emitExtensions = '.*', + emitSpirv = '.*', + emitFormats = '.*') + # These are used by the generator.py script + self.apicall = 'VKAPI_ATTR ' + self.apientry = 'VKAPI_CALL ' + self.apientryp = 'VKAPI_PTR *' + self.alignFuncParam = 48 + + # This is used to provide the video.xml to the private video XML generator + self.videoXmlPath = videoXmlPath + +# +# This object handles all the parsing from reg.py generator scripts in the Vulkan-Headers +# It will grab all the data and form it into a single object the rest of the generators will use +class BaseGenerator(OutputGenerator): + def __init__(self): + OutputGenerator.__init__(self, None, None, None) + self.vk = VulkanObject() + self.targetApiName = globalApiName + + # reg.py has a `self.featureName` but this is nicer because + # it will be either the Version or Extension object + self.currentExtension = None + self.currentVersion = None + + # We need to flag extensions that we ignore because they are disabled or not + # supported in the target API(s) + self.unsupportedExtension = False + + # Will map alias to promoted name + # ex. ['VK_FILTER_CUBIC_IMG' : 'VK_FILTER_CUBIC_EXT'] + # When generating any code, there is no reason so use the old name + self.enumAliasMap = dict() + self.enumFieldAliasMap = dict() + self.bitmaskAliasMap = dict() + self.flagAliasMap = dict() + self.flagsAliasMap = dict() + self.structAliasMap = dict() + self.handleAliasMap = dict() + + # We track all enum constants and flag bits so that we can apply their aliases in the end + self.enumFieldMap: dict[str, EnumField] = dict() + self.flagMap: dict[str, Flag] = dict() + + # De-aliases a definition name based on the specified alias map. + # There are aliases of aliases. + # e.g. VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES_KHR aliases + # VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES_KHR which itself aliases + # But it is also common for EXT types promoted to KHR then to core. + # We should not make assumptions about the nesting level of aliases, instead we resolve any + # level of alias aliasing. + def dealias(self, name: str, aliasMap: dict): + while name in aliasMap: + name = aliasMap[name] + return name + + def write(self, data): + # Prevents having to check before writing + if data is not None and data != "": + write(data, file=self.outFile) + + def beginFile(self, genOpts): + OutputGenerator.beginFile(self, genOpts) + self.filename = genOpts.filename + + # No gen*() command to get these, so do it manually + for platform in self.registry.tree.findall('platforms/platform'): + self.vk.platforms[platform.get('name')] = platform.get('protect') + + for tags in self.registry.tree.findall('tags'): + for tag in tags.findall('tag'): + self.vk.vendorTags.append(tag.get('name')) + + # If the video.xml path is provided then we need to load and parse it using + # the private video std generator + if genOpts.videoXmlPath is not None: + videoStdGenerator = _VideoStdGenerator() + videoRegistry = Registry(videoStdGenerator, genOpts) + videoRegistry.loadElementTree(ElementTree.parse(genOpts.videoXmlPath)) + videoRegistry.apiGen() + self.vk.videoStd = videoStdGenerator.vk.videoStd + + # This function should be overloaded + def generate(self): + print("WARNING: This should not be called from the child class") + return + + # This function is dense, it does all the magic to set the right extensions dependencies! + # + # The issue is if 2 extension expose a command, genCmd() will only + # show one of the extension, at endFile() we can finally go through + # and update which things depend on which extensions + # + # self.featureDictionary is built for use in the reg.py framework + # Details found in Vulkan-Docs/scripts/scriptgenerator.py + def applyExtensionDependency(self): + for extension in self.vk.extensions.values(): + # dict.key() can be None, so need to double loop + dict = self.featureDictionary[extension.name]['command'] + + # "required" == None + # or + # an additional feature dependency, which is a boolean expression of + # one or more extension and/or core version names + for required in dict: + for commandName in dict[required]: + # Skip commands removed in the target API + # This check is needed because parts of the base generator code bypass the + # dependency resolution logic in the registry tooling and thus the generator + # may attempt to generate code for commands which are not supported in the + # target API variant, thus this check needs to happen even if any specific + # target API variant may not specifically need it + if not commandName in self.vk.commands: + continue + + command = self.vk.commands[commandName] + # Make sure list is unique + command.extensions.extend([extension.name] if extension.name not in command.extensions else []) + extension.commands.extend([command] if command not in extension.commands else []) + + # While genGroup() will call twice with aliased value, it does not provide all the information we need + dict = self.featureDictionary[extension.name]['enumconstant'] + for required in dict: + # group can be a Enum or Bitmask + for group in dict[required]: + if group in self.vk.handles: + handle = self.vk.handles[group] + # Make sure list is unique + handle.extensions.extend([extension.name] if extension.name not in handle.extensions else []) + extension.handles[group].extend([handle] if handle not in extension.handles[group] else []) + if group in self.vk.enums: + if group not in extension.enumFields: + extension.enumFields[group] = [] # Dict needs init + enum = self.vk.enums[group] + # Need to convert all alias so they match what is in EnumField + enumList = list(map(lambda x: x if x not in self.enumFieldAliasMap else self.dealias(x, self.enumFieldAliasMap), dict[required][group])) + + for enumField in [x for x in enum.fields if x.name in enumList]: + # Make sure list is unique + enum.fieldExtensions.extend([extension.name] if extension.name not in enum.fieldExtensions else []) + enumField.extensions.extend([extension.name] if extension.name not in enumField.extensions else []) + extension.enumFields[group].extend([enumField] if enumField not in extension.enumFields[group] else []) + if group in self.vk.bitmasks: + if group not in extension.flagBits: + extension.flagBits[group] = [] # Dict needs init + bitmask = self.vk.bitmasks[group] + # Need to convert all alias so they match what is in Flags + flagList = list(map(lambda x: x if x not in self.flagAliasMap else self.dealias(x, self.flagAliasMap), dict[required][group])) + + for flags in [x for x in bitmask.flags if x.name in flagList]: + # Make sure list is unique + bitmask.flagExtensions.extend([extension.name] if extension.name not in bitmask.flagExtensions else []) + flags.extensions.extend([extension.name] if extension.name not in flags.extensions else []) + extension.flagBits[group].extend([flags] if flags not in extension.flagBits[group] else []) + if group in self.vk.flags: + flags = self.vk.flags[group] + # Make sure list is unique + flags.extensions.extend([extension.name] if extension.name not in flags.extensions else []) + extension.flags.extend([flags] if flags not in extension.flags[group] else []) + + # Need to do 'enum'/'bitmask' after 'enumconstant' has applied everything so we can add implicit extensions + # + # Sometimes two extensions enable an Enum, but the newer extension version has extra flags allowed + # This information seems to be implicit, so need to update it here + # Go through each Flag and append the Enum extension to it + # + # ex. VkAccelerationStructureTypeKHR where GENERIC_KHR is not allowed with just VK_NV_ray_tracing + # This only works because the values are aliased as well, making the KHR a superset enum + for extension in self.vk.extensions.values(): + dict = self.featureDictionary[extension.name]['enum'] + for required in dict: + for group in dict[required]: + for enumName in dict[required][group]: + isAlias = enumName in self.enumAliasMap + enumName = self.dealias(enumName, self.enumAliasMap) + if enumName in self.vk.enums: + enum = self.vk.enums[enumName] + enum.extensions.extend([extension.name] if extension.name not in enum.extensions else []) + extension.enums.extend([enum] if enum not in extension.enums else []) + # Update fields with implicit base extension + if isAlias: + continue + enum.fieldExtensions.extend([extension.name] if extension.name not in enum.fieldExtensions else []) + for enumField in [x for x in enum.fields if (not x.extensions or (x.extensions and all(e in enum.extensions for e in x.extensions)))]: + enumField.extensions.extend([extension.name] if extension.name not in enumField.extensions else []) + if enumName not in extension.enumFields: + extension.enumFields[enumName] = [] # Dict needs init + extension.enumFields[enumName].extend([enumField] if enumField not in extension.enumFields[enumName] else []) + + dict = self.featureDictionary[extension.name]['bitmask'] + for required in dict: + for group in dict[required]: + for bitmaskName in dict[required][group]: + bitmaskName = bitmaskName.replace('Flags', 'FlagBits') # Works since Flags is not repeated in name + isAlias = bitmaskName in self.bitmaskAliasMap + bitmaskName = self.dealias(bitmaskName, self.bitmaskAliasMap) + if bitmaskName in self.vk.bitmasks: + bitmask = self.vk.bitmasks[bitmaskName] + bitmask.extensions.extend([extension.name] if extension.name not in bitmask.extensions else []) + extension.bitmasks.extend([bitmask] if bitmask not in extension.bitmasks else []) + # Update flags with implicit base extension + if isAlias: + continue + bitmask.flagExtensions.extend([extension.name] if extension.name not in bitmask.flagExtensions else []) + for flag in [x for x in bitmask.flags if (not x.extensions or (x.extensions and all(e in bitmask.extensions for e in x.extensions)))]: + flag.extensions.extend([extension.name] if extension.name not in flag.extensions else []) + if bitmaskName not in extension.flagBits: + extension.flagBits[bitmaskName] = [] # Dict needs init + extension.flagBits[bitmaskName].extend([flag] if flag not in extension.flagBits[bitmaskName] else []) + + # Some structs (ex VkAttachmentSampleCountInfoAMD) can have multiple alias pointing to same extension + for extension in self.vk.extensions.values(): + dict = self.featureDictionary[extension.name]['struct'] + for required in dict: + for group in dict[required]: + for structName in dict[required][group]: + isAlias = structName in self.structAliasMap + structName = self.dealias(structName, self.structAliasMap) + if structName in self.vk.structs: + struct = self.vk.structs[structName] + struct.extensions.extend([extension.name] if extension.name not in struct.extensions else []) + extension.structs.extend([struct] if struct not in extension.structs else []) + + # While we update struct alias inside other structs, the command itself might have the struct as a first level param. + # We use this time to update params to have the promoted name + # Example - https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9322 + # TODO: It is unclear why only structs need dealiasing here, but not other types, so this probably needs revisiting + for command in self.vk.commands.values(): + for member in command.params: + if member.type in self.structAliasMap: + member.type = self.dealias(member.type, self.structAliasMap) + # Replace string with Version class now we have all version created + if command.legacy and command.legacy.version: + if command.legacy.version not in self.vk.versions: + # occurs if something like VK_VERSION_1_0, in which case we will always warn for deprecation + command.legacy.version = None + else: + command.legacy.version = self.vk.versions[command.legacy.version] + + # Could build up a reverse lookup map, but since these are not too large of list, just do here + # (Need to be done after we have found all the aliases) + for key, value in self.structAliasMap.items(): + self.vk.structs[self.dealias(value, self.structAliasMap)].aliases.append(key) + for key, value in self.enumFieldAliasMap.items(): + self.enumFieldMap[self.dealias(value, self.enumFieldAliasMap)].aliases.append(key) + for key, value in self.enumAliasMap.items(): + self.vk.enums[self.dealias(value, self.enumAliasMap)].aliases.append(key) + for key, value in self.flagAliasMap.items(): + self.flagMap[self.dealias(value, self.flagAliasMap)].aliases.append(key) + for key, value in self.bitmaskAliasMap.items(): + self.vk.bitmasks[self.dealias(value, self.bitmaskAliasMap)].aliases.append(key) + for key, value in self.flagsAliasMap.items(): + self.vk.flags[self.dealias(value, self.flagsAliasMap)].aliases.append(key) + for key, value in self.handleAliasMap.items(): + self.vk.handles[self.dealias(value, self.handleAliasMap)].aliases.append(key) + + def addConstants(self, constantNames: list[str]): + for constantName in constantNames: + enumInfo = self.registry.enumdict[constantName] + typeName = enumInfo.type + valueStr = enumInfo.elem.get('value') + # These values are represented in c-style + isHex = valueStr.upper().startswith('0X') + intBase = 16 if isHex else 10 + if valueStr.upper().endswith('F') and not isHex: + value = float(valueStr[:-1]) + elif valueStr.upper().endswith('U)'): + inner_number = int(valueStr.removeprefix("(~").removesuffix(")")[:-1], intBase) + value = (~inner_number) & ((1 << 32) - 1) + elif valueStr.upper().endswith('ULL)'): + inner_number = int(valueStr.removeprefix("(~").removesuffix(")")[:-3], intBase) + value = (~0) & ((1 << 64) - 1) + else: + value = int(valueStr, intBase) + self.vk.constants[constantName] = Constant(constantName, typeName, value, valueStr) + + def addVideoCodecs(self): + for xmlVideoCodec in self.registry.tree.findall('videocodecs/videocodec'): + name = xmlVideoCodec.get('name') + extend = xmlVideoCodec.get('extend') + value = xmlVideoCodec.get('value') + + profiles: dict[str, VideoProfiles] = {} + capabilities: dict[str, str] = {} + formats: dict[str, VideoFormat] = {} + + if extend is not None: + # Inherit base profiles, capabilities, and formats + profiles = copy.deepcopy(self.vk.videoCodecs[extend].profiles) + capabilities = copy.deepcopy(self.vk.videoCodecs[extend].capabilities) + formats = copy.deepcopy(self.vk.videoCodecs[extend].formats) + + for xmlVideoProfiles in xmlVideoCodec.findall('videoprofiles'): + videoProfileStructName = xmlVideoProfiles.get('struct') + videoProfileStructMembers : dict[str, VideoProfileMember] = {} + + for xmlVideoProfileMember in xmlVideoProfiles.findall('videoprofilemember'): + memberName = xmlVideoProfileMember.get('name') + memberValues: dict[str, str] = {} + + for xmlVideoProfile in xmlVideoProfileMember.findall('videoprofile'): + memberValues[xmlVideoProfile.get('value')] = xmlVideoProfile.get('name') + + videoProfileStructMembers[memberName] = VideoProfileMember(memberName, memberValues) + + profiles[videoProfileStructName] = VideoProfiles(videoProfileStructName, videoProfileStructMembers) + + for xmlVideoCapabilities in xmlVideoCodec.findall('videocapabilities'): + capabilities[xmlVideoCapabilities.get('struct')] = xmlVideoCapabilities.get('struct') + + for xmlVideoFormat in xmlVideoCodec.findall('videoformat'): + videoFormatName = xmlVideoFormat.get('name') + videoFormatExtend = xmlVideoFormat.get('extend') + + videoFormatRequiredCaps: list[VideoRequiredCapabilities] = [] + videoFormatProps: dict[str, str] = {} + + if videoFormatName is not None: + # This is a new video format category + videoFormatUsage = xmlVideoFormat.get('usage') + videoFormat = VideoFormat(videoFormatName, videoFormatUsage, videoFormatRequiredCaps, videoFormatProps) + formats[videoFormatName] = videoFormat + else: + # This is an extension to an already defined video format category + videoFormat = formats[videoFormatExtend] + videoFormatRequiredCaps = videoFormat.requiredCaps + videoFormatProps = videoFormat.properties + + for xmlVideoFormatRequiredCap in xmlVideoFormat.findall('videorequirecapabilities'): + requiredCap = VideoRequiredCapabilities(xmlVideoFormatRequiredCap.get('struct'), + xmlVideoFormatRequiredCap.get('member'), + xmlVideoFormatRequiredCap.get('value')) + videoFormatRequiredCaps.append(requiredCap) + + for xmlVideoFormatProperties in xmlVideoFormat.findall('videoformatproperties'): + videoFormatProps[xmlVideoFormatProperties.get('struct')] = xmlVideoFormatProperties.get('struct') + + self.vk.videoCodecs[name] = VideoCodec(name, value, profiles, capabilities, formats) + + def endFile(self): + # This is the point were reg.py has ran, everything is collected + # We do some post processing now + self.applyExtensionDependency() + + self.addConstants([k for k,v in self.registry.enumvaluedict.items() if v == 'API Constants']) + self.addVideoCodecs() + + self.vk.headerVersionComplete = APISpecific.createHeaderVersion(self.targetApiName, self.vk) + + # Use structs and commands to find which things are returnedOnly + for struct in [x for x in self.vk.structs.values() if not x.returnedOnly]: + for enum in [self.vk.enums[x.type] for x in struct.members if x.type in self.vk.enums]: + enum.returnedOnly = False + for bitmask in [self.vk.bitmasks[x.type] for x in struct.members if x.type in self.vk.bitmasks]: + bitmask.returnedOnly = False + for flags in [self.vk.flags[x.type] for x in struct.members if x.type in self.vk.flags]: + flags.returnedOnly = False + if flags.bitmaskName is not None: + self.vk.bitmasks[flags.bitmaskName].returnedOnly = False + for command in self.vk.commands.values(): + for enum in [self.vk.enums[x.type] for x in command.params if x.type in self.vk.enums]: + enum.returnedOnly = False + for bitmask in [self.vk.bitmasks[x.type] for x in command.params if x.type in self.vk.bitmasks]: + bitmask.returnedOnly = False + for flags in [self.vk.flags[x.type] for x in command.params if x.type in self.vk.flags]: + flags.returnedOnly = False + if flags.bitmaskName is not None: + self.vk.bitmasks[flags.bitmaskName].returnedOnly = False + + # Turn handle parents into pointers to classes + for handle in [x for x in self.vk.handles.values() if x.parent is not None]: + handle.parent = self.vk.handles[handle.parent] + # search up parent chain to see if instance or device + for handle in [x for x in self.vk.handles.values()]: + next_parent = handle.parent + while (not handle.instance and not handle.device): + handle.instance = next_parent.name == 'VkInstance' + handle.device = next_parent.name == 'VkDevice' + next_parent = next_parent.parent + + maxSyncSupport.stages = self.vk.bitmasks['VkPipelineStageFlagBits2'].flags + maxSyncEquivalent.accesses = self.vk.bitmasks['VkAccessFlagBits2'].flags + maxSyncEquivalent.stages = self.vk.bitmasks['VkPipelineStageFlagBits2'].flags + + # All inherited generators should run from here + self.generate() + + if cachingEnabled: + cachePath = os.path.join(tempfile.gettempdir(), f'vkobject_{os.getpid()}') + if not os.path.isfile(cachePath): + cacheFile = open(cachePath, 'wb') + pickle.dump(self.vk, cacheFile) + cacheFile.close() + + # This should not have to do anything but call into OutputGenerator + OutputGenerator.endFile(self) + + # + # Bypass the entire processing and load in the VkObject data + # Still need to handle the beingFile/endFile for reg.py + def generateFromCache(self, cacheVkObjectData, genOpts): + OutputGenerator.beginFile(self, genOpts) + self.filename = genOpts.filename + self.vk = cacheVkObjectData + self.generate() + OutputGenerator.endFile(self) + + # + # Processing point at beginning of each extension definition + def beginFeature(self, interface, emit): + OutputGenerator.beginFeature(self, interface, emit) + platform = interface.get('platform') + self.featureExtraProtec = self.vk.platforms[platform] if platform in self.vk.platforms else None + protect = self.vk.platforms[platform] if platform in self.vk.platforms else None + name = interface.get('name') + + # TODO - This is just mimicking featurerequirementsgenerator.py and works because the logic is simple enough (for now) + featureRequirement = [] + requires = interface.findall('./require') + for require in requires: + requireDepends = require.get('depends') + for feature in require.findall('./feature'): + featureStruct = feature.get('struct') + featureName = feature.get('name') + featureRequirement.append(FeatureRequirement(featureStruct, featureName, requireDepends)) + + if interface.tag == 'extension': + # Generator scripts built on BaseGenerator do not handle the `supported` attribute of extensions + # therefore historically the `generate_source.py` in individual ecosystem components hacked the + # registry by removing non-applicable or disabled extensions from the loaded XML already before + # reg.py parsed it. That broke the general behavior of reg.py for certain use cases so we now + # filter extensions here instead (after parsing) in order to no longer need the filtering hack + # in downstream `generate_source.py` scripts. + enabledApiList = [ globalApiName ] + ([] if mergedApiNames is None else mergedApiNames.split(',')) + if (sup := interface.get('supported')) is not None and all(api not in sup.split(',') for api in enabledApiList): + self.unsupportedExtension = True + return + + instance = interface.get('type') == 'instance' + device = not instance + depends = interface.get('depends') + vendorTag = name.split('_')[1] + platform = interface.get('platform') + provisional = boolGet(interface, 'provisional') + promotedto = interface.get('promotedto') + deprecatedby = interface.get('deprecatedby') + obsoletedby = interface.get('obsoletedby') + specialuse = splitIfGet(interface, 'specialuse') + ratifiedApis = splitIfGet(interface, 'ratified') + ratified = True if len(ratifiedApis) > 0 and self.genOpts.apiname in ratifiedApis else False + + # Not sure if better way to get this info + specVersion = self.featureDictionary[name]['enumconstant'][None][None][0] + nameString = self.featureDictionary[name]['enumconstant'][None][None][1] + + self.currentExtension = Extension(name, nameString, specVersion, instance, device, depends, vendorTag, + platform, protect, provisional, promotedto, deprecatedby, + obsoletedby, specialuse, featureRequirement, ratified) + self.vk.extensions[name] = self.currentExtension + else: # version + number = interface.get('number') + if number != '1.0': + self.currentVersion = APISpecific.createApiVersion(self.targetApiName, name, featureRequirement) + self.vk.versions[name] = self.currentVersion + + def endFeature(self): + OutputGenerator.endFeature(self) + self.currentExtension = None + self.currentVersion = None + self.unsupportedExtension = False + + # + # All <command> from XML + def genCmd(self, cmdinfo, name, alias): + OutputGenerator.genCmd(self, cmdinfo, name, alias) + + # Do not include APIs from unsupported extensions + if self.unsupportedExtension: + return + + params = [] + for param in cmdinfo.elem.findall('param'): + paramName = param.find('name').text + paramType = textIfFind(param, 'type') + paramAlias = param.get('alias') + + cdecl = self.makeCParamDecl(param, 0) + paramFullType = ' '.join(cdecl.split()[:-1]) + pointer = '*' in cdecl or paramType.startswith('PFN_') + paramConst = 'const' in cdecl + fixedSizeArray = [x[:-1] for x in cdecl.split('[') if x.endswith(']')] + + paramNoautovalidity = boolGet(param, 'noautovalidity') + + nullTerminated = False + length = param.get('altlen') if param.get('altlen') is not None else param.get('len') + if length: + # we will either find it like "null-terminated" or "enabledExtensionCount,null-terminated" + # This finds both + nullTerminated = 'null-terminated' in length + length = length.replace(',null-terminated', '') if 'null-terminated' in length else length + length = None if length == 'null-terminated' else length + + if fixedSizeArray and not length: + length = ','.join(fixedSizeArray) + + # See Member::optional code for details of this + optionalValues = splitIfGet(param, 'optional') + optional = len(optionalValues) > 0 and optionalValues[0].lower() == "true" + optionalPointer = len(optionalValues) > 1 and optionalValues[1].lower() == "true" + + # externsync will be 'true', 'maybe', '<expression>' or 'maybe:<expression>' + (externSync, externSyncPointer) = externSyncGet(param) + + params.append(Param(paramName, paramAlias, paramType, paramFullType, paramNoautovalidity, + paramConst, length, nullTerminated, pointer, fixedSizeArray, + optional, optionalPointer, + externSync, externSyncPointer, cdecl)) + + attrib = cmdinfo.elem.attrib + alias = attrib.get('alias') + tasks = splitIfGet(attrib, 'tasks') + + queues = splitIfGet(attrib, 'queues') + allowNoQueues = boolGet(attrib, 'allownoqueues') + successcodes = splitIfGet(attrib, 'successcodes') + errorcodes = splitIfGet(attrib, 'errorcodes') + cmdbufferlevel = attrib.get('cmdbufferlevel') + primary = cmdbufferlevel is not None and 'primary' in cmdbufferlevel + secondary = cmdbufferlevel is not None and 'secondary' in cmdbufferlevel + + renderpass = attrib.get('renderpass') + renderpass = CommandScope.NONE if renderpass is None else getattr(CommandScope, renderpass.upper()) + videocoding = attrib.get('videocoding') + videocoding = CommandScope.NONE if videocoding is None else getattr(CommandScope, videocoding.upper()) + + protoElem = cmdinfo.elem.find('proto') + returnType = textIfFind(protoElem, 'type') + + decls = self.makeCDecls(cmdinfo.elem) + cPrototype = decls[0] + cFunctionPointer = decls[1] + + legacy = None + if cmdinfo.deprecatedlink: + legacy = Legacy(cmdinfo.deprecatedlink, + cmdinfo.deprecatedbyversion, # is just the string, will update to class later + cmdinfo.deprecatedbyextensions) + + protect = self.currentExtension.protect if self.currentExtension is not None else None + + # These coammds have no way from the XML to detect they would be an instance command + specialInstanceCommand = ['vkCreateInstance', 'vkEnumerateInstanceExtensionProperties','vkEnumerateInstanceLayerProperties', 'vkEnumerateInstanceVersion'] + instance = len(params) > 0 and (params[0].type == 'VkInstance' or params[0].type == 'VkPhysicalDevice' or name in specialInstanceCommand) + device = not instance + + implicitElem = cmdinfo.elem.find('implicitexternsyncparams') + implicitExternSyncParams = [x.text for x in implicitElem.findall('param')] if implicitElem is not None else [] + + self.vk.commands[name] = Command(name, alias, protect, [], self.currentVersion, + returnType, params, instance, device, + tasks, queues, allowNoQueues, successcodes, errorcodes, + primary, secondary, renderpass, videocoding, + implicitExternSyncParams, legacy, cPrototype, cFunctionPointer) + + # + # List the enum for the commands + # TODO - Seems empty groups like `VkDeviceDeviceMemoryReportCreateInfoEXT` do not show up in here + def genGroup(self, groupinfo, groupName, alias): + # Do not include APIs from unsupported extensions + if self.unsupportedExtension: + return + + # There can be case where the Enum/Bitmask is in a protect, but the individual + # fields also have their own protect + groupProtect = self.currentExtension.protect if hasattr(self.currentExtension, 'protect') and self.currentExtension.protect is not None else None + enumElem = groupinfo.elem + bitwidth = 32 if enumElem.get('bitwidth') is None else int(enumElem.get('bitwidth')) + fields = [] + if enumElem.get('type') == "enum": + if alias is not None: + self.enumAliasMap[groupName] = alias + return + + for elem in enumElem.findall('enum'): + fieldName = elem.get('name') + + # Do not include non-required enum constants + # reg.py emits the enum constants of the entire type, even constants that are part of unsupported + # extensions or those that are removed by <remove> elements in a given API. reg.py correctly tracks + # down these and also alias dependencies and marks the enum constants that are actually required + # with the 'required' attribute. Therefore we also have to verify that here to make sure we only + # include enum constants that are actually required in the target API(s). + if elem.get('required') is None: + continue + + if elem.get('alias') is not None: + self.enumFieldAliasMap[fieldName] = elem.get('alias') + continue + + negative = elem.get('dir') is not None + protect = elem.get('protect') + (valueInt, valueStr) = self.enumToValue(elem, True, bitwidth) + + # Some values have multiple extensions (ex VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS_KHR) + # genGroup() lists them twice + if next((x for x in fields if x.name == fieldName), None) is None: + self.enumFieldMap[fieldName] = EnumField(fieldName, [], protect, negative, valueInt, valueStr, []) + fields.append(self.enumFieldMap[fieldName]) + + self.vk.enums[groupName] = Enum(groupName, [], groupProtect, bitwidth, True, fields, [], []) + + else: # "bitmask" + if alias is not None: + self.bitmaskAliasMap[groupName] = alias + return + + for elem in enumElem.findall('enum'): + flagName = elem.get('name') + + # Do not include non-required enum constants + # reg.py emits the enum constants of the entire type, even constants that are part of unsupported + # extensions or those that are removed by <remove> elements in a given API. reg.py correctly tracks + # down these and also alias dependencies and marks the enum constants that are actually required + # with the 'required' attribute. Therefore we also have to verify that here to make sure we only + # include enum constants that are actually required in the target API(s). + if elem.get('required') is None: + continue + + if elem.get('alias') is not None: + self.flagAliasMap[flagName] = elem.get('alias') + continue + + protect = elem.get('protect') + + (valueInt, valueStr) = self.enumToValue(elem, True, bitwidth) + flagZero = valueInt == 0 + flagMultiBit = False + # if flag uses 'value' instead of 'bitpos', will be zero or a mask + if elem.get('bitpos') is None and elem.get('value'): + flagMultiBit = valueInt != 0 + + # Some values have multiple extensions (ex VK_TOOL_PURPOSE_DEBUG_REPORTING_BIT_EXT) + # genGroup() lists them twice + if next((x for x in fields if x.name == flagName), None) is None: + self.flagMap[flagName] = Flag(flagName, [], protect, valueInt, valueStr, flagMultiBit, flagZero, []) + fields.append(self.flagMap[flagName]) + + flagName = groupName.replace('FlagBits', 'Flags') + self.vk.bitmasks[groupName] = Bitmask(groupName, [], flagName, groupProtect, bitwidth, True, fields, [], []) + + def genType(self, typeInfo, typeName, alias): + OutputGenerator.genType(self, typeInfo, typeName, alias) + + # Do not include APIs from unsupported extensions + if self.unsupportedExtension: + return + + typeElem = typeInfo.elem + protect = self.currentExtension.protect if hasattr(self.currentExtension, 'protect') and self.currentExtension.protect is not None else None + extension = [self.currentExtension.name] if self.currentExtension is not None else [] + category = typeElem.get('category') + if (category == 'struct' or category == 'union'): + if alias is not None: + self.structAliasMap[typeName] = alias + return + + union = category == 'union' + + returnedOnly = boolGet(typeElem, 'returnedonly') + allowDuplicate = boolGet(typeElem, 'allowduplicate') + + extends = splitIfGet(typeElem, 'structextends') + extendedBy = self.registry.validextensionstructs[typeName] if len(self.registry.validextensionstructs[typeName]) > 0 else [] + + membersElem = typeInfo.elem.findall('.//member') + members = [] + sType = None + + for member in membersElem: + for comment in member.findall('comment'): + member.remove(comment) + + name = textIfFind(member, 'name') + type = textIfFind(member, 'type') + sType = member.get('values') if member.get('values') is not None else sType + noautovalidity = boolGet(member, 'noautovalidity') + limittype = member.get('limittype') + + (externSync, externSyncPointer) = externSyncGet(member) + # No cases currently where a subtype of a struct is marked as externally synchronized. + assert externSyncPointer is None + + nullTerminated = False + length = member.get('altlen') if member.get('altlen') is not None else member.get('len') + if length: + # we will either find it like "null-terminated" or "enabledExtensionCount,null-terminated" + # This finds both + nullTerminated = 'null-terminated' in length + length = length.replace(',null-terminated', '') if 'null-terminated' in length else length + length = None if length == 'null-terminated' else length + + cdecl = self.makeCParamDecl(member, 0) + fullType = ' '.join(cdecl[:cdecl.rfind(name)].split()) + pointer = '*' in cdecl or type.startswith('PFN_') + const = 'const' in cdecl + # Some structs like VkTransformMatrixKHR have a 2D array + fixedSizeArray = [x[:-1] for x in cdecl.split('[') if x.endswith(']')] + + if fixedSizeArray and not length: + length = ','.join(fixedSizeArray) + + # Handle C bit field members + bitFieldWidth = int(cdecl.split(':')[1]) if ':' in cdecl else None + + selector = member.get('selector') if not union else None + selection = member.get('selection') if union else None + selections = [] + if selection: + selections = [s for s in selection.split(',')] + + # if a pointer, this can be a something like: + # optional="true,false" for ppGeometries + # optional="false,true" for pPhysicalDeviceCount + # the first is if the variable itself is optional + # the second is the value of the pointer is optional; + optionalValues = splitIfGet(member, 'optional') + optional = len(optionalValues) > 0 and optionalValues[0].lower() == "true" + optionalPointer = len(optionalValues) > 1 and optionalValues[1].lower() == "true" + + members.append(Member(name, type, fullType, noautovalidity, limittype, + const, length, nullTerminated, pointer, fixedSizeArray, + optional, optionalPointer, + externSync, cdecl, bitFieldWidth, selector, selections)) + + self.vk.structs[typeName] = Struct(typeName, [], extension, self.currentVersion, protect, members, + union, returnedOnly, sType, allowDuplicate, extends, extendedBy) + + elif category == 'handle': + if alias is not None: + self.handleAliasMap[typeName] = alias + return + type = typeElem.get('objtypeenum') + + # will resolve these later, the VulkanObjectType does not list things in dependent order + parent = typeElem.get('parent') + instance = typeName == 'VkInstance' + device = typeName == 'VkDevice' + + dispatchable = typeElem.find('type').text == 'VK_DEFINE_HANDLE' + + self.vk.handles[typeName] = Handle(typeName, [], type, protect, parent, instance, device, dispatchable, extension) + + elif category == 'define': + if typeName == 'VK_HEADER_VERSION': + self.vk.headerVersion = typeElem.find('name').tail.strip() + + elif category == 'bitmask': + if alias is not None: + self.flagsAliasMap[typeName] = alias + return + + # Bitmask types, i.e. flags + baseFlagsType = typeElem.find('type').text + bitWidth = 64 if baseFlagsType == 'VkFlags64' else 32 + + # Bitmask enum type is either in the 'requires' or 'bitvalues' attribute + # (for some reason there are two conventions) + bitmaskName = typeElem.get('bitvalues') + if bitmaskName is None: + bitmaskName = typeElem.get('requires') + + self.vk.flags[typeName] = Flags(typeName, [], bitmaskName, protect, baseFlagsType, bitWidth, True, extension) + + else: + # not all categories are used + # 'group'/'enum' are routed to genGroup instead + # 'basetype'/'include' are only for headers + # 'funcpointer` ignore until needed + return + + def genSpirv(self, spirvinfo, spirvName, alias): + OutputGenerator.genSpirv(self, spirvinfo, spirvName, alias) + spirvElem = spirvinfo.elem + name = spirvElem.get('name') + extension = True if spirvElem.tag == 'spirvextension' else False + capability = not extension + + enables = [] + for elem in spirvElem: + version = elem.attrib.get('version') + extensionEnable = elem.attrib.get('extension') + struct = elem.attrib.get('struct') + feature = elem.attrib.get('feature') + requires = elem.attrib.get('requires') + propertyEnable = elem.attrib.get('property') + member = elem.attrib.get('member') + value = elem.attrib.get('value') + enables.append(SpirvEnables(version, extensionEnable, struct, feature, + requires, propertyEnable, member, value)) + + self.vk.spirv.append(Spirv(name, extension, capability, enables)) + + def genFormat(self, format, formatinfo, alias): + OutputGenerator.genFormat(self, format, formatinfo, alias) + formatElem = format.elem + name = formatElem.get('name') + + components = [] + for component in formatElem.iterfind('component'): + type = component.get('name') + bits = component.get('bits') + numericFormat = component.get('numericFormat') + planeIndex = intIfGet(component, 'planeIndex') + components.append(FormatComponent(type, bits, numericFormat, planeIndex)) + + planes = [] + for plane in formatElem.iterfind('plane'): + index = int(plane.get('index')) + widthDivisor = int(plane.get('widthDivisor')) + heightDivisor = int(plane.get('heightDivisor')) + compatible = plane.get('compatible') + planes.append(FormatPlane(index, widthDivisor, heightDivisor, compatible)) + + className = formatElem.get('class') + blockSize = int(formatElem.get('blockSize')) + texelsPerBlock = int(formatElem.get('texelsPerBlock')) + blockExtent = splitIfGet(formatElem, 'blockExtent') + packed = intIfGet(formatElem, 'packed') + chroma = formatElem.get('chroma') + compressed = formatElem.get('compressed') + spirvImageFormat = formatElem.find('spirvimageformat') + if spirvImageFormat is not None: + spirvImageFormat = spirvImageFormat.get('name') + + self.vk.formats[name] = Format(name, className, blockSize, texelsPerBlock, + blockExtent, packed, chroma, compressed, + components, planes, spirvImageFormat) + + def genSyncStage(self, sync): + OutputGenerator.genSyncStage(self, sync) + syncElem = sync.elem + + support = maxSyncSupport + supportElem = syncElem.find('syncsupport') + if supportElem is not None: + queues = splitIfGet(supportElem, 'queues') + stageNames = splitIfGet(supportElem, 'stage') + stages = [x for x in self.vk.bitmasks['VkPipelineStageFlagBits2'].flags if x.name in stageNames] if len(stageNames) > 0 else None + support = SyncSupport(queues, stages, False) + + equivalent = maxSyncEquivalent + equivalentElem = syncElem.find('syncequivalent') + if equivalentElem is not None: + stageNames = splitIfGet(equivalentElem, 'stage') + stages = [x for x in self.vk.bitmasks['VkPipelineStageFlagBits2'].flags if x.name in stageNames] if len(stageNames) > 0 else None + accessNames = splitIfGet(equivalentElem, 'access') + accesses = [x for x in self.vk.bitmasks['VkAccessFlagBits2'].flags if x.name in accessNames] if len(accessNames) > 0 else None + equivalent = SyncEquivalent(stages, accesses, False) + + flagName = syncElem.get('name') + flag = [x for x in self.vk.bitmasks['VkPipelineStageFlagBits2'].flags if x.name == flagName] + # This check is needed because not all API variants have VK_KHR_synchronization2 + if flag: + self.vk.syncStage.append(SyncStage(flag[0], support, equivalent)) + + def genSyncAccess(self, sync): + OutputGenerator.genSyncAccess(self, sync) + syncElem = sync.elem + + support = maxSyncSupport + supportElem = syncElem.find('syncsupport') + if supportElem is not None: + queues = splitIfGet(supportElem, 'queues') + stageNames = splitIfGet(supportElem, 'stage') + stages = [x for x in self.vk.bitmasks['VkPipelineStageFlagBits2'].flags if x.name in stageNames] if len(stageNames) > 0 else None + support = SyncSupport(queues, stages, False) + + equivalent = maxSyncEquivalent + equivalentElem = syncElem.find('syncequivalent') + if equivalentElem is not None: + stageNames = splitIfGet(equivalentElem, 'stage') + stages = [x for x in self.vk.bitmasks['VkPipelineStageFlagBits2'].flags if x.name in stageNames] if len(stageNames) > 0 else None + accessNames = splitIfGet(equivalentElem, 'access') + accesses = [x for x in self.vk.bitmasks['VkAccessFlagBits2'].flags if x.name in accessNames] if len(accessNames) > 0 else None + equivalent = SyncEquivalent(stages, accesses, False) + + flagName = syncElem.get('name') + flag = [x for x in self.vk.bitmasks['VkAccessFlagBits2'].flags if x.name == flagName] + # This check is needed because not all API variants have VK_KHR_synchronization2 + if flag: + self.vk.syncAccess.append(SyncAccess(flag[0], support, equivalent)) + + def genSyncPipeline(self, sync): + OutputGenerator.genSyncPipeline(self, sync) + syncElem = sync.elem + name = syncElem.get('name') + depends = splitIfGet(syncElem, 'depends') + stages = [] + for stageElem in syncElem.findall('syncpipelinestage'): + order = stageElem.get('order') + before = stageElem.get('before') + after = stageElem.get('after') + value = stageElem.text + stages.append(SyncPipelineStage(order, before, after, value)) + + self.vk.syncPipeline.append(SyncPipeline(name, depends, stages)) + +# +# This object handles all the parsing from the video.xml (i.e. Video Std header definitions) +# It will fill in video standard definitions into the VulkanObject +class _VideoStdGenerator(BaseGenerator): + def __init__(self): + BaseGenerator.__init__(self) + self.vk.videoStd = VideoStd() + + # Track the current Video Std header we are processing + self.currentVideoStdHeader = None + + def write(self, data): + # We do not write anything here + return + + def beginFile(self, genOpts): + # We intentionally skip default BaseGenerator behavior + OutputGenerator.beginFile(self, genOpts) + + def endFile(self): + # Move parsed definitions to the Video Std definitions + self.vk.videoStd.enums = self.vk.enums + self.vk.videoStd.structs = self.vk.structs + self.vk.videoStd.constants = self.vk.constants + + # We intentionally skip default BaseGenerator behavior + OutputGenerator.endFile(self) + + def beginFeature(self, interface, emit): + # We intentionally skip default BaseGenerator behavior + OutputGenerator.beginFeature(self, interface, emit) + + # Only "extension" is possible in the video.xml, identifying the Video Std header + assert interface.tag == 'extension' + name = interface.get('name') + version: (str | None) = None + depends: list[str] = [] + + # Handle Video Std header version constant + for enum in interface.findall('require/enum[@value]'): + enumName = enum.get('name') + if enumName.endswith('_SPEC_VERSION'): + version = enum.get('value') + + # Handle dependencies on other Video Std headers + for type in interface.findall('require/type[@name]'): + typeName = type.get('name') + if typeName.startswith('vk_video/'): + depends.append(typeName[len('vk_video/'):-len('.h')]) + + headerFile = f'vk_video/{name}.h' + + self.vk.videoStd.headers[name] = VideoStdHeader(name, version, headerFile, depends) + + self.currentVideoStdHeader = self.vk.videoStd.headers[name] + + # Handle constants here as that seems the most straightforward + constantNames = [] + for enum in interface.findall('require/enum[@type]'): + constantNames.append(enum.get('name')) + self.addConstants(constantNames) + for constantName in constantNames: + self.vk.constants[constantName].videoStdHeader = self.currentVideoStdHeader.name + + def endFeature(self): + self.currentVideoStdHeader = None + + # We intentionally skip default BaseGenerator behavior + OutputGenerator.endFeature(self) + + def genCmd(self, cmdinfo, name, alias): + # video.xml should not contain any commands + assert False + + def genGroup(self, groupinfo, groupName, alias): + BaseGenerator.genGroup(self, groupinfo, groupName, alias) + + # We are supposed to be inside a video std header + assert self.currentVideoStdHeader is not None + + # Mark the enum with the Video Std header it comes from + if groupinfo.elem.get('type') == 'enum': + assert alias is None + self.vk.enums[groupName].videoStdHeader = self.currentVideoStdHeader.name + + def genType(self, typeInfo, typeName, alias): + BaseGenerator.genType(self, typeInfo, typeName, alias) + + # We are supposed to be inside a video std header + assert self.currentVideoStdHeader is not None + + # Mark the struct with the Video Std header it comes from + if typeInfo.elem.get('category') == 'struct': + assert alias is None + self.vk.structs[typeName].videoStdHeader = self.currentVideoStdHeader.name + + def genSpirv(self, spirvinfo, spirvName, alias): + # video.xml should not contain any SPIR-V info + assert False + + def genFormat(self, format, formatinfo, alias): + # video.xml should not contain any format info + assert False + + def genSyncStage(self, sync): + # video.xml should not contain any sync stage info + assert False + + def genSyncAccess(self, sync): + # video.xml should not contain any sync access info + assert False + + def genSyncPipeline(self, sync): + # video.xml should not contain any sync pipeline info + assert False
