blob: e320a7f80243e9ac2ae93961322662685bd69cb7 [file] [log] [blame]
# 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 ---
SCRIPT_DIR = Path(__file__).resolve().parent
VULKAN_XML_FILE_PATH = SCRIPT_DIR / "../../../../external/vulkan-headers/registry/vk.xml"
VULKAN_XML_FILE_PATH = VULKAN_XML_FILE_PATH.resolve()
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 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(xml_path = None, output_path = None):
"""
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.
"""
OUTPUT_VK_PY_PATH = output_path if output_path else SCRIPT_DIR / "vk.py"
# 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 = None
if xml_path is None:
xml_root = load_xml_registry(VULKAN_XML_FILE_PATH)
else:
xml_root = load_xml_registry(xml_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)