| # Copyright 2025 The Android Open Source Project | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import xml.etree.ElementTree as ET | |
| from collections import defaultdict | |
| import re | |
| from datetime import datetime | |
| from typing import Dict, Optional, List, Any, Tuple, Set, IO | |
| from pathlib import Path | |
| import pprint | |
| import copy | |
| import generator_common as gencom | |
| # --- FILE PATHS & OUTPUT CONFIGURATION --- | |
| VULKAN_XML_FILE_PATH = Path("../../../../external/vulkan-headers/registry/vk.xml") | |
| OUTPUT_VK_PY_PATH = Path("vk.py") | |
| # Output file initial content | |
| INIT_CONTENT = """\n\nfrom dataclasses import dataclass | |
| from enum import Enum | |
| import dataclasses | |
| dataclass = dataclasses.dataclass | |
| from typing import List | |
| import ctypes | |
| """ | |
| INIT_CONSTANTS_CONTENT = """ | |
| # --- Adding Pre-Defined Constants --- | |
| uint8_t = ctypes.c_uint8 | |
| uint32_t = ctypes.c_uint32 | |
| VkFlags = uint32_t | |
| int32_t = int | |
| uint64_t = ctypes.c_uint64 | |
| VkBool32 = bool | |
| VkDeviceSize = ctypes.c_uint64 | |
| size_t = ctypes.c_uint64 | |
| float_t = ctypes.c_float | |
| int64_t = ctypes.c_int64 | |
| uint16_t = ctypes.c_uint16 | |
| VkFlags64 = uint64_t | |
| """ | |
| # TODO: b/415706521 (Refactor EXTRA_STRUCTURES_CONTENT Logic to Use XML Generation) | |
| EXTRA_STRUCTURES_CONTENT = """ | |
| @dataclass | |
| class VkExtent3D: | |
| width: uint32_t | |
| height: uint32_t | |
| depth: uint32_t | |
| @dataclass | |
| class VkImageFormatProperties: | |
| maxExtent: VkExtent3D | |
| maxMipLevels: uint32_t | |
| maxArrayLayers: uint32_t | |
| sampleCounts: VkSampleCountFlags | |
| maxResourceSize: VkDeviceSize | |
| @dataclass | |
| class VkExtensionProperties: | |
| extensionName: str | |
| specVersion: uint32_t | |
| @dataclass | |
| class VkFormatProperties: | |
| linearTilingFeatures: VkFormatFeatureFlags | |
| optimalTilingFeatures: VkFormatFeatureFlags | |
| bufferFeatures: VkFormatFeatureFlags | |
| @dataclass | |
| class VkLayerProperties: | |
| layerName: str | |
| specVersion: uint32_t | |
| implementationVersion: uint32_t | |
| description: str | |
| @dataclass | |
| class VkQueueFamilyProperties: | |
| queueFlags: VkQueueFlags | |
| queueCount: uint32_t | |
| timestampValidBits: uint32_t | |
| minImageTransferGranularity: VkExtent3D | |
| """ | |
| # TODO: b/415706507 (Find a way to identify this structs from vk.xml API_1_0 tag) | |
| VULKAN_API_1_0_STRUCTS_CONTENT = """# --- STRUCTS USED BY VULKAN_API_1_0 --- | |
| VULKAN_API_1_0_STRUCTS = [ | |
| VkPhysicalDeviceProperties, | |
| VkPhysicalDeviceMemoryProperties, | |
| VkPhysicalDeviceSparseProperties, | |
| VkImageFormatProperties, | |
| VkQueueFamilyProperties, | |
| VkExtensionProperties, | |
| VkLayerProperties, | |
| VkFormatProperties, | |
| VkPhysicalDeviceLimits, | |
| VkPhysicalDeviceFeatures]""" | |
| ADDITIONAL_EXTENSION_INDEPENDENT_STRUCTS_CONTENT = """# --- ADDITIONAL EXTENSION INDEPENDENT STRUCTS --- | |
| ADDITIONAL_EXTENSION_INDEPENDENT_STRUCTS = ['VkPhysicalDeviceProperties', 'VkPhysicalDeviceFeatures', 'VkPhysicalDeviceMemoryProperties']""" | |
| # --- GENERAL CONSTANTS --- | |
| PRIMITIVE_DATA_TYPE = [ | |
| "float_t", | |
| "uint32_t", | |
| "uint8_t", | |
| "int32_t", | |
| "uint64_t", | |
| "size_t", | |
| "float", | |
| "VkBool32", | |
| "int64_t", | |
| "str", | |
| "VkDeviceSize", | |
| "uint16_t", | |
| ] | |
| FLOAT_ARRAY_REPLACEMENT_TYPE = "float_t" | |
| # --- VULKAN SPECIFIC CONSTANTS & KEYWORDS --- | |
| PHYSICAL_DEVICE_PREFIX = "VkPhysicalDevice" | |
| STANDARD_MEMBER_NAMES_TO_SKIP = ("sType", "pNext") | |
| VALID_ALIAS_FLAGS = ("VkFlags", "VkFlags64") | |
| VK_STRUCTURE_TYPE = "VkStructureType" | |
| VK_API_CONSTANTS = "API Constants" | |
| VK_API_FILTER = "vulkan,vulkansc" | |
| VK_VULKAN_FILTER = "vulkan" | |
| VK_VULKAN_SC_FILTER = "vulkansc" | |
| # --- XML SPECIFIC CONSTANTS --- | |
| # XML Element/Attribute Names | |
| TAG_TYPE = "type" | |
| TAG_MEMBER = "member" | |
| TAG_ENUM = "enum" | |
| TAG_ENUMS = "enums" | |
| TAG_EXTENSION = "extension" | |
| TAG_FEATURE = "feature" | |
| TAG_REQUIRE = "require" | |
| TAG_NAME = "name" | |
| ATTR_NAME = "name" | |
| ATTR_SUPPORTED = "supported" | |
| ATTR_PLATFORM = "platform" | |
| ATTR_CATEGORY = "category" | |
| ATTR_DEFINE = "define" | |
| ATTR_ALIAS = "alias" | |
| ATTR_STRUCT_EXTENDS = "structextends" | |
| ATTR_VALUES = "values" | |
| ATTR_VALUE = "value" | |
| ATTR_TYPE = "type" | |
| ATTR_LEN = "len" | |
| ATTR_API = "api" | |
| # XML Categories | |
| CAT_STRUCT = "struct" | |
| CAT_HANDLE = "handle" | |
| # --- REGULAR EXPRESSIONS --- | |
| # Pointer Regex Helpers | |
| CONST_POINTER_REGEX = re.compile(r"^const\s+([\w\s]+?)\s*\*\s*$") # Matches 'const <type> *' | |
| VOID_POINTER_REGEX = re.compile(r"\bvoid\s*\*") # Matches 'void*' as whole word | |
| CHAR_POINTER_REGEX = re.compile(r"\bchar\s*\*") # Matches 'char *' | |
| CONST_CHAR_POINTER_REGEX = re.compile(r"\bconst\s+char\s*\*") # Matches 'const char *' | |
| # Other Regex | |
| CORE_VERSION_REGEX = re.compile(r"^(VkPhysicalDevice)(Vulkan)(\d+)(Properties|Features)$") | |
| ARRAY_TAIL_REGEX = re.compile(r"\[\s*([^\]]+)\s*\]") # Matches array notation like [VK_UUID_SIZE] | |
| # --- GLOBAL DATA STRUCTURES (Parsed Data Storage) --- | |
| VK_PHYSICAL_STRUCT_NAMES = [] # Stores names starting with PHYSICAL_DEVICE_PREFIX | |
| ALL_STRUCT_NAMES = [] | |
| ALL_ENUM_NAMES = [] # Stores all unique enum type names found | |
| ALL_ALIAS_NAMES = [] # Stores all unique alias names found | |
| ALL_CONSTANTS = [ # Base types considered 'constant' or primitive-like | |
| "uint8_t", | |
| "uint32_t", | |
| "VkFlags", | |
| "int32_t", | |
| "uint64_t", | |
| "VkBool32", | |
| "VkDeviceSize", | |
| "size_t", | |
| "float_t", | |
| "int64_t", | |
| "uint16_t", | |
| "VkFlags64", | |
| "float", | |
| "str", | |
| "int", | |
| "double", | |
| ] | |
| NECESSARY_ENUMS = [] | |
| CORE_MAPPING_STRUCT_LIST = [] # Physical device structs that match the CORE_VERSION_REGEX pattern | |
| VK_PHYSICAL_DEVICE_DEPENDENT_STRUCTURES_LIST = [] # Structs that physical device structs depend on | |
| REQUIRED_STRUCT_NAMES = [] # All structs deemed necessary (physical device + dependencies) | |
| REQUIRED_DATA_MEMBERS = [] | |
| # Mappings derived from parsing | |
| alias_map: Dict[str, str] = {} # Maps alias name to original name | |
| feature_api_map: Dict[str, List[Dict[str, str]]] = defaultdict(list) # Maps feature name to list of {struct: type_enum} | |
| disabled_structs = [] | |
| structs_with_valid_extends = [] | |
| enum_member_map = {} | |
| additional_vk_format_values = {} | |
| # --- GENERAL UTILITY FUNCTIONS --- | |
| def write_py_file(file_handle: IO[str], content: str): | |
| """Writes the given content to the provided open file handle.""" | |
| file_handle.write(content) | |
| def get_element_text(element: Optional[ET.Element]) -> Optional[str]: | |
| """Safely retrieves and strips the text content from an XML element.""" | |
| if element is not None and element.text: | |
| return element.text.strip() | |
| return None | |
| def find_next_tag(parent: ET.Element, element: ET.Element) -> Optional[ET.Element]: | |
| """Finds the immediate next sibling XML element within the same parent.""" | |
| children = list(parent) | |
| try: | |
| index = children.index(element) | |
| if index + 1 < len(children): | |
| return children[index + 1] | |
| except ValueError: | |
| pass | |
| return None | |
| # --- XML LOADING FUNCTION --- | |
| def load_xml_registry(xml_file_path: Path) -> Optional[ET.Element]: | |
| """Loads and parses the Vulkan XML registry file into an ElementTree root object.""" | |
| if not xml_file_path.exists(): | |
| return None | |
| try: | |
| tree = ET.parse(xml_file_path) | |
| return tree.getroot() | |
| except ET.ParseError as e: | |
| return None | |
| # --- API CONSTANT & VERSION NUMBER PROCESSING --- | |
| def clean_api_constant_value(value: str, type_str: Optional[str]) -> str: | |
| """Cleans C-style suffixes and resolves specific Vulkan constants (like ~0) to their values.""" | |
| original_value = value | |
| # Remove common C numeric suffixes | |
| cleaned_value = value.replace("F", "").replace("f", "").replace("U", "").replace("L", "") | |
| # Handle special Vulkan max value constants | |
| if cleaned_value == "(~0)": | |
| if type_str == "uint32_t": | |
| return "4294967295" # 2^32 - 1 | |
| elif type_str == "uint64_t": | |
| return "0xFFFFFFFFFFFFFFFF" # 2^64 - 1 | |
| elif cleaned_value == "(~1)": | |
| if type_str == "uint32_t": | |
| return "4294967294" # 2^32 - 2 | |
| elif cleaned_value == "(~2)": | |
| if type_str == "uint32_t": | |
| return "4294967293" # 2^32 - 3 | |
| elif cleaned_value.startswith("(~"): | |
| # Unhandled bitwise negation, return original to avoid incorrect interpretation | |
| return original_value | |
| # Attempt to validate/return as hex, float, or int | |
| try: | |
| if cleaned_value.startswith("0x") or cleaned_value.startswith("0X"): | |
| int(cleaned_value, 16) | |
| return cleaned_value | |
| else: | |
| try: | |
| float(cleaned_value) | |
| return cleaned_value | |
| except ValueError: | |
| int(cleaned_value) | |
| return cleaned_value | |
| except ValueError: | |
| return original_value | |
| def write_constants(root: ET.Element, vk_py_file_handle: IO[str]): | |
| """Extracts and writes constants defined in the 'API Constants' section of the XML.""" | |
| api_constants: Dict[str, str] = {} | |
| # Find the specific <enums> tag for API constants | |
| api_constants_element = root.find(f'.//enums[@{ATTR_NAME}="{VK_API_CONSTANTS}"]') | |
| if api_constants_element is None: | |
| return api_constants | |
| # Iterate through each <enum> defining a constant | |
| for enum_element in api_constants_element.findall(f"./{TAG_ENUM}"): | |
| name = enum_element.get(ATTR_NAME) | |
| value = enum_element.get(ATTR_VALUE) | |
| type_str = enum_element.get(ATTR_TYPE) # Used by cleaner | |
| if name and value: | |
| # Clean the value (handle suffixes, special constants) | |
| cleaned_value = clean_api_constant_value(value, type_str) | |
| api_constants[name] = cleaned_value | |
| elif name and enum_element.get(ATTR_ALIAS): | |
| # Skip aliased constants here; they aren't assigned values directly | |
| pass | |
| content = "\n# --- API Constant values extracted from vk.xml ---\n" | |
| for name, value in api_constants.items(): | |
| content += f"{name} = {value}\n" | |
| content += "\n" | |
| write_py_file(vk_py_file_handle, content) | |
| def vk_make_api_version(variant: int, major: int, minor: int, patch: int) -> int: | |
| """Replicates the VK_MAKE_API_VERSION C macro logic to pack version numbers into an integer.""" | |
| # Bitwise operations to pack variant, major, minor, patch into a 32-bit integer | |
| packed_version = (variant << 29) | (major << 22) | (minor << 12) | patch | |
| return packed_version | |
| def extract_make_api_versions(root: ET.Element): | |
| """Extracts VK_API_VERSION_* defines and their corresponding (variant, major, minor, patch) tuples.""" | |
| version_defines = {} # {define_name: (variant, major, minor, patch)} | |
| # Find <type> tags categorized as 'define' that likely contain VK_MAKE_API_VERSION | |
| for type_tag in root.findall(f".//{TAG_TYPE}[@{ATTR_CATEGORY}='{ATTR_DEFINE}']"): | |
| name_elem = type_tag.find(ATTR_NAME) # The #define name (e.g., VK_API_VERSION_1_0) | |
| macro_elem = type_tag.find(ATTR_TYPE) # Should ideally contain 'VK_MAKE_API_VERSION' | |
| if name_elem is not None and macro_elem is not None: | |
| name = get_element_text(name_elem) | |
| macro_text = get_element_text(macro_elem) | |
| match_name = re.search(r"VK_API_VERSION(?:_\d+_\d+)?", name or "") | |
| if not match_name: | |
| continue | |
| full_text = "".join(type_tag.itertext()) | |
| macro_pattern = re.escape(macro_text) if macro_text else r"VK_MAKE_API_VERSION" | |
| match_args = re.search(rf"{macro_pattern}\s*\(([^)]*)\)", full_text) | |
| if match_args: | |
| version_tuple = tuple(map(int, match_args.group(1).split(","))) | |
| if len(version_tuple) == 4: | |
| version_defines[name] = version_tuple | |
| return version_defines | |
| def write_api_constants(xml_root: ET.Element, vk_py_file_handle: IO[str]): | |
| """Extracts VK_API_VERSION defines, computes their integer values, and writes them to vk.py.""" | |
| version_defines = extract_make_api_versions(xml_root) | |
| version_content = "\n# --- Computed VK_API_VERSION Constants ---\n" | |
| version_content += "VK_API_VERSION_MAP = {\n" | |
| version_defines_items = list(version_defines.items()) | |
| if(len(version_defines_items)>1): | |
| for name, version in version_defines_items[1:]: | |
| packed_value = vk_make_api_version(version[0], version[1], version[2], version[3]) | |
| version_content += f' "{name}": {packed_value},\n' | |
| version_content += "}\n" | |
| write_py_file(vk_py_file_handle, version_content) | |
| # --- ALIAS EXTRACTION & WRITING FUNCTIONS --- | |
| def fetch_flags_aliases(root: ET.Element) -> dict[str, str]: | |
| """Extracts C typedefs specifically for VkFlags and VkFlags64 aliases.""" | |
| vk_flags_aliases: dict[str, str] = {} | |
| # Find all <type> elements potentially containing typedefs | |
| for type_tag in root.findall(f".//{TAG_TYPE}"): | |
| # Check the element's text content for 'typedef' keyword | |
| if type_tag.text and "typedef" in type_tag.text: | |
| original_type_elem = type_tag.find(f"{TAG_TYPE}") # The original type being aliased | |
| alias_elem = type_tag.find(f"{TAG_NAME}") # The new alias name | |
| if original_type_elem is not None and alias_elem is not None: | |
| original_type = get_element_text(original_type_elem) | |
| alias = get_element_text(alias_elem) | |
| # Only capture aliases for types listed in VALID_ALIAS_FLAGS | |
| if original_type in VALID_ALIAS_FLAGS: | |
| if alias not in ALL_ALIAS_NAMES: | |
| ALL_ALIAS_NAMES.append(alias) | |
| vk_flags_aliases[alias] = original_type # Store {alias_name: original_type} | |
| return vk_flags_aliases | |
| def write_aliases( | |
| vk_py_file_handle: IO[str], | |
| aliases: dict[str, str], | |
| comment: str, | |
| filter_required_data_members: bool = False, | |
| ) -> None: | |
| """Writes extracted type aliases (VkFlags or struct aliases) as Python assignments to a file.""" | |
| content = f"\n{comment}\n" | |
| if filter_required_data_members: | |
| content += "\n".join(f"{alias} = {original}" for alias, original in aliases.items() if alias in REQUIRED_DATA_MEMBERS) | |
| else: | |
| content += "\n".join(f"{alias} = {original}" for alias, original in aliases.items() if is_valid_struct_name(original)) | |
| content += "\n\n" | |
| write_py_file(vk_py_file_handle, content) | |
| # --- ENUM EXTRACTION & WRITING FUNCTIONS --- | |
| def fetch_enums_with_type_attribute(root: ET.Element): | |
| """Parses all <enums> blocks (except API constants) to extract enum members and values.""" | |
| parsed_enums: Dict[str, Dict[str, Optional[str]]] = defaultdict(dict) | |
| processed_enums = set() # Keep track of processed enum types to avoid duplicates | |
| for enums_tag in root.findall(f"{TAG_ENUMS}"): | |
| enum_type_name = enums_tag.get(ATTR_NAME) # Name of the enum type (e.g., VkResult) | |
| # Skip if no name or if it's the special API constants block | |
| if not enum_type_name or enum_type_name == VK_API_CONSTANTS: | |
| continue | |
| if enum_type_name in processed_enums: | |
| continue | |
| # Add to global list if it's a newly encountered enum type | |
| if enum_type_name not in ALL_ENUM_NAMES: | |
| ALL_ENUM_NAMES.append(enum_type_name) | |
| enum_map = {} # Stores {member_name: value/bitpos} for the current enum type | |
| # Iterate through individual <enum> members within the <enums> block | |
| for enum_tag in enums_tag.findall(f"{TAG_ENUM}"): | |
| name = enum_tag.get(ATTR_NAME) # Enum member name (e.g., VK_SUCCESS) | |
| value = enum_tag.get(ATTR_VALUE) # Explicit value | |
| bitPos = enum_tag.get("bitpos") # Bit position (for bitmasks) | |
| if name is not None and (value is not None or bitPos is not None): | |
| # Prefer bitpos if value is missing (common for bitmasks) | |
| if not value and bitPos: | |
| enum_map[name] = bitPos | |
| else: | |
| enum_map[name] = value | |
| parsed_enums[enum_type_name] = enum_map | |
| processed_enums.add(enum_type_name) | |
| return parsed_enums | |
| def write_enums(vk_py_file_handle: IO[str], enums: dict[str, dict[str, Optional[str]]]) -> None: | |
| """Writes necessary enum definitions as Python Enum classes and generates C++ traits string.""" | |
| content = "\n# --- Enum Definitions ---\n" | |
| # Iterate through all parsed enum types | |
| for enum_name, members in enums.items(): | |
| # Only generate code for enums that were deemed necessary (used by structs) | |
| if enum_name not in NECESSARY_ENUMS: | |
| continue | |
| enum_member_map[enum_name] = members | |
| # --- Python Enum class generation --- | |
| content += f"class {enum_name}(Enum):\n" | |
| if members: | |
| for member, value in members.items(): | |
| # Ensure both member name and value/bitpos exist | |
| if value is not None and member: | |
| content += f" {member} = {value}\n" | |
| if enum_name == "VkFormat": | |
| enum_member_map[enum_name].update(additional_vk_format_values) | |
| for member, value in additional_vk_format_values.items(): | |
| # Ensure both member name and value/bitpos exist | |
| if value is not None and member: | |
| content += f" {member} = {int(value)}\n" | |
| else: | |
| content += " pass\n" | |
| content += "\n" | |
| write_py_file(vk_py_file_handle, content) | |
| # --- HANDLE EXTRACTION & WRITING FUNCTIONS --- | |
| def fetch_struct_handles(root: ET.Element): | |
| """Extracts a list of names for types categorized as 'handle' in the XML.""" | |
| created_class_names = [] | |
| # Find all <type> tags with category='handle' | |
| for type_tag in root.findall(f".//{TAG_TYPE}[@{ATTR_CATEGORY}='{CAT_HANDLE}']"): | |
| name_tag = type_tag.find("name") # Find the <name> child element | |
| if name_tag is not None and name_tag.text: | |
| # Basic sanitization: remove non-alphanumeric characters (except underscore) | |
| class_name = re.sub(r"[^a-zA-Z0-9_]", "", name_tag.text.strip()) | |
| if class_name and class_name not in created_class_names: | |
| created_class_names.append(class_name) | |
| # Add handle names to the global list of all struct-like types | |
| if class_name not in ALL_STRUCT_NAMES: | |
| ALL_STRUCT_NAMES.append(class_name) | |
| return created_class_names | |
| def write_empty_dataclasses(dataclasses_list: List[str], vk_py_file_handle: IO[str]): | |
| """Generates empty Python dataclasses for Vulkan handle types and adds predefined common structs.""" | |
| created_class_names = set() # Track generated classes to avoid duplicates | |
| content_to_append = "\n# --- Empty Handle Dataclasses ---\n" | |
| # Generate empty dataclasses only for handles that are dependencies of physical device structs | |
| for class_name in dataclasses_list: | |
| if class_name not in VK_PHYSICAL_DEVICE_DEPENDENT_STRUCTURES_LIST: | |
| continue | |
| if class_name not in created_class_names: | |
| content_to_append += f"@dataclass\n" | |
| content_to_append += f"class {class_name}:\n" | |
| content_to_append += f" pass\n\n" | |
| created_class_names.add(class_name) | |
| # Add manually defined common Vulkan struct definitions | |
| content_to_append += "# --- Pre-defined Struct Definitions ---\n" | |
| content_to_append += EXTRA_STRUCTURES_CONTENT | |
| content_to_append += "\n" | |
| write_py_file(vk_py_file_handle, content_to_append) | |
| # --- STRUCT DEFINITION PROCESSING (Parsing & Writing vk_definitions.py content) --- | |
| # --- Struct Validation Helpers --- | |
| def is_valid_struct_name(struct_name): | |
| """Checks if a struct name starts with the physical device prefix and is not invalid.""" | |
| return struct_name.startswith(PHYSICAL_DEVICE_PREFIX) | |
| def check_valid_struct_extends(struct_extends): | |
| """Validates if a struct extends one of the base physical device property/feature structs.""" | |
| return "VkPhysicalDeviceProperties2" in struct_extends or "VkPhysicalDeviceFeatures2" in struct_extends | |
| def check_if_valid_struct(struct_name, alias_name): | |
| """Determines if a struct or its alias is considered valid for processing (not None or disabled).""" | |
| if not struct_name: | |
| return False | |
| # Check both original and alias name against the disabled list | |
| if struct_name in disabled_structs or alias_name in disabled_structs: | |
| return False | |
| return True | |
| # --- Type Formatting for Struct Members --- | |
| def format_data_types(c_type: str, array_size_str: Optional[str], struct_name_context: str) -> str: | |
| """Converts C type strings (including pointers, arrays) into Python type hints.""" | |
| py_type_hint = c_type # Default to original C type if no specific rule matches | |
| is_list = False # Flag indicating if the type should be List[...] | |
| shouldSkipCheck = False # Flag to bypass the unknown type check | |
| # 1. Handle specific pointer types first | |
| if CONST_CHAR_POINTER_REGEX.search(c_type) or CHAR_POINTER_REGEX.search(c_type): | |
| py_type_hint = "str" # 'const char*' or 'char*' maps to Python 'str' | |
| elif VOID_POINTER_REGEX.search(c_type): | |
| py_type_hint = "object" # 'void*' maps to Python 'object' (or could use 'Any') | |
| # 2. Handle general 'const <type> *' (pointer to const) -> 'List[<type>]' | |
| elif const_ptr_match := CONST_POINTER_REGEX.match(c_type): | |
| base_type = const_ptr_match.group(1).strip() # Extract the base type | |
| py_type_hint = base_type | |
| is_list = True # Pointers generally map to lists | |
| # 3. Handle general non-const '<type> *' -> 'List[<type>]' | |
| elif c_type.strip().endswith("*") and not py_type_hint == "object": | |
| base_type = c_type.strip().replace("*", "").strip() | |
| py_type_hint = base_type | |
| is_list = True # Pointers map to lists | |
| # 4. Handle C-style arrays '[SIZE]' if 'array_size_str' is provided | |
| elif array_size_str: | |
| base_type = c_type # Type before the brackets | |
| if base_type == "float": | |
| # Represent float arrays using a specific type hint format if needed | |
| py_type_hint = f"{FLOAT_ARRAY_REPLACEMENT_TYPE} * {array_size_str}" | |
| shouldSkipCheck = True # Skip check for this specific format | |
| elif base_type == "char": | |
| # Fixed-size char arrays in C often represent strings | |
| py_type_hint = "str" | |
| elif base_type in PRIMITIVE_DATA_TYPE: | |
| # Represent primitive arrays using a specific format if needed (e.g., for ctypes) | |
| py_type_hint = f"{base_type} * {array_size_str}" | |
| shouldSkipCheck = True | |
| else: | |
| # Assume arrays of non-primitives (structs, enums) map to List | |
| py_type_hint = base_type | |
| is_list = True | |
| # 5. If none of the above, it's a simple type name (e.g., uint32_t, VkResult) | |
| else: | |
| py_type_hint = c_type # Use the C type name directly | |
| # --- Clean up and Final Check --- | |
| py_type_hint = py_type_hint.replace("const ", "").strip() # Remove 'const ' prefix | |
| core_type_for_check = py_type_hint # The base type after initial conversion | |
| # Verify the resulting base Python type hint is known (primitive, struct, enum, alias) | |
| if ( | |
| not shouldSkipCheck | |
| and core_type_for_check not in ALL_STRUCT_NAMES | |
| and core_type_for_check not in ALL_ENUM_NAMES | |
| and core_type_for_check not in ALL_ALIAS_NAMES | |
| and core_type_for_check not in ALL_CONSTANTS | |
| ): | |
| raise Exception(f"Warning: Unknown base type '{core_type_for_check}' derived from C type '{c_type}' " f"in struct '{struct_name_context}'") | |
| if is_list: | |
| py_type_hint = f"List[{py_type_hint}]" | |
| return py_type_hint | |
| # --- Core Struct Parsing --- | |
| def fetch_all_structs_and_aliases( | |
| struct_elements: List[ET.Element], | |
| ) -> Tuple[Dict[str, List[Tuple[str, str, Optional[str]]]], Dict[str, str]]: | |
| """ | |
| Parses VkPhysicalDevice struct definitions and aliases from XML. | |
| Extracts member details (name, C type, array size) for each struct, | |
| handles aliases, and populates global lists for dependency tracking | |
| (e.g., `VK_PHYSICAL_STRUCT_NAMES`, `REQUIRED_STRUCT_NAMES`, `NECESSARY_ENUMS`). | |
| """ | |
| parsed_structs: Dict[str, List[Tuple[str, str, Optional[str]]]] = {} # {struct_name: [(member_name, c_type, array_size)]} | |
| struct_alias_data = {} # Stores {alias_name: original_struct_name} | |
| for struct_elem in struct_elements: | |
| struct_name = struct_elem.get(ATTR_NAME) | |
| alias_name = struct_elem.get(ATTR_ALIAS) | |
| is_struct_of_interest = is_valid_struct_name(struct_name) | |
| # Skip if the struct (or its alias) is invalid or disabled | |
| if not check_if_valid_struct(struct_name, alias_name): | |
| continue | |
| # Add if structs has valid structextends ('VkPhysicalDeviceProperties2' or 'VkPhysicalDeviceFeatures2') | |
| if struct_name in structs_with_valid_extends: | |
| if struct_name not in VK_PHYSICAL_STRUCT_NAMES: | |
| VK_PHYSICAL_STRUCT_NAMES.append(struct_name) | |
| # --- Handle Aliases --- | |
| if alias_name: | |
| struct_alias_data[struct_name] = alias_name | |
| continue # Don't process members for the alias tag itself | |
| # --- Process Actual Struct Definitions --- | |
| if is_struct_of_interest: | |
| if struct_name not in REQUIRED_STRUCT_NAMES: | |
| REQUIRED_STRUCT_NAMES.append(struct_name) | |
| struct_members: List[Tuple[str, str, Optional[str]]] = [] | |
| # Iterate through <member> tags within the struct definition | |
| for member_elem in struct_elem.findall(TAG_MEMBER): | |
| member_type_tag = member_elem.find(TAG_TYPE) | |
| member_name_tag = member_elem.find(TAG_NAME) | |
| member_type = get_element_text(member_type_tag) # C type (e.g., 'uint32_t', 'const char*') | |
| member_name = get_element_text(member_name_tag) # Member variable name | |
| member_array_size: Optional[str] = None # Extracted array size (like 'VK_UUID_SIZE') | |
| member_type_c = member_type # Keep the original C type string | |
| # Skip standard Vulkan struct members (sType, pNext) | |
| if member_name in STANDARD_MEMBER_NAMES_TO_SKIP: | |
| continue | |
| # Determine base type for dependency checking (strip const/pointer) | |
| base_type_for_necessity_check = member_type_c.replace("const", "").replace("*", "").strip() | |
| # --- Determine Array Size (from 'len' attribute or '[size]' notation) --- | |
| len_attr = member_elem.get(ATTR_LEN) | |
| if len_attr and len_attr.strip().lower() != "null-terminated": | |
| member_array_size = len_attr.strip() | |
| elif member_name_tag is not None: # Check for '[size]' notation after <name> tag | |
| tail_text = member_name_tag.tail.strip() if member_name_tag.tail else "" | |
| next_tag = find_next_tag(member_elem, member_name_tag) | |
| # Case 2a: Size defined by an adjacent <enum> tag: <name>type_name</name>[<enum>SIZE</enum>] | |
| if tail_text.startswith("[") and next_tag is not None and next_tag.tag == TAG_ENUM: | |
| enum_text = get_element_text(next_tag) | |
| enum_tail = next_tag.tail.strip() if next_tag.tail else "" | |
| if enum_text and enum_tail.endswith("]"): | |
| member_array_size = enum_text | |
| # Case 2b: Size defined in plain text in tail: <name>type_name</name>[32] | |
| elif not member_array_size: | |
| match = ARRAY_TAIL_REGEX.search(tail_text) | |
| if match: | |
| member_array_size = match.group(1).strip() | |
| # --- Dependency Tracking --- | |
| # If parsing a physical device struct and a member is a non-primitive type, | |
| # mark that type as a required dependency. | |
| if is_struct_of_interest and base_type_for_necessity_check not in PRIMITIVE_DATA_TYPE: | |
| if ( | |
| base_type_for_necessity_check and base_type_for_necessity_check != "void" and base_type_for_necessity_check not in REQUIRED_STRUCT_NAMES | |
| ): # Check if the type is valid, not void, and not already required | |
| REQUIRED_STRUCT_NAMES.append(base_type_for_necessity_check) | |
| # Also track as a direct dependency of physical device structs | |
| if base_type_for_necessity_check not in VK_PHYSICAL_DEVICE_DEPENDENT_STRUCTURES_LIST: | |
| VK_PHYSICAL_DEVICE_DEPENDENT_STRUCTURES_LIST.append(base_type_for_necessity_check) | |
| if member_type and member_name: | |
| if struct_name in REQUIRED_STRUCT_NAMES: | |
| # Adding all required ENUMs | |
| if base_type_for_necessity_check in ALL_ENUM_NAMES and base_type_for_necessity_check not in NECESSARY_ENUMS: | |
| NECESSARY_ENUMS.append(base_type_for_necessity_check) | |
| if base_type_for_necessity_check not in REQUIRED_DATA_MEMBERS: | |
| REQUIRED_DATA_MEMBERS.append(base_type_for_necessity_check) | |
| struct_members.append((member_name, member_type, member_array_size)) | |
| if struct_name: | |
| parsed_structs[struct_name] = struct_members | |
| return parsed_structs, struct_alias_data | |
| # --- Writing Struct Dataclasses --- | |
| def write_structs(vk_py_file_handle: IO[str], structs: Dict[str, List[Tuple[str, str, Optional[str]]]]) -> None: | |
| """Writes Python dataclass definitions for parsed structs, ensuring dependencies are written first.""" | |
| content = "\n# --- Vulkan Struct Definitions (Dependencies first, then PhysicalDevice structs) ---\n" | |
| local_content = "" | |
| # Helper function to generate the @dataclass string for a single struct | |
| def process_struct(class_name: str, member_list: List[Tuple[str, str, Optional[str]]]): | |
| nonlocal local_content | |
| local_content += f"@dataclass\nclass {class_name}:\n" | |
| if member_list: | |
| for member_name, member_type_c, size_val in member_list: | |
| # Convert the C type to a Python type hint | |
| python_type_hint = format_data_types(member_type_c, size_val, class_name) | |
| local_content += f" {member_name}: {python_type_hint}\n" # Add member line | |
| else: | |
| # Handle structs with no members (e.g., placeholder structs) | |
| local_content += " pass\n" | |
| local_content += "\n" # Add blank line after class definition | |
| # --- Write Structs in Dependency Order --- | |
| # First pass: Write structs that are dependencies of physical device structs | |
| for class_name, member_list in structs.items(): | |
| if class_name in VK_PHYSICAL_DEVICE_DEPENDENT_STRUCTURES_LIST: | |
| process_struct(class_name, member_list) | |
| # Second pass: Write the required physical device structs themselves (if not already written as dependencies) | |
| for class_name, member_list in structs.items(): | |
| if class_name in REQUIRED_STRUCT_NAMES and class_name not in VK_PHYSICAL_DEVICE_DEPENDENT_STRUCTURES_LIST: | |
| process_struct(class_name, member_list) | |
| # Combine the initial comment with the generated struct definitions | |
| content += local_content | |
| write_py_file(vk_py_file_handle, content) | |
| # --- STRUCT & EXTENSION MAPPING LOGIC (Processing for vk.py content) --- | |
| # --- Initial Filtering based on XML attributes/API types --- | |
| def create_disabled_struct_list(struct_elements: List[ET.Element]) -> None: | |
| """Identifies structs to disable based on invalid suffixes or if they don't extend expected base structs.""" | |
| for type_element in struct_elements: | |
| struct_name = type_element.get(ATTR_NAME) | |
| structextends = type_element.get(ATTR_STRUCT_EXTENDS) # Check the 'structextends' attribute | |
| # Condition for disabling a struct: | |
| # 1. It has a 'structextends' attribute, but it doesn't reference the valid base structs ('VkPhysicalDeviceProperties2' or 'VkPhysicalDeviceFeatures2') | |
| is_invalid_extends = structextends and not check_valid_struct_extends(struct_extends=structextends) | |
| if is_invalid_extends: | |
| if struct_name and struct_name not in disabled_structs: | |
| disabled_structs.append(struct_name) | |
| def extract_vulkan_sc_features(root: ET.Element): | |
| """Identifies physical device structs required only by Vulkan SC API and adds them to the disabled list.""" | |
| # Find <feature> tags specific to the 'vulkansc' API | |
| feature_tags = root.findall(f'.//{TAG_FEATURE}[@{ATTR_API}="{VK_VULKAN_SC_FILTER}"]') | |
| for feature in feature_tags: | |
| feature_name = feature.get(ATTR_NAME) | |
| if not feature_name: | |
| continue | |
| # Find types required by this Vulkan SC feature | |
| for require_tag in feature.findall(f"./{TAG_REQUIRE}"): | |
| for type_tag in require_tag.findall(f"./{TAG_TYPE}"): | |
| type_name = type_tag.get(ATTR_NAME) | |
| # If it's a physical device struct, add it to the disabled list | |
| if type_name and type_name.startswith(PHYSICAL_DEVICE_PREFIX): | |
| if type_name not in disabled_structs: | |
| disabled_structs.append(type_name) | |
| # --- Core Mappings for Structs --- | |
| def extract_struct_to_type_mapping(struct_elements: List[ET.Element]) -> Dict[str, str]: | |
| """ | |
| Maps Vulkan struct names (including aliases) to their VkStructureType enum strings. | |
| Identifies core version structs for `CORE_MAPPING_STRUCT_LIST` and structs | |
| with valid 'structextends' attributes for `structs_with_valid_extends`. | |
| Resolves aliases to find their sType values. | |
| """ | |
| struct_to_type_map: Dict[str, str] = {} # {struct_name: sType_enum_value} | |
| # --- First Pass: Extract Direct sType Values --- | |
| for type_elem in struct_elements: | |
| struct_name = type_elem.get(ATTR_NAME) | |
| alias_name = type_elem.get(ATTR_ALIAS) | |
| struct_extends = type_elem.get(ATTR_STRUCT_EXTENDS) | |
| if struct_name not in ALL_STRUCT_NAMES: | |
| ALL_STRUCT_NAMES.append(struct_name) | |
| # Skip invalid, disabled, or non-physical device structs | |
| if not check_if_valid_struct(struct_name, alias_name) or not is_valid_struct_name(struct_name): | |
| continue | |
| # Handle Aliases: Store alias mapping and skip member processing for this tag | |
| if alias_name: | |
| alias_map[struct_name] = alias_name # Store {alias: original} | |
| if alias_name in structs_with_valid_extends: | |
| if struct_name not in structs_with_valid_extends: | |
| structs_with_valid_extends.append(struct_name) | |
| continue | |
| # Track structs that don't extend the expected base types | |
| if struct_extends and check_valid_struct_extends(struct_extends=struct_extends): | |
| if struct_name not in structs_with_valid_extends: | |
| structs_with_valid_extends.append(struct_name) | |
| # Track core physical device structs (e.g., VkPhysicalDeviceVulkan11Properties) separately | |
| if struct_name not in CORE_MAPPING_STRUCT_LIST: | |
| match = CORE_VERSION_REGEX.match(struct_name) | |
| if match: | |
| CORE_MAPPING_STRUCT_LIST.append(struct_name) | |
| # Find the 'sType' member to get the structure type enum value | |
| for member_elem in type_elem.findall(TAG_MEMBER): | |
| name_elem = member_elem.find(TAG_NAME) | |
| if get_element_text(name_elem) == "sType": | |
| values = member_elem.get(ATTR_VALUES) | |
| if values: | |
| struct_to_type_map[struct_name] = values # Store {struct_name: sType_value} | |
| break | |
| # --- Second Pass: Resolve Aliases --- | |
| # Helper function to recursively find the sType value by following alias chains | |
| def resolve_alias_chain(alias: str, visited=None) -> Optional[str]: | |
| if visited is None: | |
| visited = set() | |
| if alias in visited: | |
| return None | |
| visited.add(alias) | |
| target = alias_map.get(alias) # Get the name the alias points to | |
| if not target: | |
| return None | |
| if target in struct_to_type_map: | |
| return struct_to_type_map[target] | |
| # Otherwise, if the target is itself an alias, resolve it recursively | |
| elif target in alias_map: | |
| return resolve_alias_chain(target, visited) | |
| else: | |
| return None | |
| # Resolve sType values for all found struct aliases and add them to the map | |
| resolved_aliases = {} | |
| for alias, original in alias_map.items(): | |
| # Only resolve if the original struct is a relevant physical device struct | |
| if is_valid_struct_name(original): | |
| resolved_value = resolve_alias_chain(alias) | |
| if resolved_value: | |
| resolved_aliases[alias] = resolved_value | |
| struct_to_type_map.update(resolved_aliases) | |
| return struct_to_type_map | |
| def extract_struct_extends_mapping(struct_elements: List[ET.Element]) -> Dict[str, str]: | |
| alias_struct_name_to_target_name_map: Dict[str, str] = {} # {alias_name: name_it_points_to} | |
| # Stores structextends for non-alias structs that define it | |
| defined_struct_name_to_struct_extends_map: Dict[str, str] = {} | |
| all_declared_struct_names: Set[str] = set() # Stores all names from type_elem.get(ATTR_NAME) | |
| # --- First Pass: Collect direct structextends and alias definitions --- | |
| for type_elem in struct_elements: | |
| struct_name = type_elem.get(ATTR_NAME) | |
| if not check_if_valid_struct(struct_name, None): | |
| continue | |
| all_declared_struct_names.add(struct_name) | |
| alias_target = type_elem.get(ATTR_ALIAS) | |
| struct_extends_value = type_elem.get(ATTR_STRUCT_EXTENDS) | |
| if struct_extends_value and not check_valid_struct_extends(struct_extends_value): | |
| continue | |
| if alias_target: | |
| # This struct_name is an alias for alias_target | |
| alias_struct_name_to_target_name_map[struct_name] = alias_target | |
| elif struct_extends_value: | |
| # This struct is not an alias and has a structextends attribute directly | |
| defined_struct_name_to_struct_extends_map[struct_name] = struct_extends_value | |
| # --- Second Pass: Resolve structextends for all struct names (including aliases) --- | |
| final_struct_name_to_resolved_struct_extends_map: Dict[str, str] = {} | |
| def get_resolved_struct_extends(name_to_resolve: str, visited_in_chain: Set[str]) -> Optional[str]: | |
| """ | |
| Recursively resolves the structextends value for a given name. | |
| - If name_to_resolve has a direct structextends, returns it. | |
| - If name_to_resolve is an alias, follows the alias chain. | |
| - Returns None if no structextends is found or a cycle is detected. | |
| """ | |
| if name_to_resolve in visited_in_chain: | |
| # Cycle detected in alias chain | |
| return None | |
| visited_in_chain.add(name_to_resolve) | |
| # Case 1: The name_to_resolve is a non-alias struct that directly defines structextends | |
| if name_to_resolve in defined_struct_name_to_struct_extends_map: | |
| return defined_struct_name_to_struct_extends_map[name_to_resolve] | |
| # Case 2: The name_to_resolve is an alias. Follow the chain. | |
| if name_to_resolve in alias_struct_name_to_target_name_map: | |
| target_name = alias_struct_name_to_target_name_map[name_to_resolve] | |
| return get_resolved_struct_extends(target_name, visited_in_chain) | |
| # Case 3: The name is not an alias we know of, and not in the direct map. | |
| # This means it's an original struct without 'structextends', or an unresolvable name. | |
| return None | |
| # Iterate through all unique struct names found in the XML | |
| for name in all_declared_struct_names: | |
| resolved_extends_value = get_resolved_struct_extends(name, set()) | |
| if resolved_extends_value: | |
| final_struct_name_to_resolved_struct_extends_map[name] = resolved_extends_value | |
| return final_struct_name_to_resolved_struct_extends_map | |
| def write_structs_extends_mapping(struct_elements: List[ET.Element], vk_py_file_handle: IO[str]): | |
| struct_extends_mapping_dict = extract_struct_extends_mapping(struct_elements) | |
| struct_extends_mapping_str = pprint.pformat(struct_extends_mapping_dict, indent=4, width=100) | |
| content = f"\n# --- STRUCT EXTENDS MAPPINGS ---\nSTRUCT_EXTENDS_MAPPING = {struct_extends_mapping_str}\n\n" | |
| write_py_file(vk_py_file_handle, content) | |
| # --- Feature/Extension/Core Version Based Mappings --- | |
| def extract_feature_struct_mapping(root: ET.Element, struct_to_type_map: Dict[str, str], api_filter: str): | |
| """ | |
| Maps Vulkan API features (e.g., VK_API_VERSION_1_1) to PhysicalDevice structs they introduce. | |
| Populates the global `feature_api_map`. Filters structs based on: | |
| - Validity (PhysicalDevice, not disabled). | |
| - Having valid 'structextends' (in `structs_with_valid_extends`). | |
| - Not being a core version struct (not in `CORE_MAPPING_STRUCT_LIST`). | |
| """ | |
| # Find <feature> tags matching the specified API filter (e.g., 'vulkan') | |
| feature_tags = root.findall(f'.//{TAG_FEATURE}[@{ATTR_API}="{api_filter}"]') | |
| for feature in feature_tags: | |
| feature_name = feature.get(ATTR_NAME) # e.g., "VK_API_VERSION_1_1" | |
| if not feature_name: | |
| continue | |
| if feature_name not in feature_api_map: | |
| feature_api_map[feature_name] = [] | |
| # Find <require> blocks within the feature | |
| for require_tag in feature.findall(f"./{TAG_REQUIRE}"): | |
| # Find <type> tags specifying required types (structs, enums, etc.) | |
| for type_tag in require_tag.findall(f"./{TAG_TYPE}"): | |
| type_name = type_tag.get(ATTR_NAME) | |
| is_struct_of_interest = is_valid_struct_name(type_name) | |
| # --- Skip if not relevant --- | |
| if type_name in disabled_structs: | |
| continue | |
| if type_name not in structs_with_valid_extends: | |
| continue | |
| if type_name in CORE_MAPPING_STRUCT_LIST: | |
| continue # Core structs handled separately | |
| # --- Process relevant structs --- | |
| if type_name and is_struct_of_interest: | |
| structure_type = struct_to_type_map.get(type_name) | |
| if not structure_type: # Skip if sType couldn't be found (shouldn't happen for valid structs) | |
| continue | |
| # Create the mapping {struct_name: sType_value} | |
| struct_map = {type_name: structure_type} | |
| if struct_map not in feature_api_map[feature_name]: | |
| feature_api_map[feature_name].append(struct_map) | |
| def extract_extension_struct_mapping(root: ET.Element, struct_to_type_map: Dict[str, str]) -> Tuple[Dict[str, List[Dict[str, str]]], Set[str]]: | |
| """ | |
| Maps enabled Vulkan extensions to their required PhysicalDevice structs. | |
| Filters extensions (e.g., not 'disabled', 'android' platform). Structs from | |
| skipped extensions are added to `disabled_structs`. | |
| For included extensions, structs must be: | |
| - Valid PhysicalDevice structs (not disabled). | |
| - Have valid 'structextends' (in `structs_with_valid_extends`). | |
| """ | |
| extension_map: Dict[str, List[Dict[str, str]]] = defaultdict(list) # {ext_name: [{struct: sType}]} | |
| extensions_found = root.findall(f".//{TAG_EXTENSION}") # Find all <extension> tags | |
| processed_structs: Set[str] = set() | |
| for extension in extensions_found: | |
| ext_name = extension.get(ATTR_NAME) # Extension name (e.g., "VK_KHR_surface") | |
| supported = extension.get(ATTR_SUPPORTED) # Usually "vulkan", "disabled", etc. | |
| platform = extension.get(ATTR_PLATFORM) # e.g., "android", "win32" | |
| if not ext_name: | |
| continue | |
| # --- Filter out disabled, non-standard, or unsupported platform extensions --- | |
| is_disabled_or_unsupported = supported == "disabled" or (platform and platform != "android") | |
| if is_disabled_or_unsupported: | |
| # If extension is skipped, add its required types to the global disabled list | |
| for require_tag in extension.findall(f"./{TAG_REQUIRE}"): | |
| type_names_list = [type_tag.get(ATTR_NAME) for type_tag in require_tag.findall(f"./{TAG_TYPE}") if type_tag.get(ATTR_NAME)] | |
| for type_name in type_names_list: | |
| if type_name not in disabled_structs: | |
| disabled_structs.append(type_name) | |
| continue # Skip processing this extension further | |
| # --- Process enabled/supported extensions --- | |
| current_ext_structs: List[Dict[str, str]] = [] # Structs required by *this* specific extension | |
| for require_tag in extension.findall(f"./{TAG_REQUIRE}"): | |
| # Get names of all types required by this section of the extension | |
| type_names_list = [type_tag.get(ATTR_NAME) for type_tag in require_tag.findall(f"./{TAG_TYPE}") if type_tag.get(ATTR_NAME)] | |
| for type_name in type_names_list: | |
| # Skip disabled structs | |
| if type_name in disabled_structs: | |
| continue | |
| # Process only relevant physical device structs not already added to the map | |
| if type_name not in processed_structs and type_name in ALL_STRUCT_NAMES: | |
| if "Properties" not in type_name and "Features" not in type_name: | |
| if type_name not in disabled_structs: | |
| disabled_structs.append(type_name) | |
| continue | |
| if type_name not in structs_with_valid_extends: | |
| continue | |
| # Look up the sType value from the precomputed map | |
| structure_type = struct_to_type_map.get(type_name) | |
| if structure_type: | |
| struct_map = {type_name: structure_type} | |
| # Add to this extension's list if not already present | |
| if struct_map not in current_ext_structs: | |
| current_ext_structs.append(struct_map) | |
| processed_structs.add(type_name) # Mark struct as processed globally | |
| if current_ext_structs: | |
| extension_map[ext_name] = current_ext_structs | |
| return dict(extension_map), processed_structs | |
| def generate_core_struct_mapping(struct_names: list[str], vk_py_file_handle: IO[str]) -> dict: | |
| """ | |
| Generates and writes the VULKAN_CORES_AND_STRUCTS_MAPPING dictionary. | |
| This map links core Vulkan versions (e.g., "Core11") to their specific | |
| PhysicalDevice Properties and Features structs (e.g., VkPhysicalDeviceVulkan11Features). | |
| It processes struct names from the `CORE_MAPPING_STRUCT_LIST`. | |
| """ | |
| STRUCTURE_TYPE_PREFIX_PART = "PHYSICAL_DEVICE" | |
| versions_map = defaultdict(list) # { "Core11": [{struct: sType}], ... } | |
| # Process struct names matching the core version pattern (e.g., VkPhysicalDeviceVulkan11Properties) | |
| for struct_name in struct_names: | |
| match = CORE_VERSION_REGEX.match(struct_name) | |
| if match: | |
| # Extract version digits (e.g., "11") and suffix (e.g., "Properties") | |
| _, _, version_digits, suffix = match.groups() | |
| core_key = f"Core{version_digits}" # Create key like "Core11", "Core12" | |
| # Format version for the enum name (e.g., "11" -> "1_1") | |
| if len(version_digits) == 2: | |
| version_snake = f"{version_digits[0]}_{version_digits[1]}" | |
| else: # Unlikely based on current vk.xml naming | |
| version_snake = version_digits | |
| # Construct the expected VkStructureType enum value name based on convention | |
| suffix_upper = suffix.upper() # "Properties" -> "PROPERTIES" | |
| structure_type_value = f"VK_STRUCTURE_TYPE_{STRUCTURE_TYPE_PREFIX_PART}_VULKAN_{version_snake}_{suffix_upper}" | |
| inner_map = {struct_name: structure_type_value} | |
| versions_map[core_key].append(inner_map) | |
| sorted_versions_map = dict(sorted(versions_map.items(), key=lambda item: int(item[0][4:]))) | |
| core_comment = """\ | |
| # VULKAN_CORES_AND_STRUCTS_MAPPING: Maps core Vulkan versions (e.g., "Core11") to their | |
| # specific PhysicalDevice Properties and Features structs. | |
| # Struct Filters: | |
| # - Name matches "VkPhysicalDeviceVulkan<Version><Properties|Features>" (from CORE_MAPPING_STRUCT_LIST). | |
| # - sType is programmatically derived. | |
| # Format: {"versions": {core_version_key: [{struct_name: sType_enum_value}, ...]}}""" | |
| allStructInfo = "\n# --- Vulkan Core Version to Struct Mappings ---\n" | |
| allStructInfo += core_comment + "\n" # Add the comment | |
| allStructInfo += """VULKAN_CORES_AND_STRUCTS_MAPPING = {"versions": """ | |
| allStructInfo += f"{pprint.pformat(sorted_versions_map, indent=4, width=100)}" | |
| allStructInfo += "}\n\n" | |
| write_py_file(vk_py_file_handle, allStructInfo) | |
| return {"versions": sorted_versions_map} | |
| # --- Ancillary Mappings & Lists for vk.py --- | |
| def extract_list_size_mapping(struct_elements: List[ET.Element]) : | |
| """Extracts mappings from list members to their corresponding size-indicating members within structs.""" | |
| member_map = {} # Stores {list_member_name: size_member_name} | |
| struct_with_dynamic_size_list_variables = {} # Stores {struct_name: list of list variables} | |
| for type_element in struct_elements: | |
| struct_name = type_element.get(ATTR_NAME) | |
| # Skip invalid or non-physical device structs | |
| if not check_if_valid_struct(struct_name, None) or not is_valid_struct_name(struct_name): | |
| continue | |
| # Find members within the struct that have a 'len' attribute | |
| for member_element in type_element.findall(f"./{TAG_MEMBER}[@{ATTR_LEN}]"): | |
| len_attribute_value = member_element.get(ATTR_LEN) # Name of the member holding the size | |
| name_tag = member_element.find(TAG_NAME) | |
| name_value = get_element_text(name_tag) # Name of the list/array member | |
| member_type_element = member_element.find(ATTR_TYPE) | |
| is_pointer = '*' in member_type_element.tail if member_type_element.tail else False | |
| # Store the mapping if valid (names exist, len is not "null-terminated") | |
| if name_value and len_attribute_value and len_attribute_value.strip().lower() != "null-terminated": | |
| member_map[name_value] = len_attribute_value.strip() | |
| if is_pointer: | |
| struct_with_dynamic_size_list_variables.setdefault(struct_name, []).append(name_value) | |
| return member_map, struct_with_dynamic_size_list_variables | |
| def write_list_size_mapping(struct_elements: List[ET.Element], vk_py_file_handle: IO[str]): | |
| def sort_dict_by_key_and_content(input_dict): | |
| """ | |
| Sorts a dictionary first by its keys alphabetically, | |
| and then sorts the elements within the sets associated with each key. | |
| """ | |
| sorted_items_by_key = sorted(input_dict.items()) | |
| sorted_dict = {} | |
| for key, value_set in sorted_items_by_key: | |
| sorted_dict[key] = sorted(list(value_set)) | |
| return sorted_dict | |
| def write_formatted_code(list_variable_name, list_data): | |
| formatted_list_code = pprint.pformat(list_data, indent=4, width=100) | |
| formatted_content = f"""{list_variable_name} = {formatted_list_code}\n\n""" | |
| write_py_file(vk_py_file_handle, formatted_content) | |
| """Extracts and writes the list-member-to-size-member mapping to a Python file.""" | |
| content = "# --- List Size Mappings (Field name to size field name) ---\n" | |
| write_py_file(vk_py_file_handle, content) | |
| list_size_map, dynamic_list_variables = extract_list_size_mapping(struct_elements) | |
| write_formatted_code("LIST_TYPE_FIELD_AND_SIZE_MAPPING", list_size_map) | |
| sorted_dynamic_list_variables = sort_dict_by_key_and_content(dynamic_list_variables) | |
| write_formatted_code("STRUCT_WITH_DYNAMIC_SIZE_LIST_MAPPING", sorted_dynamic_list_variables) | |
| def write_all_structs(vk_py_file_handle: IO[str]) -> None: | |
| """Writes a Python list of all processed PhysicalDevice struct names.""" | |
| VK_PHYSICAL_STRUCT_NAMES.sort() | |
| comment = ( | |
| "\n# --- List of All Processed Physical Device Structs ---\n" | |
| "# Includes structs that:\n" | |
| "# 1. Are not in 'disabled_structs' (implicitly, via VK_PHYSICAL_STRUCT_NAMES population).\n" | |
| '# 2. Extend "VkPhysicalDeviceProperties2" or "VkPhysicalDeviceFeatures2"\n' | |
| "# (i.e., are in 'structs_with_valid_extends')." | |
| ) | |
| allStructInfo = comment + "\n" | |
| allStructInfo += "ALL_STRUCTS_EXTENDING_FEATURES_OR_PROPERTIES = [\n" | |
| # Add each physical device struct name as a string element in the list | |
| for class_name in VK_PHYSICAL_STRUCT_NAMES: | |
| allStructInfo += f" {class_name},\n" | |
| allStructInfo += "]\n\n" | |
| write_py_file(vk_py_file_handle, allStructInfo) | |
| def write_extension_independent_structs(structs_in_extensions: Set[str], vk_py_file_handle: IO[str]): | |
| """Writes a list of PhysicalDevice structs not tied to specific features or enabled extensions.""" | |
| all_physical_device_structs = VK_PHYSICAL_STRUCT_NAMES | |
| # Find structs that are not disabled and not explicitly required by features/extensions | |
| independent_structs = [ | |
| struct | |
| for struct in all_physical_device_structs | |
| if struct not in disabled_structs and struct in structs_with_valid_extends and struct not in CORE_MAPPING_STRUCT_LIST and struct not in structs_in_extensions | |
| ] | |
| independent_structs.sort() | |
| comment = ( | |
| "\n# --- Extension Independent Structs ---\n" | |
| "# These are PhysicalDevice structs that meet the following criteria:\n" | |
| "# 1. Sourced from VK_PHYSICAL_STRUCT_NAMES.\n" | |
| "# 2. Not in the global 'disabled_structs' list.\n" | |
| '# 3. Extend "VkPhysicalDeviceProperties2" or "VkPhysicalDeviceFeatures2" (in \'structs_with_valid_extends\').\n' | |
| "# 4. Not core version-specific (not in CORE_MAPPING_STRUCT_LIST).\n" | |
| "# 5. Not required by any enabled Vulkan extension (not in 'structs_in_extensions')." | |
| ) | |
| content = comment + "\n" | |
| independent_structs_str = pprint.pformat(independent_structs, indent=4, width=100) | |
| content += f"EXTENSION_INDEPENDENT_STRUCTS = {independent_structs_str}\n\n" | |
| write_py_file(vk_py_file_handle, content) | |
| # --- Orchestration & Content Generation for Mappings --- | |
| def parse_structs_and_extensions(xml_root: ET.Element, struct_elements: List[ET.Element]): | |
| """ | |
| Helps in extraction of key mappings for Vulkan structs, features, and extensions. | |
| Calls helpers to: | |
| 1. Map structs to sTypes (`extract_struct_to_type_mapping`). | |
| 2. Map features to their structs (`extract_feature_struct_mapping`). | |
| 3. Map extensions to their structs (`extract_extension_struct_mapping`). | |
| Populates global lists/dicts as a side effect. | |
| """ | |
| # 1. Create the mapping from struct names (including aliases) to sType values | |
| struct_to_type_map = extract_struct_to_type_mapping(struct_elements) | |
| # 2. Extract feature-to-struct using api filters | |
| extract_feature_struct_mapping(xml_root, struct_to_type_map, VK_API_FILTER) | |
| extract_feature_struct_mapping(xml_root, struct_to_type_map, VK_VULKAN_FILTER) | |
| # 3. Extract extension-to-struct mappings for enabled extensions, also getting the set of structs used in extensions | |
| extension_map_data, structs_in_extensions = extract_extension_struct_mapping(xml_root, struct_to_type_map) | |
| return structs_in_extensions, dict(feature_api_map), extension_map_data | |
| def generate_vk_py_content( | |
| feature_map: Dict[str, List[Dict[str, str]]], | |
| extension_map: Dict[str, List[Dict[str, str]]], | |
| ) -> str: | |
| """ | |
| Formats feature and extension struct mappings for vk.py. | |
| Generates Python dictionary string definitions for: | |
| - VULKAN_EXTENSIONS_AND_STRUCTS_MAPPING | |
| - VULKAN_VERSIONS_AND_STRUCTS_MAPPING | |
| Includes comments detailing their generation filters. | |
| """ | |
| # --- Vulkan Extension to Struct Mappings --- | |
| extension_comment = """\ | |
| # VULKAN_EXTENSIONS_AND_STRUCTS_MAPPING: Maps enabled extension names to their PhysicalDevice structs. | |
| # Extension Filters: | |
| # - 'supported' is not "disabled". | |
| # - 'platform' (if present) is "android". | |
| # Struct Filters (per extension): | |
| # - Not in global 'disabled_structs'. | |
| # - Extends "VkPhysicalDeviceProperties2" or "VkPhysicalDeviceFeatures2". | |
| # Format: {ext_name: [{struct_name: sType_enum_value}, ...]}""" | |
| content = "\n# --- Vulkan Extension to Struct Mappings ---\n" | |
| content += extension_comment + "\n" | |
| sorted_data = dict(sorted(extension_map.items())) | |
| extension_map_str = pprint.pformat(sorted_data, indent=4, width=100) | |
| content += """VULKAN_EXTENSIONS_AND_STRUCTS_MAPPING = {"extensions":\n""" | |
| content += f"{extension_map_str}" | |
| content += "}\n\n" | |
| # --- Vulkan Feature to Struct Mappings --- | |
| feature_comment = """\ | |
| # VULKAN_VERSIONS_AND_STRUCTS_MAPPING: Maps API features (e.g., "VK_API_VERSION_1_1") | |
| # from "vulkan" API to PhysicalDevice structs they introduce. | |
| # Excludes core version structs (e.g., VkPhysicalDeviceVulkan11Properties), see VULKAN_CORES_AND_STRUCTS_MAPPING. | |
| # Struct Filters (per feature): | |
| # - Name: Starts "VkPhysicalDevice". | |
| # - Not in global 'disabled_structs'. | |
| # - Extends "VkPhysicalDeviceProperties2" or "VkPhysicalDeviceFeatures2". | |
| # - Not in CORE_MAPPING_STRUCT_LIST. | |
| # NOTE: | |
| # VK_VERSION_1_0" is empty as it does not map to any structure which passes our structure-filter criteria. | |
| # We have hardcoded the code-block for VK_VERSION_1_0 in vkjson_generator.py | |
| # Format: {feature_name: [{struct_name: sType_enum_value}, ...]}""" | |
| content += "# --- Vulkan Feature to Struct Mappings ---\n" | |
| content += feature_comment + "\n" | |
| feature_map_str = pprint.pformat(feature_map, indent=4, width=100) | |
| content += f"VULKAN_VERSIONS_AND_STRUCTS_MAPPING = {feature_map_str}\n\n" | |
| return content | |
| def write_initial_content(vk_py_file_handle: IO[str]): | |
| copyright_header = ( | |
| gencom.copyright_and_warning(datetime.now().year).replace("*/", "").replace("/*", "").replace(" *", "#").replace("// WARNING: This file is generated. See ../README.md for instructions.", "").strip() | |
| ) | |
| write_py_file(vk_py_file_handle, copyright_header) | |
| write_py_file(vk_py_file_handle, INIT_CONTENT + INIT_CONSTANTS_CONTENT) | |
| def extract_vkformat_enums(xml_root): | |
| """ | |
| Parses an XML root element to find <feature> and <extension> tags, | |
| and extracts names from specific <enum extends="VkFormat"> tags within them. | |
| """ | |
| result_map = {} | |
| format_offset_mapping = {} | |
| # Iterate over <feature> tags | |
| for feature_tag in xml_root.findall(f".//{TAG_FEATURE}"): | |
| feature_name = feature_tag.get(f"{ATTR_NAME}") | |
| if not feature_name: | |
| continue # Skip if feature tag has no name | |
| enum_names = [] | |
| for require_tag in feature_tag.findall(f".//{TAG_REQUIRE}"): | |
| for enum_tag in require_tag.findall(f'.//enum[@extends="VkFormat"]'): | |
| enum_name = enum_tag.get(f"{ATTR_NAME}") | |
| ext_number = enum_tag.get("extnumber") | |
| ext_offset = enum_tag.get("offset") | |
| calculated_extension = 0 | |
| if not ext_number or not ext_offset: | |
| raise Exception(f"Invalid Extension Found: {enum_name}") | |
| else: | |
| calculated_extension = 1000000000 + ((int(ext_number) - 1) * 1000) + int(ext_offset) | |
| format_offset_mapping[enum_name] = calculated_extension | |
| additional_vk_format_values[enum_name] = str(calculated_extension) | |
| if enum_name: | |
| enum_names.append((enum_name, calculated_extension)) | |
| if enum_names: # Only add if there are relevant enums | |
| if feature_name in result_map: | |
| result_map[feature_name].extend(enum_names) | |
| else: | |
| result_map[feature_name] = enum_names | |
| # Iterate over <extension> tags | |
| for extension_tag in xml_root.findall(f".//{TAG_EXTENSION}"): | |
| extension_name = extension_tag.get(f"{ATTR_NAME}") | |
| supported = extension_tag.get(ATTR_SUPPORTED) # Usually "vulkan", "disabled", etc. | |
| if supported == "disabled": | |
| continue | |
| ext_number = extension_tag.get("number") | |
| calculated_extension = 0 | |
| if not extension_name: | |
| continue # Skip if extension tag has no name | |
| enum_names = [] | |
| for require_tag in extension_tag.findall(f".//{TAG_REQUIRE}"): | |
| for enum_tag in require_tag.findall('.//enum[@extends="VkFormat"]'): | |
| enum_name = enum_tag.get(f"{ATTR_NAME}") | |
| ext_offset = enum_tag.get("offset") | |
| ext_alias = enum_tag.get("alias") | |
| if not ext_number: | |
| raise Exception(f"Invalid Extension Found: {enum_name}") | |
| else: | |
| if not ext_offset: | |
| if format_offset_mapping.get(ext_alias): | |
| calculated_extension = format_offset_mapping[ext_alias] | |
| else: | |
| calculated_extension = 1000000000 + ((int(ext_number) - 1) * 1000) + int(ext_offset) | |
| if not ext_alias: | |
| additional_vk_format_values[enum_name] = str(calculated_extension) | |
| if enum_name: | |
| enum_names.append((enum_name, calculated_extension)) | |
| if enum_names: # Only add if there are relevant enums | |
| if extension_name in result_map: | |
| result_map[extension_name].extend(enum_names) | |
| else: | |
| result_map[extension_name] = enum_names | |
| # Remove duplicates from lists if any were introduced by multiple require tags | |
| for key in result_map: | |
| result_map[key] = list(set(result_map[key])) | |
| result_map[key] = sorted(result_map[key], key=lambda x: x[0], reverse=True) | |
| result_map[key] = sorted(result_map[key], key=lambda x: x[1]) | |
| return result_map | |
| def copy_vulkan_1_0_enums(enums_data, vk_format_map): | |
| copy_vulkan_1_0_vkformats = copy.copy(enums_data["VkFormat"]) | |
| for name, value in copy_vulkan_1_0_vkformats.items(): | |
| if not vk_format_map.get("VK_VERSION_1_0"): | |
| vk_format_map["VK_VERSION_1_0"] = [] | |
| if int(value) == 0: | |
| continue | |
| vk_format_map["VK_VERSION_1_0"].append((name, int(value))) | |
| # --- CODEGEN EXECUTION --- | |
| def gen_vk(): | |
| """ | |
| Orchestrates parsing of Vulkan XML registry and generation of vk.py. | |
| Loads vk.xml, then calls a sequence of functions to: | |
| 1. Filter and identify structs (e.g., disable SC-specific ones). | |
| 2. Extract mappings: struct-to-sType, feature-to-structs, extension-to-structs. | |
| 3. Fetch detailed definitions for enums, aliases, structs, and handles. | |
| 4. Write all parsed and processed data into vk.py, including dataclasses, | |
| constants, aliases, and various mapping dictionaries. | |
| """ | |
| # Ensure output directory exists | |
| OUTPUT_VK_PY_PATH.parent.mkdir(parents=True, exist_ok=True) | |
| with open(OUTPUT_VK_PY_PATH, "w", encoding="utf-8") as vk_py_file_handle: | |
| write_initial_content(vk_py_file_handle) | |
| xml_root = load_xml_registry(VULKAN_XML_FILE_PATH) | |
| if xml_root is None: | |
| return | |
| all_struct_type_elements = xml_root.findall(f".//{TAG_TYPE}[@{ATTR_CATEGORY}='{CAT_STRUCT}']") | |
| create_disabled_struct_list(all_struct_type_elements) | |
| extract_vulkan_sc_features(xml_root) | |
| empty_dataclass_names = fetch_struct_handles(xml_root) | |
| structs_in_extensions, feature_map_data, extension_map_data = parse_structs_and_extensions(xml_root, all_struct_type_elements) | |
| vk_format_map = extract_vkformat_enums(xml_root) | |
| enums_data = fetch_enums_with_type_attribute(xml_root) | |
| aliases_vk_flags_data = fetch_flags_aliases(xml_root) | |
| all_structs_data, struct_alias_data = fetch_all_structs_and_aliases(all_struct_type_elements) | |
| copy_vulkan_1_0_enums(enums_data, vk_format_map) | |
| write_enums(vk_py_file_handle, enums_data) | |
| write_constants(xml_root, vk_py_file_handle) | |
| write_api_constants(xml_root, vk_py_file_handle) | |
| write_aliases(vk_py_file_handle, aliases_vk_flags_data, "# --- VkFlags Type Aliases ---", filter_required_data_members=True) | |
| write_empty_dataclasses(empty_dataclass_names, vk_py_file_handle) | |
| write_structs(vk_py_file_handle, all_structs_data) | |
| write_aliases(vk_py_file_handle, struct_alias_data, "# --- Physical Device Struct Aliases ---") | |
| write_all_structs(vk_py_file_handle) | |
| write_py_file(vk_py_file_handle, generate_vk_py_content(feature_map=feature_map_data, extension_map=extension_map_data)) | |
| write_extension_independent_structs(structs_in_extensions, vk_py_file_handle) | |
| generate_core_struct_mapping(CORE_MAPPING_STRUCT_LIST, vk_py_file_handle) | |
| write_list_size_mapping(all_struct_type_elements, vk_py_file_handle) | |
| write_py_file(vk_py_file_handle, VULKAN_API_1_0_STRUCTS_CONTENT + "\n\n") | |
| write_py_file(vk_py_file_handle, ADDITIONAL_EXTENSION_INDEPENDENT_STRUCTS_CONTENT + "\n") | |
| write_structs_extends_mapping(all_struct_type_elements, vk_py_file_handle) | |
| enum_traits_content = f"\n# --- Enum Traits Mapping ---\nENUM_TRAITS_MAPPING = {pprint.pformat(enum_member_map, indent=4, width=100, sort_dicts=False)}\n\n" | |
| write_py_file(vk_py_file_handle, enum_traits_content) | |
| vk_formats_content = f"\n# --- VK Format Mapping ---\nVK_FORMAT_MAPPING = {pprint.pformat(vk_format_map, indent=4, width=100, sort_dicts=False)}" | |
| write_py_file(vk_py_file_handle, vk_formats_content) |