#!/usr/bin/env python3 # Wine Vulkan generator # # Copyright 2017-2018 Roderick Colenbrander # Copyright 2022 Jacek Caban for CodeWeavers # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA # import argparse import logging import os import re import sys import urllib.request import xml.etree.ElementTree as ET from collections import OrderedDict from collections.abc import Sequence from enum import Enum # This script generates code for a Wine Vulkan ICD driver from Vulkan's vk.xml. # Generating the code is like 10x worse than OpenGL, which is mostly a calling # convention passthrough. # # The script parses vk.xml and maps functions and types to helper objects. These # helper objects simplify the xml parsing and map closely to the Vulkan types. # The code generation utilizes the helper objects during code generation and # most of the ugly work is carried out by these objects. # # Vulkan ICD challenges: # - Vulkan ICD loader (vulkan-1.dll) relies on a section at the start of # 'dispatchable handles' (e.g. VkDevice, VkInstance) for it to insert # its private data. It uses this area to stare its own dispatch tables # for loader internal use. This means any dispatchable objects need wrapping. # # - Vulkan structures have different alignment between win32 and 32-bit Linux. # This means structures with alignment differences need conversion logic. # Often structures are nested, so the parent structure may not need any # conversion, but some child may need some. # # vk.xml parsing challenges: # - Contains type data for all platforms (generic Vulkan, Windows, Linux,..). # Parsing of extension information required to pull in types and functions # we really want to generate. Just tying all the data together is tricky. # # - Extensions can affect core types e.g. add new enum values, bitflags or # additional structure chaining through 'pNext' / 'sType'. # # - Arrays are used all over the place for parameters or for structure members. # Array length is often stored in a previous parameter or another structure # member and thus needs careful parsing. LOGGER = logging.Logger("vulkan") LOGGER.addHandler(logging.StreamHandler()) VK_XML_VERSION = "1.3.235" WINE_VK_VERSION = (1, 3) # Filenames to create. WINE_VULKAN_H = "../../include/wine/vulkan.h" WINE_VULKAN_DRIVER_H = "../../include/wine/vulkan_driver.h" WINE_VULKAN_LOADER_SPEC = "../vulkan-1/vulkan-1.spec" WINE_VULKAN_JSON = "winevulkan.json" WINE_VULKAN_SPEC = "winevulkan.spec" WINE_VULKAN_THUNKS_C = "vulkan_thunks.c" WINE_VULKAN_THUNKS_H = "vulkan_thunks.h" WINE_VULKAN_LOADER_THUNKS_C = "loader_thunks.c" WINE_VULKAN_LOADER_THUNKS_H = "loader_thunks.h" # Extension enum values start at a certain offset (EXT_BASE). # Relative to the offset each extension has a block (EXT_BLOCK_SIZE) # of values. # Start for a given extension is: # EXT_BASE + (extension_number-1) * EXT_BLOCK_SIZE EXT_BASE = 1000000000 EXT_BLOCK_SIZE = 1000 UNSUPPORTED_EXTENSIONS = [ # Instance extensions "VK_EXT_headless_surface", # Needs WSI work. "VK_KHR_display", # Needs WSI work. "VK_KHR_surface_protected_capabilities", # Device extensions "VK_AMD_display_native_hdr", "VK_EXT_full_screen_exclusive", "VK_EXT_hdr_metadata", # Needs WSI work. "VK_GOOGLE_display_timing", "VK_KHR_external_fence_win32", "VK_KHR_external_semaphore_win32", # Relates to external_semaphore and needs type conversions in bitflags. "VK_KHR_shared_presentable_image", # Needs WSI work. "VK_KHR_win32_keyed_mutex", "VK_NV_external_memory_rdma", # Needs shared resources work. # Extensions for other platforms "VK_EXT_external_memory_dma_buf", "VK_EXT_image_drm_format_modifier", "VK_EXT_metal_objects", "VK_EXT_physical_device_drm", "VK_GOOGLE_surfaceless_query", "VK_KHR_external_fence_fd", "VK_KHR_external_memory_fd", "VK_KHR_external_semaphore_fd", "VK_SEC_amigo_profiling", # Angle specific. # Extensions which require callback handling "VK_EXT_device_memory_report", # Deprecated extensions "VK_NV_external_memory_capabilities", "VK_NV_external_memory_win32", ] # Either internal extensions which aren't present on the win32 platform which # winevulkan may nonetheless use, or extensions we want to generate headers for # but not expose to applications (useful for test commits) UNEXPOSED_EXTENSIONS = { "VK_KHR_external_memory_win32", } # The Vulkan loader provides entry-points for core functionality and important # extensions. Based on vulkan-1.def this amounts to WSI extensions on 1.0.51. CORE_EXTENSIONS = [ "VK_KHR_display", "VK_KHR_display_swapchain", "VK_KHR_get_surface_capabilities2", "VK_KHR_surface", "VK_KHR_swapchain", "VK_KHR_win32_surface", ] # Some experimental extensions are used by shipping applications so their API is extremely unlikely # to change in a backwards-incompatible way. Allow translation of those extensions with WineVulkan. ALLOWED_X_EXTENSIONS = [ "VK_NVX_binary_import", "VK_NVX_image_view_handle", ] # Functions part of our winevulkan graphics driver interface. # DRIVER_VERSION should be bumped on any change to driver interface # in FUNCTION_OVERRIDES DRIVER_VERSION = 11 class ThunkType(Enum): NONE = 1 PUBLIC = 2 PRIVATE = 3 # Table of functions for which we have a special implementation. # These are regular device / instance functions for which we need # to do more work compared to a regular thunk or because they are # part of the driver interface. # - dispatch set whether we need a function pointer in the device # / instance dispatch table. # - driver sets whether the API is part of the driver interface. # - thunk sets whether to create a thunk in vulkan_thunks.c. # - NONE means there's a fully custom implementation. # - PUBLIC means the implementation is fully auto generated. # - PRIVATE thunks can be used in custom implementations for # struct conversion. # - loader_thunk sets whether to create a thunk for unix funcs. FUNCTION_OVERRIDES = { # Global functions "vkCreateInstance" : {"dispatch" : False, "driver" : True, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE, "extra_param" : "client_ptr"}, "vkEnumerateInstanceExtensionProperties" : {"dispatch" : False, "driver" : True, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE}, "vkEnumerateInstanceLayerProperties" : {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.NONE}, "vkEnumerateInstanceVersion": {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE}, "vkGetInstanceProcAddr": {"dispatch" : False, "driver" : True, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.NONE}, # Instance functions "vkCreateDevice" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE, "extra_param" : "client_ptr"}, "vkDestroyInstance" : {"dispatch" : False, "driver" : True, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE}, "vkEnumerateDeviceExtensionProperties" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE}, "vkEnumerateDeviceLayerProperties": {"dispatch": True, "driver": False, "thunk": ThunkType.NONE}, "vkEnumeratePhysicalDeviceGroups" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE}, "vkEnumeratePhysicalDevices" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE}, "vkGetPhysicalDeviceExternalBufferProperties" : {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE}, "vkGetPhysicalDeviceExternalFenceProperties" : {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE}, "vkGetPhysicalDeviceExternalSemaphoreProperties" : {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE}, "vkGetPhysicalDeviceImageFormatProperties2" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, "vkGetPhysicalDeviceProperties2" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PUBLIC, "loader_thunk" : ThunkType.PRIVATE}, "vkGetPhysicalDeviceProperties2KHR" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PUBLIC, "loader_thunk" : ThunkType.PRIVATE}, # Device functions "vkAllocateCommandBuffers" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE}, "vkCreateCommandPool" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE, "extra_param" : "client_ptr"}, "vkDestroyCommandPool" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE}, "vkDestroyDevice" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE}, "vkFreeCommandBuffers" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.PRIVATE}, "vkGetDeviceProcAddr" : {"dispatch" : False, "driver" : True, "thunk" : ThunkType.NONE, "loader_thunk" : ThunkType.NONE}, "vkGetDeviceQueue" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE}, "vkGetDeviceQueue2" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE}, "vkAllocateMemory" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, "vkFreeMemory" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, "vkMapMemory" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, "vkUnmapMemory" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, "vkCreateBuffer" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, "vkCreateImage" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, # VK_KHR_surface "vkDestroySurfaceKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.NONE}, "vkGetPhysicalDeviceSurfaceSupportKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PRIVATE}, "vkGetPhysicalDeviceSurfaceFormatsKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, "vkGetPhysicalDeviceSurfacePresentModesKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, # VK_KHR_get_surface_capabilities2 "vkGetPhysicalDeviceSurfaceCapabilities2KHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PRIVATE}, "vkGetPhysicalDeviceSurfaceFormats2KHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, # VK_KHR_win32_surface "vkCreateWin32SurfaceKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.NONE}, "vkGetPhysicalDeviceWin32PresentationSupportKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, # VK_KHR_swapchain "vkCreateSwapchainKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, "vkDestroySwapchainKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, "vkGetSwapchainImagesKHR": {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, "vkQueuePresentKHR": {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, # VK_KHR_external_fence_capabilities "vkGetPhysicalDeviceExternalFencePropertiesKHR" : {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE}, # VK_KHR_external_memory_capabilities "vkGetPhysicalDeviceExternalBufferPropertiesKHR" : {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE}, "vkGetPhysicalDeviceImageFormatProperties2KHR" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.PRIVATE}, # VK_KHR_external_semaphore_capabilities "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR" : {"dispatch" : False, "driver" : False, "thunk" : ThunkType.NONE}, # VK_KHR_device_group_creation "vkEnumeratePhysicalDeviceGroupsKHR" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE}, # VK_KHR_device_group "vkGetDeviceGroupSurfacePresentModesKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, "vkGetPhysicalDevicePresentRectanglesKHR" : {"dispatch" : True, "driver" : True, "thunk" : ThunkType.PUBLIC}, # VK_EXT_calibrated_timestamps "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE}, "vkGetCalibratedTimestampsEXT" : {"dispatch" : True, "driver" : False, "thunk" : ThunkType.NONE}, # VK_EXT_debug_utils "vkCreateDebugUtilsMessengerEXT" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE}, "vkDestroyDebugUtilsMessengerEXT" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE}, # VK_EXT_debug_report "vkCreateDebugReportCallbackEXT" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE}, "vkDestroyDebugReportCallbackEXT" : {"dispatch": True, "driver" : False, "thunk" : ThunkType.NONE}, } STRUCT_CHAIN_CONVERSIONS = { # Ignore to not confuse host loader. "VkDeviceCreateInfo": ["VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO"], "VkInstanceCreateInfo": ["VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO"], } # Some struct members are conditionally ignored and callers are free to leave them uninitialized. # We can't deduce that from XML, so we allow expressing it here. MEMBER_LENGTH_EXPRESSIONS = { "VkWriteDescriptorSet": { "pImageInfo": "{struct}descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM ? {len} : 0", "pBufferInfo": "{struct}descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC || " + "{struct}descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC ? {len} : 0", } } class Direction(Enum): """ Parameter direction: input, output, input_output. """ INPUT = 1 OUTPUT = 2 class VkBaseType(object): def __init__(self, name, _type, alias=None, requires=None): """ Vulkan base type class. VkBaseType is mostly used by Vulkan to define its own base types like VkFlags through typedef out of e.g. uint32_t. Args: name (:obj:'str'): Name of the base type. _type (:obj:'str'): Underlying type alias (bool): type is an alias or not. requires (:obj:'str', optional): Other types required. Often bitmask values pull in a *FlagBits type. """ self.name = name self.type = _type self.alias = alias self.requires = requires self.required = False def definition(self): # Definition is similar for alias or non-alias as type # is already set to alias. if not self.type is None: return "typedef {0} {1};\n".format(self.type, self.name) else: return "struct {0};\n".format(self.name) def is_alias(self): return bool(self.alias) class VkConstant(object): def __init__(self, name, value): self.name = name self.value = value def definition(self): text = "#define {0} {1}\n".format(self.name, self.value) return text class VkDefine(object): def __init__(self, name, value): self.name = name self.value = value @staticmethod def from_xml(define): name_elem = define.find("name") if name_elem is None: # some_value name = define.attrib.get("name") # We override behavior of VK_USE_64_BIT_PTR_DEFINES as the default non-dispatchable handle # definition various between 64-bit (uses pointers) and 32-bit (uses uint64_t). # This complicates TRACEs in the thunks, so just use uint64_t. if name == "VK_USE_64_BIT_PTR_DEFINES": value = "#define VK_USE_64_BIT_PTR_DEFINES 0" else: value = define.text return VkDefine(name, value) # With a name element the structure is like: # some_namesome_value name = name_elem.text # Perform minimal parsing for Vulkan constants, which we don't need, but are referenced # elsewhere in vk.xml. # - VK_API_VERSION is a messy, deprecated constant and we don't want generate code for it. # - AHardwareBuffer/ANativeWindow are forward declarations for Android types, which leaked # into the define region. if name in ["VK_API_VERSION", "AHardwareBuffer", "ANativeWindow", "CAMetalLayer"]: return VkDefine(name, None) # The body of the define is basically unstructured C code. It is not meant for easy parsing. # Some lines contain deprecated values or comments, which we try to filter out. value = "" for line in define.text.splitlines(): # Skip comments or deprecated values. if "//" in line: continue value += line for child in define: value += child.text if child.tail is not None: # Split comments for VK_API_VERSION_1_0 / VK_API_VERSION_1_1 if "//" in child.tail: value += child.tail.split("//")[0] else: value += child.tail return VkDefine(name, value.rstrip(' ')) def definition(self): if self.value is None: return "" # Nothing to do as the value was already put in the right form during parsing. return "{0}\n".format(self.value) class VkEnum(object): def __init__(self, name, bitwidth, alias=None): if not bitwidth in [32, 64]: LOGGER.error("unknown bitwidth {0} for {1}".format(bitwidth, name)) self.name = name self.bitwidth = bitwidth self.values = [] if alias == None else alias.values self.required = False self.alias = alias self.aliased_by = [] @staticmethod def from_alias(enum, alias): name = enum.attrib.get("name") aliasee = VkEnum(name, alias.bitwidth, alias=alias) alias.add_aliased_by(aliasee) return aliasee @staticmethod def from_xml(enum): name = enum.attrib.get("name") bitwidth = int(enum.attrib.get("bitwidth", "32")) result = VkEnum(name, bitwidth) for v in enum.findall("enum"): value_name = v.attrib.get("name") # Value is either a value or a bitpos, only one can exist. value = v.attrib.get("value") alias_name = v.attrib.get("alias") if alias_name: result.create_alias(value_name, alias_name) elif value: result.create_value(value_name, value) else: # bitmask result.create_bitpos(value_name, int(v.attrib.get("bitpos"))) if bitwidth == 32: # vulkan.h contains a *_MAX_ENUM value set to 32-bit at the time of writing, # which is to prepare for extensions as they can add values and hence affect # the size definition. max_name = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2', name).upper() + "_MAX_ENUM" result.create_value(max_name, "0x7fffffff") return result def create_alias(self, name, alias_name): """ Create an aliased value for this enum """ self.add(VkEnumValue(name, self.bitwidth, alias=alias_name)) def create_value(self, name, value): """ Create a new value for this enum """ # Some values are in hex form. We want to preserve the hex representation # at least when we convert back to a string. Internally we want to use int. hex = "0x" in value self.add(VkEnumValue(name, self.bitwidth, value=int(value, 0), hex=hex)) def create_bitpos(self, name, pos): """ Create a new bitmask value for this enum """ self.add(VkEnumValue(name, self.bitwidth, value=(1 << pos), hex=True)) def add(self, value): """ Add a value to enum. """ # Extensions can add new enum values. When an extension is promoted to Core # the registry defines the value twice once for old extension and once for # new Core features. Add the duplicate if it's explicitly marked as an # alias, otherwise ignore it. for v in self.values: if not value.is_alias() and v.value == value.value: LOGGER.debug("Adding duplicate enum value {0} to {1}".format(v, self.name)) return # Avoid adding duplicate aliases multiple times if not any(x.name == value.name for x in self.values): self.values.append(value) def fixup_64bit_aliases(self): """ Replace 64bit aliases with literal values """ # Older GCC versions need a literal to initialize a static const uint64_t # which is what we use for 64bit bitmasks. if self.bitwidth != 64: return for value in self.values: if not value.is_alias(): continue alias = next(x for x in self.values if x.name == value.alias) value.hex = alias.hex value.value = alias.value def definition(self): if self.is_alias(): return "" default_value = 0x7ffffffe if self.bitwidth == 32 else 0xfffffffffffffffe # Print values sorted, values can have been added in a random order. values = sorted(self.values, key=lambda value: value.value if value.value is not None else default_value) if self.bitwidth == 32: text = "typedef enum {0}\n{{\n".format(self.name) for value in values: text += " {0},\n".format(value.definition()) text += "}} {0};\n".format(self.name) elif self.bitwidth == 64: text = "typedef VkFlags64 {0};\n\n".format(self.name) for value in values: text += "static const {0} {1};\n".format(self.name, value.definition()) for aliasee in self.aliased_by: text += "typedef {0} {1};\n".format(self.name, aliasee.name) text += "\n" return text def is_alias(self): return bool(self.alias) def add_aliased_by(self, aliasee): self.aliased_by.append(aliasee) class VkEnumValue(object): def __init__(self, name, bitwidth, value=None, hex=False, alias=None): self.name = name self.bitwidth = bitwidth self.value = value self.hex = hex self.alias = alias def __repr__(self): postfix = "ull" if self.bitwidth == 64 else "" if self.is_alias() and self.value == None: return "{0}={1}".format(self.name, self.alias) return "{0}={1}{2}".format(self.name, self.value, postfix) def definition(self): """ Convert to text definition e.g. VK_FOO = 1 """ postfix = "ull" if self.bitwidth == 64 else "" if self.is_alias() and self.value == None: return "{0} = {1}".format(self.name, self.alias) # Hex is commonly used for FlagBits and sometimes within # a non-FlagBits enum for a bitmask value as well. if self.hex: return "{0} = 0x{1:08x}{2}".format(self.name, self.value, postfix) else: return "{0} = {1}{2}".format(self.name, self.value, postfix) def is_alias(self): return self.alias is not None class VkFunction(object): def __init__(self, _type=None, name=None, params=[], alias=None): self.extensions = set() self.name = name self.type = _type self.params = params self.alias = alias # For some functions we need some extra metadata from FUNCTION_OVERRIDES. func_info = FUNCTION_OVERRIDES.get(self.name, None) self.dispatch = func_info["dispatch"] if func_info else True self.driver = func_info["driver"] if func_info else False self.thunk_type = func_info["thunk"] if func_info else ThunkType.PUBLIC self.loader_thunk_type = func_info["loader_thunk"] if func_info and "loader_thunk" in func_info else ThunkType.PUBLIC self.extra_param = func_info["extra_param"] if func_info and "extra_param" in func_info else None # Required is set while parsing which APIs and types are required # and is used by the code generation. self.required = True if func_info else False @staticmethod def from_alias(command, alias): """ Create VkFunction from an alias command. Args: command: xml data for command alias (VkFunction): function to use as a base for types / parameters. Returns: VkFunction """ func_name = command.attrib.get("name") func_type = alias.type params = alias.params return VkFunction(_type=func_type, name=func_name, params=params, alias=alias) @staticmethod def from_xml(command, types): proto = command.find("proto") func_name = proto.find("name").text func_type = proto.find("type").text params = [] for param in command.findall("param"): vk_param = VkParam.from_xml(param, types, params) params.append(vk_param) return VkFunction(_type=func_type, name=func_name, params=params) def get_conversions(self): """ Get a list of conversion functions required for this function if any. Parameters which are structures may require conversion between win32 and the host platform. This function returns a list of conversions required. """ conversions = [] for param in self.params: conversions.extend(param.get_conversions(self.thunk_type == ThunkType.PUBLIC)) return conversions def is_alias(self): return bool(self.alias) def is_core_func(self): """ Returns whether the function is a Vulkan core function. Core functions are APIs defined by the Vulkan spec to be part of the Core API as well as several KHR WSI extensions. """ if not self.extensions: return True return any(ext in self.extensions for ext in CORE_EXTENSIONS) def is_device_func(self): # If none of the other, it must be a device function return not self.is_global_func() and not self.is_instance_func() and not self.is_phys_dev_func() def is_driver_func(self): """ Returns if function is part of Wine driver interface. """ return self.driver def is_global_func(self): # Treat vkGetInstanceProcAddr as a global function as it # can operate with NULL for vkInstance. if self.name == "vkGetInstanceProcAddr": return True # Global functions are not passed a dispatchable object. elif self.params[0].is_dispatchable(): return False return True def is_instance_func(self): # Instance functions are passed VkInstance. if self.params[0].type == "VkInstance": return True return False def is_phys_dev_func(self): # Physical device functions are passed VkPhysicalDevice. if self.params[0].type == "VkPhysicalDevice": return True return False def is_required(self): return self.required def returns_longlong(self): return self.type in ["uint64_t", "VkDeviceAddress"] def needs_dispatch(self): return self.dispatch def needs_private_thunk(self): return self.needs_exposing() and self.loader_thunk_type != ThunkType.NONE and \ self.thunk_type != ThunkType.PUBLIC def needs_exposing(self): # The function needs exposed if at-least one extension isn't both UNSUPPORTED and UNEXPOSED return self.is_required() and (not self.extensions or not self.extensions.issubset(UNEXPOSED_EXTENSIONS)) def pfn(self, prefix="p", call_conv=None): """ Create function pointer. """ if call_conv: pfn = "{0} ({1} *{2}_{3})(".format(self.type, call_conv, prefix, self.name) else: pfn = "{0} (*{1}_{2})(".format(self.type, prefix, self.name) for i, param in enumerate(self.params): if param.const: pfn += param.const + " " pfn += param.type if param.is_pointer(): pfn += " " + param.pointer if param.array_len is not None: pfn += "[{0}]".format(param.array_len) if i < len(self.params) - 1: pfn += ", " pfn += ")" return pfn def prototype(self, call_conv=None, prefix=None, postfix=None, is_thunk=False): """ Generate prototype for given function. Args: call_conv (str, optional): calling convention e.g. WINAPI prefix (str, optional): prefix to append prior to function name e.g. vkFoo -> wine_vkFoo postfix (str, optional): text to append after function name but prior to semicolon e.g. DECLSPEC_HIDDEN """ proto = "{0}".format(self.type) if call_conv is not None: proto += " {0}".format(call_conv) if prefix is not None: proto += " {0}{1}(".format(prefix, self.name) else: proto += " {0}(".format(self.name) # Add all the parameters. proto += ", ".join([p.definition() for p in self.params]) if is_thunk and self.extra_param: proto += ", void *" + self.extra_param if postfix is not None: proto += ") {0}".format(postfix) else: proto += ")" return proto def loader_body(self): body = " struct {0}_params params;\n".format(self.name) body += " NTSTATUS status;\n" for p in self.params: body += " params.{0} = {0};\n".format(p.name) # Call the Unix function. body += " status = UNIX_CALL({0}, ¶ms);\n".format(self.name) body += " assert(!status);\n" if self.type != "void": body += " return params.result;\n" return body def body(self, conv, unwrap, params_prefix=""): body = "" needs_alloc = False # Declare any tmp parameters for conversion. for p in self.params: if p.needs_variable(conv, unwrap): if p.is_dynamic_array(): body += " {2}{0} *{1}_host;\n".format( p.type, p.name, "const " if p.is_const() else "") elif p.optional: body += " {0} *{1}_host = NULL;\n".format(p.type, p.name) needs_alloc = True else: body += " {0} {1}_host;\n".format(p.type, p.name) if p.needs_alloc(conv, unwrap): needs_alloc = True if needs_alloc: body += " struct conversion_context ctx;\n" body += "\n" body += " {0}\n".format(self.trace(params_prefix=params_prefix, conv=conv)) if self.params[0].optional and self.params[0].is_handle(): if self.type != "void": LOGGER.warning("return type {0} with optional handle not supported".format(self.type)) body += " if (!{0}{1})\n".format(params_prefix, self.params[0].name) body += " return STATUS_SUCCESS;\n\n" if needs_alloc: body += " init_conversion_context(&ctx);\n" # Call any win_to_host conversion calls. unwrap = self.thunk_type == ThunkType.PUBLIC for p in self.params: if p.needs_conversion(conv, unwrap, Direction.INPUT): body += p.copy(Direction.INPUT, conv, unwrap, prefix=params_prefix) elif p.is_dynamic_array() and p.needs_conversion(conv, unwrap, Direction.OUTPUT): body += " {0}_host = ({2}{0} && {1}) ? conversion_context_alloc(&ctx, sizeof(*{0}_host) * {1}) : NULL;\n".format( p.name, p.get_dyn_array_len(params_prefix, conv), params_prefix) # Build list of parameters containing converted and non-converted parameters. # The param itself knows if conversion is needed and applies it when we set conv=True. params = ", ".join([p.variable(conv=conv, unwrap=unwrap, params_prefix=params_prefix) for p in self.params]) if self.extra_param: if conv: params += ", UlongToPtr({0}{1})".format(params_prefix, self.extra_param) else: params += ", {0}{1}".format(params_prefix, self.extra_param) if unwrap or self.thunk_type == ThunkType.PUBLIC: func_prefix = "{0}.p_".format(self.params[0].dispatch_table(params_prefix, conv)) else: func_prefix = "wine_" # Call the native Vulkan function. if self.type == "void": body += " {0}{1}({2});\n".format(func_prefix, self.name, params) else: body += " {0}result = {1}{2}({3});\n".format(params_prefix, func_prefix, self.name, params) # Call any host_to_win conversion calls. for p in self.params: if p.needs_conversion(conv, unwrap, Direction.OUTPUT): body += p.copy(Direction.OUTPUT, conv, unwrap, prefix=params_prefix) if needs_alloc: body += " free_conversion_context(&ctx);\n" # Finally return the result. body += " return STATUS_SUCCESS;\n" return body def spec(self, prefix=None, symbol=None): """ Generate spec file entry for this function. Args prefix (str, optional): prefix to prepend to entry point name. symbol (str, optional): allows overriding the name of the function implementing the entry point. """ spec = "" params = " ".join([p.spec() for p in self.params]) if prefix is not None: spec += "@ stdcall -private {0}{1}({2})".format(prefix, self.name, params) else: spec += "@ stdcall {0}({1})".format(self.name, params) if symbol is not None: spec += " " + symbol spec += "\n" return spec def stub(self, call_conv=None, prefix=None): stub = self.prototype(call_conv=call_conv, prefix=prefix) stub += "\n{\n" stub += " {0}".format(self.trace(message="stub: ", trace_func="FIXME")) if self.type == "VkResult": stub += " return VK_ERROR_OUT_OF_HOST_MEMORY;\n" elif self.type == "VkBool32": stub += " return VK_FALSE;\n" elif self.type == "PFN_vkVoidFunction": stub += " return NULL;\n" stub += "}\n\n" return stub def thunk(self, prefix=None, conv=False): thunk = "" if not conv: thunk += "#ifdef _WIN64\n" thunk += "static NTSTATUS {0}{1}(void *args)\n".format(prefix, self.name) thunk += "{\n" if conv: thunk += " struct\n" thunk += " {\n" for p in self.params: thunk += " {0};\n".format(p.definition(conv=True, is_member=True)) if self.extra_param: thunk += " PTR32 {0};\n".format(self.extra_param) if self.type != "void": thunk += " {0} result;\n".format(self.type) thunk += " } *params = args;\n" else: thunk += " struct {0}_params *params = args;\n".format(self.name) thunk += self.body(conv=conv, unwrap=self.thunk_type == ThunkType.PUBLIC, params_prefix="params->") thunk += "}\n" if not conv: thunk += "#endif /* _WIN64 */\n" thunk += "\n" return thunk def loader_thunk(self, prefix=None): thunk = self.prototype(call_conv="WINAPI", prefix=prefix) thunk += "\n{\n" thunk += self.loader_body() thunk += "}\n\n" return thunk def trace(self, message=None, trace_func=None, params_prefix="", conv=False): """ Create a trace string including all parameters. Args: message (str, optional): text to print at start of trace message e.g. 'stub: ' trace_func (str, optional): used to override trace function e.g. FIXME, printf, etcetera. """ if trace_func is not None: trace = "{0}(\"".format(trace_func) else: trace = "TRACE(\"" if message is not None: trace += message # First loop is for all the format strings. trace += ", ".join([p.format_string(conv) for p in self.params]) trace += "\\n\"" # Second loop for parameter names and optional conversions. for param in self.params: if param.format_conv is not None: trace += ", " + param.format_conv.format("{0}{1}".format(params_prefix, param.name)) else: trace += ", {0}{1}".format(params_prefix, param.name) trace += ");\n" return trace class VkFunctionPointer(object): def __init__(self, _type, name, members, forward_decls): self.name = name self.members = members self.type = _type self.required = False self.forward_decls = forward_decls @staticmethod def from_xml(funcpointer): members = [] begin = None for t in funcpointer.findall("type"): # General form: # void* pUserData, # Parsing of the tail (anything past ) is tricky since there # can be other data on the next line like: const int.. const = True if begin and "const" in begin else False _type = t.text lines = t.tail.split(",\n") if lines[0][0] == "*": pointer = "*" name = lines[0][1:].strip() else: pointer = None name = lines[0].strip() # Filter out ); if it is contained. name = name.partition(");")[0] # If tail encompasses multiple lines, assign the second line to begin # for the next line. try: begin = lines[1].strip() except IndexError: begin = None members.append(VkMember(const=const, _type=_type, pointer=pointer, name=name)) _type = funcpointer.text name = funcpointer.find("name").text if "requires" in funcpointer.attrib: forward_decls = funcpointer.attrib.get("requires").split(",") else: forward_decls = [] return VkFunctionPointer(_type, name, members, forward_decls) def definition(self): text = "" # forward declare required structs for decl in self.forward_decls: text += "typedef struct {0} {0};\n".format(decl) text += "{0} {1})(\n".format(self.type, self.name) first = True if len(self.members) > 0: for m in self.members: if first: text += " " + m.definition() first = False else: text += ",\n " + m.definition() else: # Just make the compiler happy by adding a void parameter. text += "void" text += ");\n" return text def is_alias(self): return False class VkHandle(object): def __init__(self, name, _type, parent, alias=None): self.name = name self.type = _type self.parent = parent self.alias = alias self.required = False self.object_type = None @staticmethod def from_alias(handle, alias): name = handle.attrib.get("name") return VkHandle(name, alias.type, alias.parent, alias=alias) @staticmethod def from_xml(handle): name = handle.find("name").text _type = handle.find("type").text parent = handle.attrib.get("parent") # Most objects have a parent e.g. VkQueue has VkDevice. return VkHandle(name, _type, parent) def dispatch_table(self, param): if not self.is_dispatchable(): return None if self.parent is None: # Should only happen for VkInstance return "wine_instance_from_handle({0})->funcs".format(param) elif self.name == "VkCommandBuffer": return "wine_cmd_buffer_from_handle({0})->device->funcs".format(param) elif self.name == "VkDevice": return "wine_device_from_handle({0})->funcs".format(param) elif self.name == "VkPhysicalDevice": return "wine_phys_dev_from_handle({0})->instance->funcs".format(param) elif self.name == "VkQueue": return "wine_queue_from_handle({0})->device->funcs".format(param) elif self.parent in ["VkInstance", "VkPhysicalDevice"]: return "{0}->instance->funcs".format(param) elif self.parent in ["VkDevice", "VkCommandPool"]: return "{0}->device->funcs".format(param) else: LOGGER.error("Unhandled dispatchable parent: {0}".format(self.parent)) def definition(self): """ Generates handle definition e.g. VK_DEFINE_HANDLE(vkInstance) """ # Legacy types are typedef'ed to the new type if they are aliases. if self.is_alias(): return "typedef {0} {1};\n".format(self.alias.name, self.name) return "{0}({1})\n".format(self.type, self.name) def is_alias(self): return self.alias is not None def is_dispatchable(self): """ Some handles like VkInstance, VkDevice are dispatchable objects, which means they contain a dispatch table of function pointers. """ return self.type == "VK_DEFINE_HANDLE" def is_required(self): return self.required def native_handle(self, name): """ Provide access to the native handle of a wrapped object. """ if self.name == "VkCommandBuffer": return "wine_cmd_buffer_from_handle({0})->command_buffer".format(name) if self.name == "VkCommandPool": return "wine_cmd_pool_from_handle({0})->command_pool".format(name) if self.name == "VkDebugUtilsMessengerEXT": return "wine_debug_utils_messenger_from_handle({0})->debug_messenger".format(name) if self.name == "VkDebugReportCallbackEXT": return "wine_debug_report_callback_from_handle({0})->debug_callback".format(name) if self.name == "VkDevice": return "wine_device_from_handle({0})->device".format(name) if self.name == "VkInstance": return "wine_instance_from_handle({0})->instance".format(name) if self.name == "VkDeviceMemory": return "wine_device_memory_from_handle({0})->memory".format(name) if self.name == "VkPhysicalDevice": return "wine_phys_dev_from_handle({0})->phys_dev".format(name) if self.name == "VkQueue": return "wine_queue_from_handle({0})->queue".format(name) if self.name == "VkSurfaceKHR": return "wine_surface_from_handle({0})->surface".format(name) if self.is_dispatchable(): LOGGER.error("Unhandled native handle for: {0}".format(self.name)) return None def driver_handle(self, name): """ Provide access to the handle that should be passed to the wine driver """ if self.name == "VkSurfaceKHR": return "wine_surface_from_handle({0})->driver_surface".format(name) return self.native_handle(name) def is_wrapped(self): return self.native_handle("test") is not None def needs_unwrapping(self): return self.is_wrapped() class VkVariable(object): def __init__(self, const=False, type_info=None, type=None, name=None, pointer=None, array_len=None, dyn_array_len=None, object_type=None, optional=False, returnedonly=False, parent=None, selection=None, selector=None): self.const = const self.type_info = type_info self.type = type self.name = name self.parent = parent self.object_type = object_type self.optional = optional self.returnedonly = returnedonly self.selection = selection self.selector = selector self.pointer = pointer self.array_len = array_len self.dyn_array_len = dyn_array_len self.pointer_array = False if isinstance(dyn_array_len, str): i = dyn_array_len.find(",") if i != -1: self.dyn_array_len = dyn_array_len[0:i] self.pointer_array = True if type_info: self.set_type_info(type_info) def __eq__(self, other): """ Compare member based on name against a string. """ return self.name == other def set_type_info(self, type_info): """ Helper function to set type information from the type registry. This is needed, because not all type data is available at time of parsing. """ self.type_info = type_info self.handle = type_info["data"] if type_info["category"] == "handle" else None self.struct = type_info["data"] if type_info["category"] == "struct" or type_info["category"] == "union" else None def get_dyn_array_len(self, prefix, conv): if isinstance(self.dyn_array_len, int): return self.dyn_array_len len_str = self.dyn_array_len parent = self.parent len = prefix # check if lenght is a member of another struct (for example pAllocateInfo->commandBufferCount) i = len_str.find("->") if i != -1: var = parent[parent.index(len_str[0:i])] len_str = len_str[i+2:] len = "({0})->".format(var.value(len, conv)) parent = var.struct if len_str in parent: var = parent[parent.index(len_str)] len = var.value(len, conv); if var.is_pointer(): len = "*" + len else: len += len_str if isinstance(self.parent, VkStruct) and self.parent.name in MEMBER_LENGTH_EXPRESSIONS: exprs = MEMBER_LENGTH_EXPRESSIONS[self.parent.name] if self.name in exprs: len = exprs[self.name].format(struct=prefix, len=len) return len def is_const(self): return self.const def is_pointer(self): return self.pointer is not None def is_pointer_size(self): if self.type in ["size_t", "HWND", "HINSTANCE"]: return True if self.is_handle() and self.handle.is_dispatchable(): return True return False def is_handle(self): return self.handle is not None def is_struct(self): return self.type_info["category"] == "struct" def is_union(self): return self.type_info["category"] == "union" def is_bitmask(self): return self.type_info["category"] == "bitmask" def is_enum(self): return self.type_info["category"] == "enum" def is_dynamic_array(self): """ Returns if the member is an array element. Vulkan uses this for dynamically sized arrays for which there is a 'count' parameter. """ return self.dyn_array_len is not None def is_static_array(self): """ Returns if the member is an array. Vulkan uses this often for fixed size arrays in which the length is part of the member. """ return self.array_len is not None def is_generic_handle(self): """ Returns True if the member is a unit64_t containing a handle with a separate object type """ return self.object_type != None and self.type == "uint64_t" def needs_alignment(self): """ Check if this member needs alignment for 64-bit data. Various structures need alignment on 64-bit variables due to compiler differences on 32-bit between Win32 and Linux. """ if self.is_pointer(): return False elif self.type == "size_t": return False elif self.type in ["uint64_t", "VkDeviceAddress", "VkDeviceSize"]: return True elif self.is_bitmask(): return self.type_info["data"].type == "VkFlags64" elif self.is_enum(): return self.type_info["data"].bitwidth == 64 elif self.is_struct() or self.is_union(): return self.type_info["data"].needs_alignment() elif self.is_handle(): # Dispatchable handles are pointers to objects, while # non-dispatchable are uint64_t and hence need alignment. return not self.handle.is_dispatchable() return False def needs_unwrapping(self): """ Returns if variable needs unwrapping of handle. """ if self.is_struct(): return self.struct.needs_unwrapping() if self.is_handle(): return self.handle.needs_unwrapping() if self.is_generic_handle(): return True return False def needs_alloc(self, conv, unwrap): """ Returns True if conversion needs allocation """ if self.is_dynamic_array(): return self.needs_conversion(conv, unwrap, Direction.INPUT, False) \ or self.needs_conversion(conv, unwrap, Direction.OUTPUT, False) return (self.is_struct() or (self.is_union() and self.selector)) and self.struct.needs_alloc(conv, unwrap) def needs_host_type(self): return (self.is_struct() or (self.is_union() and self.selector)) and self.struct.needs_host_type() def get_conversions(self, unwrap, parent_const=False): """ Get a list of conversions required for this parameter if any. Parameters which are structures may require conversion between win32 and the host platform. This function returns a list of conversions required. """ conversions = [] # Collect any member conversions first, so we can guarantee # those functions will be defined prior to usage by the # 'parent' param requiring conversion. if self.is_struct() or (self.is_union() and self.selector): struct = self.struct is_const = self.is_const() if self.is_pointer() else parent_const conversions.extend(struct.get_conversions(unwrap, is_const)) for conv in [False, True]: if struct.needs_conversion(conv, unwrap, Direction.INPUT, is_const): conversions.append(StructConversionFunction(struct, Direction.INPUT, conv, unwrap, is_const)) if struct.needs_conversion(conv, unwrap, Direction.OUTPUT, is_const): conversions.append(StructConversionFunction(struct, Direction.OUTPUT, conv, unwrap, is_const)) if self.is_static_array() or self.is_dynamic_array(): for conv in [False, True]: if self.needs_conversion(conv, unwrap, Direction.INPUT, parent_const): conversions.append(ArrayConversionFunction(self, Direction.INPUT, conv, unwrap)) if self.needs_conversion(conv, unwrap, Direction.OUTPUT, parent_const): conversions.append(ArrayConversionFunction(self, Direction.OUTPUT, conv, unwrap)) return conversions def needs_ptr32_type(self): """ Check if variable needs to use PTR32 type. """ return self.is_pointer() or self.is_pointer_size() or self.is_static_array() def value(self, prefix, conv): """ Returns code accessing member value, casting 32-bit pointers when needed. """ if not conv or not self.needs_ptr32_type() or (not self.is_pointer() and self.type == "size_t"): return prefix + self.name cast_type = "" if self.const: cast_type += "const " if self.pointer_array or ((self.is_pointer() or self.is_static_array()) and self.is_pointer_size()): cast_type += "PTR32 *" else: cast_type += self.type if self.needs_host_type(): cast_type += "32" if self.is_pointer(): cast_type += " {0}".format(self.pointer) elif self.is_static_array(): cast_type += " *" return "({0})UlongToPtr({1}{2})".format(cast_type, prefix, self.name) class VkMember(VkVariable): def __init__(self, const=False, struct_fwd_decl=False,_type=None, pointer=None, name=None, array_len=None, dyn_array_len=None, optional=False, values=None, object_type=None, bit_width=None, returnedonly=False, parent=None, selection=None, selector=None): VkVariable.__init__(self, const=const, type=_type, name=name, pointer=pointer, array_len=array_len, dyn_array_len=dyn_array_len, object_type=object_type, optional=optional, returnedonly=returnedonly, parent=parent, selection=selection, selector=selector) self.struct_fwd_decl = struct_fwd_decl self.values = values self.bit_width = bit_width def __repr__(self): return "{0} {1} {2} {3} {4} {5} {6}".format(self.const, self.struct_fwd_decl, self.type, self.pointer, self.name, self.array_len, self.dyn_array_len) @staticmethod def from_xml(member, returnedonly, parent): """ Helper function for parsing a member tag within a struct or union. """ name_elem = member.find("name") type_elem = member.find("type") const = False struct_fwd_decl = False member_type = None pointer = None array_len = None bit_width = None values = member.get("values") if member.text: if "const" in member.text: const = True # Some members contain forward declarations: # - VkBaseInstructure has a member "const struct VkBaseInStructure *pNext" # - VkWaylandSurfaceCreateInfoKHR has a member "struct wl_display *display" if "struct" in member.text: struct_fwd_decl = True if type_elem is not None: member_type = type_elem.text if type_elem.tail is not None: pointer = type_elem.tail.strip() if type_elem.tail.strip() != "" else None # Name of other member within, which stores the number of # elements pointed to be by this member. dyn_array_len = member.get("len") # Some members are optional, which is important for conversion code e.g. not dereference NULL pointer. optional = True if member.get("optional") else False # Usually we need to allocate memory for dynamic arrays. We need to do the same in a few other cases # like for VkCommandBufferBeginInfo.pInheritanceInfo. Just threat such cases as dynamic arrays of # size 1 to simplify code generation. if dyn_array_len is None and pointer is not None: dyn_array_len = 1 # Some members are arrays, attempt to parse these. Formats include: # charextensionName[VK_MAX_EXTENSION_NAME_SIZE] # uint32_tfoo[4] if name_elem.tail and name_elem.tail[0] == '[': LOGGER.debug("Found array type") enum_elem = member.find("enum") if enum_elem is not None: array_len = enum_elem.text else: # Remove brackets around length array_len = name_elem.tail.strip("[]") object_type = member.get("objecttype", None) # Some members are bit field values: # uint32_t mask:8 if name_elem.tail and name_elem.tail[0] == ':': LOGGER.debug("Found bit field") bit_width = int(name_elem.tail[1:]) selection = member.get("selection").split(',') if member.get("selection") else None selector = member.get("selector", None) return VkMember(const=const, struct_fwd_decl=struct_fwd_decl, _type=member_type, pointer=pointer, name=name_elem.text, array_len=array_len, dyn_array_len=dyn_array_len, optional=optional, values=values, object_type=object_type, bit_width=bit_width, returnedonly=returnedonly, parent=parent, selection=selection, selector=selector) def copy(self, input, output, direction, conv, unwrap): """ Helper method for use by conversion logic to generate a C-code statement to copy this member. - `conv` indicates whether the statement is in a struct alignment conversion path. """ win_type = "win32" if conv else "win64" if self.needs_conversion(conv, unwrap, direction, False): if self.is_dynamic_array(): # Array length is either a variable name (string) or an int. count = self.get_dyn_array_len(input, conv) host_part = "host" if unwrap else "unwrapped_host" pointer_part = "pointer_" if self.pointer_array else "" if direction == Direction.OUTPUT: return "convert_{2}_{7}array_{6}_to_{5}({3}{1}, {0}, {4});\n".format( self.value(output, conv), self.name, self.type, input, count, win_type, host_part, pointer_part) else: return "{0}{1} = convert_{2}_{7}array_{5}_to_{6}(ctx, {3}, {4});\n".format( output, self.name, self.type, self.value(input, conv), count, win_type, host_part, pointer_part) elif self.is_static_array(): count = self.array_len if direction == Direction.OUTPUT: # Needed by VkMemoryHeap.memoryHeaps host_part = "host" if unwrap else "unwrapped_host" return "convert_{0}_array_{6}_to_{5}({2}{1}, {3}{1}, {4});\n".format( self.type, self.name, input, output, count, win_type, host_part) else: # Nothing needed this yet. LOGGER.warn("TODO: implement copying of static array for {0}.{1}".format(self.type, self.name)) elif self.is_handle() and self.needs_unwrapping(): handle = self.type_info["data"] if direction == Direction.OUTPUT: LOGGER.err("OUTPUT parameter {0}.{1} cannot be unwrapped".format(self.type, self.name)) elif self.optional: return "{0}{1} = {2} ? {3} : 0;\n".format(output, self.name, self.value(input, conv), handle.driver_handle(self.value(input, conv))) else: return "{0}{1} = {2};\n".format(output, self.name, handle.driver_handle(self.value(input, conv))) elif self.is_generic_handle(): if direction == Direction.OUTPUT: LOGGER.err("OUTPUT parameter {0}.{1} cannot be unwrapped".format(self.type, self.name)) else: return "{0}{1} = wine_vk_unwrap_handle({2}{3}, {2}{1});\n".format(output, self.name, input, self.object_type) else: selector_part = ", {0}{1}".format(input, self.selector) if self.selector else "" if direction == Direction.OUTPUT: return "convert_{0}_host_to_{4}(&{2}{1}, &{3}{1}{5});\n".format(self.type, self.name, input, output, win_type, selector_part) else: ctx_param = "ctx, " if self.needs_alloc(conv, unwrap) else "" host_part = "host" if unwrap else "unwrapped_host" return "convert_{0}_{4}_to_{6}({5}&{2}{1}, &{3}{1}{7});\n".format(self.type, self.name, input, output, win_type, ctx_param, host_part, selector_part) elif self.is_static_array(): bytes_count = "{0} * sizeof({1})".format(self.array_len, self.type) return "memcpy({0}{1}, {2}{1}, {3});\n".format(output, self.name, input, bytes_count) elif direction == Direction.INPUT: return "{0}{1} = {2};\n".format(output, self.name, self.value(input, conv)) elif conv and direction == Direction.OUTPUT and self.is_pointer(): return "{0}{1} = PtrToUlong({2}{1});\n".format(output, self.name, input) else: return "{0}{1} = {2}{1};\n".format(output, self.name, input) def definition(self, align=False, conv=False): """ Generate prototype for given function. Args: align (bool, optional): Enable alignment if a type needs it. This adds WINE_VK_ALIGN(8) to a member. conv (bool, optional): Enable conversion if a type needs it. This appends '_host' to the name. """ if conv and (self.is_pointer() or self.is_pointer_size()): text = "PTR32 " + self.name if self.is_static_array(): text += "[{0}]".format(self.array_len) return text text = "" if self.is_const(): text += "const " if self.is_struct_forward_declaration(): text += "struct " text += self.type if conv and self.needs_host_type(): text += "32" if self.is_pointer(): text += " {0}{1}".format(self.pointer, self.name) else: if align and self.needs_alignment(): if conv: text += " DECLSPEC_ALIGN(8) " + self.name else: text += " WINE_VK_ALIGN(8) " + self.name else: text += " " + self.name if self.is_static_array(): text += "[{0}]".format(self.array_len) if self.is_bit_field(): text += ":{}".format(self.bit_width) return text def is_struct_forward_declaration(self): return self.struct_fwd_decl def is_bit_field(self): return self.bit_width is not None def needs_conversion(self, conv, unwrap, direction, struct_const): """ Check if member needs conversion. """ # we can't convert unions if we don't have a selector if self.is_union() and not self.selector: return False is_const = self.is_const() if self.is_pointer() else struct_const # const members don't needs output conversion unless they are structs with non-const pointers if direction == Direction.OUTPUT and is_const and not self.is_struct(): return False if direction == Direction.INPUT: # returnedonly members don't needs input conversions if not self.is_pointer() and self.returnedonly: return False # pointer arrays always need input conversion if conv and self.is_dynamic_array() and self.pointer_array: return True if self.is_handle(): if unwrap and self.handle.is_wrapped(): return True if conv and self.handle.is_dispatchable(): return True elif self.is_generic_handle(): if unwrap: return True elif self.is_struct() or self.is_union(): if self.struct.needs_conversion(conv, unwrap, direction, is_const): return True # if pointer member needs output conversion, it also needs input conversion # to allocate the pointer if direction == Direction.INPUT and self.is_pointer() and \ self.needs_conversion(conv, unwrap, Direction.OUTPUT, struct_const): return True return False class VkParam(VkVariable): """ Helper class which describes a parameter to a function call. """ def __init__(self, type_info, const=None, pointer=None, name=None, parent=None, array_len=None, dyn_array_len=None, object_type=None, optional=False): VkVariable.__init__(self, const=const, type_info=type_info, type=type_info["name"], name=name, pointer=pointer, array_len=array_len, dyn_array_len=dyn_array_len, object_type=object_type, optional=optional, parent=parent) self._set_format_string() def __repr__(self): return "{0} {1} {2} {3} {4} {5}".format(self.const, self.type, self.pointer, self.name, self.array_len, self.dyn_array_len) @staticmethod def from_xml(param, types, parent): """ Helper function to create VkParam from xml. """ # Parameter parsing is slightly tricky. All the data is contained within # a param tag, but some data is within subtags while others are text # before or after the type tag. # Common structure: # const char* pLayerName name_elem = param.find("name") array_len = None name = name_elem.text # Tail contains array length e.g. for blendConstants param of vkSetBlendConstants if name_elem.tail is not None: array_len = name_elem.tail.strip("[]") # Name of other parameter in function prototype, which stores the number of # elements pointed to be by this parameter. dyn_array_len = param.get("len", None) const = param.text.strip() if param.text else None type_elem = param.find("type") pointer = type_elem.tail.strip() if type_elem.tail.strip() != "" else None attr = param.get("optional") optional = attr and attr.startswith("true") # Some uint64_t are actually handles with a separate type param object_type = param.get("objecttype", None) # Since we have parsed all types before hand, this should not happen. type_info = types.get(type_elem.text, None) if type_info is None: LOGGER.err("type info not found for: {0}".format(type_elem.text)) return VkParam(type_info, const=const, pointer=pointer, name=name, array_len=array_len, dyn_array_len=dyn_array_len, object_type=object_type, optional=optional, parent=parent) def _set_format_string(self): """ Internal helper function to be used by constructor to set format string. """ # Determine a format string used by code generation for traces. # 64-bit types need a conversion function. self.format_conv = None if self.is_static_array() or self.is_pointer(): self.format_str = "%p" else: if self.type_info["category"] in ["bitmask"]: # Since 1.2.170 bitmasks can be 32 or 64-bit, check the basetype. if self.type_info["data"].type == "VkFlags64": self.format_str = "0x%s" self.format_conv = "wine_dbgstr_longlong({0})" else: self.format_str = "%#x" elif self.type_info["category"] in ["enum"]: self.format_str = "%#x" elif self.is_handle(): # We use uint64_t for non-dispatchable handles as opposed to pointers # for dispatchable handles. if self.handle.is_dispatchable(): self.format_str = "%p" else: self.format_str = "0x%s" self.format_conv = "wine_dbgstr_longlong({0})" elif self.type == "float": self.format_str = "%f" elif self.type == "int": self.format_str = "%d" elif self.type == "int32_t": self.format_str = "%d" elif self.type == "size_t": self.format_str = "0x%s" self.format_conv = "wine_dbgstr_longlong({0})" elif self.type in ["uint16_t", "uint32_t", "VkBool32"]: self.format_str = "%u" elif self.type in ["uint64_t", "VkDeviceAddress", "VkDeviceSize"]: self.format_str = "0x%s" self.format_conv = "wine_dbgstr_longlong({0})" elif self.type == "HANDLE": self.format_str = "%p" elif self.type in ["VisualID", "xcb_visualid_t", "RROutput", "zx_handle_t"]: # Don't care about specific types for non-Windows platforms. self.format_str = "" else: LOGGER.warn("Unhandled type: {0}".format(self.type_info)) def copy(self, direction, conv, unwrap, prefix=""): win_type = "win32" if conv else "win64" wrap_part = "" if unwrap or not self.needs_unwrapping() else "unwrapped_" if direction == Direction.INPUT: ctx_param = "&ctx, " if self.needs_alloc(conv, unwrap) else "" if self.is_dynamic_array(): return " {0}_host = convert_{2}_array_{4}_to_{6}host({5}{1}, {3});\n".format( self.name, self.value(prefix, conv), self.type, self.get_dyn_array_len(prefix, conv), win_type, ctx_param, wrap_part) elif self.optional: ret = " if ({0}{1})\n".format(prefix, self.name) ret += " {\n" ret += " {0}_host = conversion_context_alloc(&ctx, sizeof(*{0}_host));\n".format(self.name) ret += " convert_{0}_{3}_to_{5}host({4}{1}, {2}_host);\n".format( self.type, self.value(prefix, conv), self.name, win_type, ctx_param, wrap_part) ret += " }\n" return ret elif self.is_struct(): return " convert_{0}_{3}_to_{5}host({4}{1}, &{2}_host);\n".format( self.type, self.value(prefix, conv), self.name, win_type, ctx_param, wrap_part) elif self.is_pointer_size() and self.type != "size_t": return " {0}_host = UlongToPtr(*{1});\n".format(self.name, self.value(prefix, conv)) else: return " {0}_host = *{1};\n".format(self.name, self.value(prefix, conv)) else: if self.is_dynamic_array(): return " convert_{0}_array_{1}host_to_{2}({3}_host, {4}, {5});\n".format( self.type, wrap_part, win_type, self.name, self.value(prefix, conv), self.get_dyn_array_len(prefix, conv)) elif self.is_struct(): ref_part = "" if self.optional else "&" return " convert_{0}_host_to_{3}({4}{2}_host, {1});\n".format( self.type, self.value(prefix, conv), self.name, win_type, ref_part) elif self.is_pointer_size() and self.type != "size_t": return " *{0} = PtrToUlong({1}_host);\n".format(self.value(prefix, conv), self.name) else: return " *{0} = {1}_host;\n".format(self.value(prefix, conv), self.name) def definition(self, postfix=None, is_member=False, conv=False): """ Return prototype for the parameter. E.g. 'const char *foo' """ if is_member and conv and self.needs_ptr32_type(): return "PTR32 {0}".format(self.name) proto = "" if self.const: proto += self.const + " " proto += self.type name = self.name if conv and self.needs_host_type(): proto += "32" if is_member and self.needs_alignment(): proto += " DECLSPEC_ALIGN(8)" if self.is_pointer(): proto += " {0}{1}".format(self.pointer, name) elif is_member and self.is_static_array(): proto += " *" + name else: proto += " " + name # Allows appending something to the variable name useful for # win32 to host conversion. if postfix is not None: proto += postfix if not is_member and self.is_static_array(): proto += "[{0}]".format(self.array_len) return proto def dispatch_table(self, params_prefix, conv): """ Return functions dispatch table pointer for dispatchable objects. """ if not self.is_dispatchable(): return None return self.handle.dispatch_table(self.value(params_prefix, conv)) def format_string(self, conv): if conv and self.needs_ptr32_type() and (self.type != "size_t" or self.is_pointer()): return "%#x" return self.format_str def is_dispatchable(self): if not self.is_handle(): return False return self.handle.is_dispatchable() def needs_conversion(self, conv, unwrap, direction, parent_const=False): """ Check if param needs conversion. """ if self.is_struct(): return self.struct.needs_conversion(conv, unwrap, direction, self.is_const()) if self.is_handle(): # non-pointer handles are handled inline in thunks if not self.is_dynamic_array() and not self.is_static_array(): return conv and self.is_pointer() and self.handle.is_dispatchable() # vkAllocateCommandBuffers is a special case, we use it in our private thunk as an input param param_direction = (Direction.INPUT if self.is_const() else Direction.OUTPUT) if self.name == "pCommandBuffers": param_direction = Direction.INPUT if direction != param_direction: return False if unwrap and self.handle.is_wrapped(): return True if conv and self.handle.is_dispatchable(): return True elif self.is_pointer() and self.is_pointer_size(): return conv return False def needs_variable(self, conv, unwrap): if self.needs_conversion(conv, unwrap, Direction.INPUT): return True if self.needs_conversion(conv, unwrap, Direction.OUTPUT): return True return False def spec(self): """ Generate spec file entry for this parameter. """ if self.is_pointer() and self.type == "char": return "str" if self.is_dispatchable() or self.is_pointer() or self.is_static_array(): return "ptr" if self.type_info["category"] in ["bitmask"]: # Since 1.2.170 bitmasks can be 32 or 64-bit, check the basetype. if self.type_info["data"].type == "VkFlags64": return "int64" else: return "long" if self.type_info["category"] in ["enum"]: return "long" if self.is_handle() and not self.is_dispatchable(): return "int64" if self.type == "float": return "float" if self.type in ["int", "int32_t", "size_t", "uint16_t", "uint32_t", "VkBool32"]: return "long" if self.type in ["uint64_t", "VkDeviceSize"]: return "int64" LOGGER.error("Unhandled spec conversion for type: {0}".format(self.type)) def variable(self, conv, unwrap, params_prefix=""): """ Returns 'glue' code during generation of a function call on how to access the variable. This function handles various scenarios such as 'unwrapping' if dispatchable objects and renaming of parameters in case of win32 -> host conversion. Args: conv (bool, optional): Enable conversion if the param needs it. This appends '_host' to the name. """ # Hack until we enable allocation callbacks from ICD to application. These are a joy # to enable one day, because of calling convention conversion. if unwrap and "VkAllocationCallbacks" in self.type: LOGGER.debug("TODO: setting NULL VkAllocationCallbacks for {0}".format(self.name)) return "NULL" if self.needs_variable(conv, unwrap): if self.is_dynamic_array() or self.optional: return "{0}_host".format(self.name) else: return "&{0}_host".format(self.name) p = self.value(params_prefix, conv) if unwrap: unwrap_handle = None if self.object_type != None and self.type == "uint64_t": unwrap_handle = "wine_vk_unwrap_handle({0}{1}, {0}{2})".format( params_prefix, self.object_type, self.name) elif self.is_handle(): # We need to pass the native handle to the native Vulkan calls and # the wine driver's handle to calls which are wrapped by the driver. unwrap_handle = self.handle.driver_handle(p) if unwrap_handle: if self.optional: unwrap_handle = "{0}{1} ? {2} : 0".format(params_prefix, self.name, unwrap_handle) return unwrap_handle return p class VkStruct(Sequence): """ Class which represents the type union and struct. """ def __init__(self, name, members, returnedonly, structextends, alias=None, union=False): self.name = name self.members = members self.returnedonly = returnedonly self.structextends = structextends self.required = False self.alias = alias self.union = union self.type_info = None # To be set later. self.struct_extensions = [] self.aliased_by = [] def __getitem__(self, i): return self.members[i] def __len__(self): return len(self.members) @staticmethod def from_alias(struct, alias): name = struct.attrib.get("name") aliasee = VkStruct(name, alias.members, alias.returnedonly, alias.structextends, alias=alias) alias.add_aliased_by(aliasee) return aliasee @staticmethod def from_xml(struct): # Unions and structs are the same parsing wise, but we need to # know which one we are dealing with later on for code generation. union = True if struct.attrib["category"] == "union" else False name = struct.attrib.get("name") # 'Output' structures for which data is filled in by the API are # marked as 'returnedonly'. returnedonly = True if struct.attrib.get("returnedonly") else False # Those structs seem to be broken in spec, they are specified as # returned only, but documented as input structs. if name in ["VkSubpassShadingPipelineCreateInfoHUAWEI", "VkPipelineShaderStageRequiredSubgroupSizeCreateInfo"]: returnedonly = False # Those structs don't have returnedonly in spec, but they could (should?). if name in ["VkSurfaceCapabilitiesPresentBarrierNV", "VkCooperativeMatrixPropertiesNV", "VkPerformanceValueINTEL"]: returnedonly = True structextends = struct.attrib.get("structextends") structextends = structextends.split(",") if structextends else [] s = VkStruct(name, [], returnedonly, structextends, union=union) for member in struct.findall("member"): vk_member = VkMember.from_xml(member, returnedonly, s) s.members.append(vk_member) return s @staticmethod def decouple_structs(structs): """ Helper function which decouples a list of structs. Structures often depend on other structures. To make the C compiler happy we need to define 'substructures' first. This function analyzes the list of structures and reorders them in such a way that they are decoupled. """ tmp_structs = list(structs) # Don't modify the original structures. decoupled_structs = [] while (len(tmp_structs) > 0): # Iterate over a copy because we want to modify the list inside the loop. for struct in list(tmp_structs): dependends = False if not struct.required: tmp_structs.remove(struct) continue for m in struct: if not (m.is_struct() or m.is_union()): continue # VkBaseInstructure and VkBaseOutStructure reference themselves. if m.type == struct.name: break found = False # Check if a struct we depend on has already been defined. for s in decoupled_structs: if s.name == m.type: found = True break if not found: # Check if the struct we depend on is even in the list of structs. # If found now, it means we haven't met all dependencies before we # can operate on the current struct. # When generating 'host' structs we may not be able to find a struct # as the list would only contain the structs requiring conversion. for s in tmp_structs: if s.name == m.type: dependends = True break if dependends == False: decoupled_structs.append(struct) tmp_structs.remove(struct) return decoupled_structs def definition(self, align=False, conv=False): """ Convert structure to textual definition. Args: align (bool, optional): enable alignment to 64-bit for win32 struct compatibility. conv (bool, optional): enable struct conversion if the struct needs it. postfix (str, optional): text to append to end of struct name, useful for struct renaming. """ if self.is_alias(): return "" suffix = "32" if conv else "" if self.union: text = "typedef union {0}".format(self.name) else: text = "typedef struct {0}".format(self.name) text += suffix text += "\n{\n" for m in self: if align and m.needs_alignment(): text += " {0};\n".format(m.definition(align=align, conv=conv)) else: text += " {0};\n".format(m.definition(conv=conv)) text += "}} {0}{1};\n".format(self.name, suffix) for aliasee in self.aliased_by: text += "typedef {0}{2} {1}{2};\n".format(self.name, aliasee.name, suffix) return text def is_alias(self): return bool(self.alias) def add_aliased_by(self, aliasee): self.aliased_by.append(aliasee) def needs_alignment(self): """ Check if structure needs alignment for 64-bit data. Various structures need alignment on 64-bit variables due to compiler differences on 32-bit between Win32 and Linux. """ for m in self.members: if self.name == m.type: continue if m.needs_alignment(): return True return False def needs_unwrapping(self): """ Returns if struct members need unwrapping of handle. """ for m in self.members: if self.name == m.type: continue if m.needs_unwrapping(): return True return False def needs_extensions_conversion(self, conv, direction): """ Check if struct contains extensions chain that needs to be converted """ if direction == Direction.INPUT and self.name in STRUCT_CHAIN_CONVERSIONS: return True if not "pNext" in self: return False is_const = self.members[self.members.index("pNext")].is_const() # VkOpticalFlowSessionCreateInfoNV is missing const in its pNext pointer if self.name in ["VkOpticalFlowSessionCreateInfoNV", "VkDescriptorBufferBindingInfoEXT"]: is_const = True needs_output_copy = False for e in self.struct_extensions: if not e.required: continue if e.needs_conversion(conv, True, direction, is_const, check_extensions=False): return True if direction == Direction.INPUT: # we need input conversion of structs containing struct chain even if it's returnedonly, # so that we have a chance to allocate buffers if e.needs_conversion(conv, True, Direction.OUTPUT, is_const, check_extensions=False): return True return False def needs_conversion(self, conv, unwrap, direction, is_const, check_extensions=True): """ Check if struct needs conversion. """ # VkAllocationCallbacks never needs conversion if self.name == "VkAllocationCallbacks": return False # pFixedRateFlags field is missing const, but it doesn't need output conversion if direction == Direction.OUTPUT and self.name == "VkImageCompressionControlEXT": return False needs_output_copy = False for m in self.members: if self.name == m.type: continue if m.name == "pNext": # pNext is a pointer, so it always needs conversion if conv and direction == Direction.INPUT: return True # we need input conversion of structs containing struct chain even if it's returnedonly if direction == Direction.INPUT and \ self.needs_conversion(conv, unwrap, Direction.OUTPUT, is_const): return True continue # for non-pointer members, check for returnedonly and const attributes if not m.is_pointer() or m.type == "void": if direction == Direction.INPUT: if self.returnedonly: continue else: if is_const or m.is_const(): continue # check alignment and pointer-sized members for 32-bit conversions if conv and (direction == Direction.INPUT or not is_const): if m.is_pointer() or m.is_pointer_size(): return True # we don't check structs here, they will will be traversed by needs_conversion chain anyway if not m.is_struct() and m.needs_alignment(): return True if m.needs_conversion(conv, unwrap, direction, is_const): return True # pointers will be handled by needs_conversion, but if we have any other non-const # member, we may need to copy output if direction == Direction.OUTPUT and not m.is_pointer() and not is_const and not m.is_const(): needs_output_copy = True # if output needs any copy and we need input conversion, then we also need output conversion if needs_output_copy and self.needs_conversion(conv, unwrap, Direction.INPUT, check_extensions): return True return check_extensions and self.needs_extensions_conversion(conv, direction) def needs_alloc(self, conv, unwrap): """ Check if any struct member needs some memory allocation.""" if self.needs_extensions_conversion(conv, Direction.INPUT): return True for m in self.members: if self.name == m.type: continue if m.needs_alloc(conv, unwrap): return True return False def needs_host_type(self): # VkAllocationCallbacks never needs conversion if self.name == "VkAllocationCallbacks": return False for m in self.members: if self.name == m.type: continue if m.is_pointer() or m.is_pointer_size(): return True if m.needs_alignment(): return True if (m.is_struct() or m.is_union()) and m.struct.needs_host_type(): return True def set_type_info(self, types): """ Helper function to set type information from the type registry. This is needed, because not all type data is available at time of parsing. """ for m in self.members: type_info = types[m.type] m.set_type_info(type_info) def get_conversions(self, unwrap, parent_const): conversions = [] # Collect any conversion for any extension structs. for e in self.struct_extensions: if not e.required: continue conversions.extend(e.get_conversions(True, parent_const)) # Collect any conversion for any member structs. for m in self: if m.type == self.name: continue conversions.extend(m.get_conversions(unwrap, parent_const)) return conversions class StructConversionFunction(object): def __init__(self, struct, direction, conv, unwrap, const): self.direction = direction self.operand = struct self.type = struct.name self.conv = conv self.unwrap = unwrap or not self.operand.needs_unwrapping() self.const = const name = "convert_{0}_".format(self.type) win_type = "win32" if self.conv else "win64" host_part = "host" if self.unwrap else "unwrapped_host" if self.direction == Direction.INPUT: name += "{0}_to_{1}".format(win_type, host_part) else: # Direction.OUTPUT name += "{0}_to_{1}".format(host_part, win_type) self.name = name def __eq__(self, other): return self.name == other.name def member_needs_copy(self, struct, m): if self.direction == Direction.OUTPUT: if m.name in ["sType", "pNext"]: return False if self.const and not m.is_pointer(): return False if m.is_const() and not m.needs_conversion(self.conv, self.unwrap, Direction.OUTPUT, self.const): return False else: if m.name == "pNext": return True if m.name != "sType" and struct.returnedonly and not m.needs_conversion( self.conv, self.unwrap, Direction.INPUT, self.const): return False return True def definition(self): """ Helper function for generating a struct conversion function. """ # It doesn't make sense to generate conversion functions for non-struct variables # which aren't in arrays, as this should be handled by the copy() function if not isinstance(self.operand, VkStruct): return "" body = "" if not self.conv: body += "#ifdef _WIN64\n" needs_alloc = self.direction != Direction.OUTPUT and self.operand.needs_alloc(self.conv, self.unwrap) win_type = self.type if self.conv and self.operand.needs_host_type(): win_type += "32" if self.direction == Direction.OUTPUT and self.const: win_type = "const " + win_type if self.conv: body += "static inline void {0}(".format(self.name) if self.direction == Direction.OUTPUT: params = ["const {0} *in".format(self.type), "{0} *out".format(win_type)] else: params = ["const {0} *in".format(win_type), "{0} *out".format(self.type)] if self.operand.union: params.append("VkFlags selector") # Generate parameter list if needs_alloc: body += "struct conversion_context *ctx, " body += ", ".join(p for p in params) body += ")\n" else: body += "static inline void {0}(".format(self.name) params = ["const {0} *in".format(self.type), "{0} *out".format(self.type)] # Generate parameter list if needs_alloc: body += "struct conversion_context *ctx, " body += ", ".join(p for p in params) body += ")\n" needs_extensions = self.operand.needs_extensions_conversion(self.conv, self.direction) body += "{\n" if needs_extensions: if self.direction == Direction.INPUT: if self.conv: body += " const VkBaseInStructure32 *in_header;\n" else: body += " const VkBaseInStructure *in_header;\n" body += " VkBaseOutStructure *out_header = (void *)out;\n\n" else: body += " const VkBaseInStructure *in_header;\n" if self.conv: body += " VkBaseOutStructure32 *out_header = (void *)out;\n\n" else: body += " VkBaseOutStructure *out_header = (void *)out;\n\n" body += " if (!in) return;\n\n" for m in self.operand: if not self.member_needs_copy(self.operand, m): continue if m.name == "pNext" and (needs_extensions or self.conv): body += " out->pNext = NULL;\n" continue if m.selection: body += " if (" body += " || ".join("selector == {}".format(s) for s in m.selection) body += ")\n " body += " " + m.copy("in->", "out->", self.direction, self.conv, self.unwrap) if needs_extensions: if self.conv and self.direction == Direction.INPUT: body += "\n for (in_header = UlongToPtr(in->pNext); in_header; in_header = UlongToPtr(in_header->pNext))\n" else: body += "\n for (in_header = (void *)in->pNext; in_header; in_header = (void *)in_header->pNext)\n" body += " {\n" body += " switch (in_header->sType)\n" body += " {\n" ident = " " if self.direction == Direction.INPUT and self.type in STRUCT_CHAIN_CONVERSIONS: for i in STRUCT_CHAIN_CONVERSIONS[self.type]: body += " case {0}:\n".format(i) body += ident + "break;\n" for ext in self.operand.struct_extensions: if not ext.required: continue if self.direction == Direction.OUTPUT and not any([self.member_needs_copy(ext, m) for m in ext]): continue stype = next(x for x in ext.members if x.name == "sType").values win_type = ext.name + "32" if self.conv and ext.needs_host_type() else ext.name if self.direction == Direction.INPUT: in_type = "const " + win_type out_type = ext.name else: in_type = "const " + ext.name out_type = win_type body += " case {0}:\n".format(stype) body += " {\n" if self.direction == Direction.INPUT: body += ident + "{0} *out_ext = conversion_context_alloc(ctx, sizeof(*out_ext));\n".format(out_type) elif self.conv: body += ident + "{0} *out_ext = find_next_struct32(out_header, {1});\n".format(out_type, stype) else: body += ident + "{0} *out_ext = find_next_struct(out_header, {1});\n".format(out_type, stype) copy_body = "" for m in ext: if m.name == "sType": copy_body += ident + "out_ext->sType = {0};\n".format(stype) continue if not self.member_needs_copy(ext, m): continue if m.name == "pNext": copy_body += ident + "out_ext->pNext = NULL;\n" continue copy_body += ident + m.copy("in_ext->", "out_ext->", self.direction, self.conv, True) # Generate the definition of "in_ext" if we need it if "in_ext->" in copy_body: body += ident + "{0} *in_ext = ({0} *)in_header;\n".format(in_type) body += copy_body if self.direction == Direction.INPUT: body += ident + "out_header->pNext = (void *)out_ext;\n" body += ident + "out_header = (void *)out_ext;\n" body += ident + "break;\n" body += " }\n" body += " default:\n" if self.direction == Direction.INPUT: body += ident + "FIXME(\"Unhandled sType %u.\", in_header->sType);\n" body += " break;\n" body += " }\n" body += " }\n" elif self.conv and self.direction == Direction.INPUT and "pNext" in self.operand: body += " if (in->pNext)\n" body += " FIXME(\"Unexpected pNext\\n\");\n" body += "}\n" if not self.conv: body += "#endif /* _WIN64 */\n" body += "\n" return body class ArrayConversionFunction(object): def __init__(self, array, direction, conv, unwrap): self.array = array self.direction = direction self.type = array.type self.conv = conv self.unwrap = unwrap or not array.needs_unwrapping() if array.is_static_array() and direction == Direction.INPUT: LOGGER.error("Static array input conversion is not supported") name = "convert_{0}_".format(array.type) if array.pointer_array: name += "pointer_" name += "array_" win_type = "win32" if self.conv else "win64" host_part = "host" if self.unwrap else "unwrapped_host" if self.direction == Direction.INPUT: name += "{0}_to_{1}".format(win_type, host_part) else: # Direction.OUTPUT name += "{0}_to_{1}".format(host_part, win_type) self.name = name def __eq__(self, other): return self.name == other.name def definition(self): """ Helper function for generating a conversion function for array operands. """ body = "" if not self.conv: body += "#ifdef _WIN64\n" needs_alloc = self.direction != Direction.OUTPUT and self.array.needs_alloc(self.conv, self.unwrap) win_type = self.type if self.conv: if self.array.needs_host_type(): win_type += "32" elif self.array.is_handle() and self.array.handle.is_dispatchable(): win_type = "PTR32" if self.direction == Direction.OUTPUT and self.array.is_const(): win_type = "const " + win_type pointer_part = self.array.pointer if self.array.pointer else "*" if self.direction == Direction.OUTPUT: params = ["const {0} {1}in".format(self.type, pointer_part), "{0} {1}out".format(win_type, pointer_part), "uint32_t count"] return_type = None elif self.conv and self.array.pointer_array: params = ["const PTR32 *in", "uint32_t count"] return_type = self.type else: params = ["const {0} {1}in".format(win_type, pointer_part), "uint32_t count"] return_type = self.type needs_copy = not self.array.is_struct() or self.direction != Direction.INPUT or \ not self.array.struct.returnedonly or "pNext" in self.array.struct # Generate function prototype. if return_type: body += "static inline {0}{1} {2}{3}(".format( "const " if self.array.is_const() else "", return_type, pointer_part, self.name) else: body += "static inline void {0}(".format(self.name) if needs_alloc: body += "struct conversion_context *ctx, " body += ", ".join(p for p in params) body += ")\n{\n" if return_type: body += " {0} {1}out;\n".format(return_type, "**" if self.array.pointer_array else "*") if needs_copy: body += " unsigned int i;\n\n" if return_type: body += " if (!in || !count) return NULL;\n\n" else: body += " if (!in) return;\n\n" if self.direction == Direction.INPUT: body += " out = conversion_context_alloc(ctx, count * sizeof(*out));\n" if needs_copy: body += " for (i = 0; i < count; i++)\n" body += " {\n" if self.array.is_struct(): struct = self.array.struct win_part = "win32" if self.conv else "win64" host_part = "host" if self.unwrap else "unwrapped_host" if self.direction == Direction.INPUT: conv_suffix = "{0}_to_{1}".format(win_part, host_part) else: conv_suffix = "{0}_to_{1}".format(host_part, win_part) ctx_part = "" if self.direction == Direction.INPUT and struct.needs_alloc(self.conv, self.unwrap): ctx_part = "ctx, " if not self.array.pointer_array: body += " convert_{0}_{1}({2}&in[i], &out[i]);\n".format( struct.name, conv_suffix, ctx_part) else: if struct.needs_conversion(self.conv, self.unwrap, self.direction, False): body += " if (in[i])\n" body += " {\n" body += " out[i] = conversion_context_alloc(ctx, sizeof(*out[i]));\n" if self.conv: in_param = "({0} *)UlongToPtr(in[i])".format(win_type) else: in_param = "in[i]" body += " convert_{0}_{1}({2}{3}, out[i]);\n".format( struct.name, conv_suffix, ctx_part, in_param) body += " }\n" body += " else\n" body += " out[i] = NULL;\n" else: body += " out[i] = UlongToPtr(in[i]);\n".format(win_type) elif self.array.is_handle(): if self.array.pointer_array: LOGGER.error("Unhandled handle pointer arrays") handle = self.array.handle if not self.conv or not handle.is_dispatchable(): input = "in[i]" elif self.direction == Direction.INPUT: input = "UlongToPtr(in[i])" else: input = "PtrToUlong(in[i])" if not self.unwrap or not handle.is_wrapped(): body += " out[i] = {0};\n".format(input) elif self.direction == Direction.INPUT: body += " out[i] = " + handle.driver_handle(input) + ";\n" else: LOGGER.warning("Unhandled handle output conversion") elif self.array.pointer_array: body += " out[i] = UlongToPtr(in[i]);\n" else: body += " out[i] = in[i];\n" body += " }\n" if return_type: body += "\n return {0}out;\n".format("(void *)" if self.array.pointer_array else "") body += "}\n" if not self.conv: body += "#endif /* _WIN64 */\n" body += "\n" return body class VkGenerator(object): def __init__(self, registry): self.registry = registry # Build a list conversion functions for struct conversion. self.conversions = [] self.host_structs = [] for func in self.registry.funcs.values(): if not func.needs_exposing(): continue conversions = func.get_conversions() for conv in conversions: # Append if we don't already have this conversion. if not any(c == conv for c in self.conversions): self.conversions.append(conv) if not isinstance(conv, StructConversionFunction): continue for e in conv.operand.struct_extensions: if not e.required or not e.needs_host_type(): continue if not any(s.name == e.name for s in self.host_structs): self.host_structs.append(e) if not conv.operand.needs_host_type(): continue # Structs can be used in different ways by different conversions # e.g. array vs non-array. Just make sure we pull in each struct once. if not any(s.name == conv.operand.name for s in self.host_structs): self.host_structs.append(conv.operand) def _generate_copyright(self, f, spec_file=False): f.write("# " if spec_file else "/* ") f.write("Automatically generated from Vulkan vk.xml; DO NOT EDIT!\n") lines = ["", "This file is generated from Vulkan vk.xml file covered", "by the following copyright and permission notice:"] lines.extend([l.rstrip(" ") for l in self.registry.copyright.splitlines()]) for line in lines: f.write("{0}{1}".format("# " if spec_file else " * ", line).rstrip(" ") + "\n") f.write("\n" if spec_file else " */\n\n") def generate_thunks_c(self, f): self._generate_copyright(f) f.write("#if 0\n") f.write("#pragma makedep unix\n") f.write("#endif\n\n") f.write("#include \"config.h\"\n\n") f.write("#include \n\n") f.write("#include \"vulkan_private.h\"\n\n") f.write("WINE_DEFAULT_DEBUG_CHANNEL(vulkan);\n\n") for struct in self.host_structs: f.write(struct.definition(conv=True, align=True)) f.write("\n") f.write("static uint64_t wine_vk_unwrap_handle(uint32_t type, uint64_t handle)\n") f.write("{\n") f.write(" switch(type)\n") f.write(" {\n") for handle in self.registry.handles: if not handle.is_required() or not handle.is_wrapped() or handle.is_alias(): continue f.write(" case {}:\n".format(handle.object_type)) if handle.is_dispatchable(): f.write(" return (uint64_t) (uintptr_t) ") f.write(handle.native_handle("(({}) (uintptr_t) handle)".format(handle.name))) else: f.write(" return (uint64_t) ") f.write(handle.native_handle("handle")) f.write(";\n"); f.write(" default:\n") f.write(" return handle;\n") f.write(" }\n") f.write("}\n\n") # Generate any conversion helper functions. for conv in self.conversions: f.write(conv.definition()) # Create thunks for instance and device functions. # Global functions don't go through the thunks. for vk_func in self.registry.funcs.values(): if not vk_func.needs_exposing(): continue if vk_func.loader_thunk_type == ThunkType.NONE: continue f.write(vk_func.thunk(prefix="thunk64_")) f.write(vk_func.thunk(prefix="thunk32_", conv=True)) # Create array of device extensions. f.write("static const char * const vk_device_extensions[] =\n{\n") for ext in self.registry.extensions: if ext["type"] != "device": continue if ext["name"] in UNEXPOSED_EXTENSIONS: continue f.write(" \"{0}\",\n".format(ext["name"])) f.write("};\n\n") # Create array of instance extensions. f.write("static const char * const vk_instance_extensions[] =\n{\n") for ext in self.registry.extensions: if ext["type"] != "instance": continue if ext["name"] in UNEXPOSED_EXTENSIONS: continue f.write(" \"{0}\",\n".format(ext["name"])) f.write("};\n\n") f.write("BOOL wine_vk_device_extension_supported(const char *name)\n") f.write("{\n") f.write(" unsigned int i;\n") f.write(" for (i = 0; i < ARRAY_SIZE(vk_device_extensions); i++)\n") f.write(" {\n") f.write(" if (strcmp(vk_device_extensions[i], name) == 0)\n") f.write(" return TRUE;\n") f.write(" }\n") f.write(" return FALSE;\n") f.write("}\n\n") f.write("BOOL wine_vk_instance_extension_supported(const char *name)\n") f.write("{\n") f.write(" unsigned int i;\n") f.write(" for (i = 0; i < ARRAY_SIZE(vk_instance_extensions); i++)\n") f.write(" {\n") f.write(" if (strcmp(vk_instance_extensions[i], name) == 0)\n") f.write(" return TRUE;\n") f.write(" }\n") f.write(" return FALSE;\n") f.write("}\n\n") f.write("BOOL wine_vk_is_type_wrapped(VkObjectType type)\n") f.write("{\n") f.write(" return FALSE") for handle in self.registry.handles: if not handle.is_required() or not handle.is_wrapped() or handle.is_alias(): continue f.write(" ||\n type == {}".format(handle.object_type)) f.write(";\n") f.write("}\n\n") f.write("#ifdef _WIN64\n\n") f.write("const unixlib_entry_t __wine_unix_call_funcs[] =\n") f.write("{\n") f.write(" init_vulkan,\n") f.write(" vk_is_available_instance_function,\n") f.write(" vk_is_available_device_function,\n") for vk_func in self.registry.funcs.values(): if not vk_func.needs_exposing(): continue if vk_func.loader_thunk_type == ThunkType.NONE: continue f.write(" {1}{0},\n".format(vk_func.name, "thunk64_")) f.write("};\n") f.write("C_ASSERT(ARRAYSIZE(__wine_unix_call_funcs) == unix_count);\n\n") f.write("#endif /* _WIN64 */\n\n") f.write("#ifdef _WIN64\n") f.write("const unixlib_entry_t __wine_unix_call_wow64_funcs[] =\n") f.write("#else\n") f.write("const unixlib_entry_t __wine_unix_call_funcs[] =\n") f.write("#endif\n") f.write("{\n") f.write(" init_vulkan,\n") f.write(" vk_is_available_instance_function32,\n") f.write(" vk_is_available_device_function32,\n") for vk_func in self.registry.funcs.values(): if not vk_func.needs_exposing(): continue if vk_func.loader_thunk_type == ThunkType.NONE: continue f.write(" {1}{0},\n".format(vk_func.name, "thunk32_")) f.write("};\n") f.write("C_ASSERT(ARRAYSIZE(__wine_unix_call_funcs) == unix_count);\n") def generate_thunks_h(self, f, prefix): self._generate_copyright(f) f.write("#ifndef __WINE_VULKAN_THUNKS_H\n") f.write("#define __WINE_VULKAN_THUNKS_H\n\n") f.write("#define WINE_VK_VERSION VK_API_VERSION_{0}_{1}\n\n".format(WINE_VK_VERSION[0], WINE_VK_VERSION[1])) # Generate prototypes for device and instance functions requiring a custom implementation. f.write("/* Functions for which we have custom implementations outside of the thunks. */\n") for vk_func in self.registry.funcs.values(): if not vk_func.needs_private_thunk(): continue f.write("{0};\n".format(vk_func.prototype(prefix=prefix, postfix="DECLSPEC_HIDDEN", is_thunk=True))) f.write("\n") f.write("/* For use by vkDevice and children */\n") f.write("struct vulkan_device_funcs\n{\n") for vk_func in self.registry.device_funcs: if not vk_func.needs_exposing(): continue if not vk_func.needs_dispatch(): LOGGER.debug("skipping {0} in vulkan_device_funcs".format(vk_func.name)) continue f.write(" {0};\n".format(vk_func.pfn())) f.write("};\n\n") f.write("/* For use by vkInstance and children */\n") f.write("struct vulkan_instance_funcs\n{\n") for vk_func in self.registry.instance_funcs + self.registry.phys_dev_funcs: if not vk_func.needs_exposing(): continue if not vk_func.needs_dispatch(): LOGGER.debug("skipping {0} in vulkan_instance_funcs".format(vk_func.name)) continue f.write(" {0};\n".format(vk_func.pfn())) f.write("};\n\n") f.write("#define ALL_VK_DEVICE_FUNCS() \\\n") first = True for vk_func in self.registry.device_funcs: if not vk_func.needs_exposing(): continue if not vk_func.needs_dispatch(): LOGGER.debug("skipping {0} in ALL_VK_DEVICE_FUNCS".format(vk_func.name)) continue if first: f.write(" USE_VK_FUNC({0})".format(vk_func.name)) first = False else: f.write(" \\\n USE_VK_FUNC({0})".format(vk_func.name)) f.write("\n\n") f.write("#define ALL_VK_INSTANCE_FUNCS() \\\n") first = True for vk_func in self.registry.instance_funcs + self.registry.phys_dev_funcs: if not vk_func.needs_exposing(): continue if not vk_func.needs_dispatch(): LOGGER.debug("skipping {0} in ALL_VK_INSTANCE_FUNCS".format(vk_func.name)) continue if first: f.write(" USE_VK_FUNC({0})".format(vk_func.name)) first = False else: f.write(" \\\n USE_VK_FUNC({0})".format(vk_func.name)) f.write("\n\n") f.write("#endif /* __WINE_VULKAN_THUNKS_H */\n") def generate_loader_thunks_c(self, f): self._generate_copyright(f) f.write("#include \"vulkan_loader.h\"\n\n") f.write("WINE_DEFAULT_DEBUG_CHANNEL(vulkan);\n\n") for vk_func in self.registry.funcs.values(): if not vk_func.needs_exposing(): continue if vk_func.loader_thunk_type != ThunkType.PUBLIC: continue f.write(vk_func.loader_thunk()) f.write("static const struct vulkan_func vk_device_dispatch_table[] =\n{\n") for vk_func in self.registry.device_funcs: if not vk_func.needs_exposing(): continue f.write(" {{\"{0}\", {0}}},\n".format(vk_func.name)) f.write("};\n\n") f.write("static const struct vulkan_func vk_phys_dev_dispatch_table[] =\n{\n") for vk_func in self.registry.phys_dev_funcs: if not vk_func.needs_exposing(): continue f.write(" {{\"{0}\", {0}}},\n".format(vk_func.name)) f.write("};\n\n") f.write("static const struct vulkan_func vk_instance_dispatch_table[] =\n{\n") for vk_func in self.registry.instance_funcs: if not vk_func.needs_exposing(): continue f.write(" {{\"{0}\", {0}}},\n".format(vk_func.name)) f.write("};\n\n") f.write("void *wine_vk_get_device_proc_addr(const char *name)\n") f.write("{\n") f.write(" unsigned int i;\n") f.write(" for (i = 0; i < ARRAY_SIZE(vk_device_dispatch_table); i++)\n") f.write(" {\n") f.write(" if (strcmp(vk_device_dispatch_table[i].name, name) == 0)\n") f.write(" {\n") f.write(" TRACE(\"Found name=%s in device table\\n\", debugstr_a(name));\n") f.write(" return vk_device_dispatch_table[i].func;\n") f.write(" }\n") f.write(" }\n") f.write(" return NULL;\n") f.write("}\n\n") f.write("void *wine_vk_get_phys_dev_proc_addr(const char *name)\n") f.write("{\n") f.write(" unsigned int i;\n") f.write(" for (i = 0; i < ARRAY_SIZE(vk_phys_dev_dispatch_table); i++)\n") f.write(" {\n") f.write(" if (strcmp(vk_phys_dev_dispatch_table[i].name, name) == 0)\n") f.write(" {\n") f.write(" TRACE(\"Found name=%s in physical device table\\n\", debugstr_a(name));\n") f.write(" return vk_phys_dev_dispatch_table[i].func;\n") f.write(" }\n") f.write(" }\n") f.write(" return NULL;\n") f.write("}\n\n") f.write("void *wine_vk_get_instance_proc_addr(const char *name)\n") f.write("{\n") f.write(" unsigned int i;\n") f.write(" for (i = 0; i < ARRAY_SIZE(vk_instance_dispatch_table); i++)\n") f.write(" {\n") f.write(" if (strcmp(vk_instance_dispatch_table[i].name, name) == 0)\n") f.write(" {\n") f.write(" TRACE(\"Found name=%s in instance table\\n\", debugstr_a(name));\n") f.write(" return vk_instance_dispatch_table[i].func;\n") f.write(" }\n") f.write(" }\n") f.write(" return NULL;\n") f.write("}\n") def generate_loader_thunks_h(self, f): self._generate_copyright(f) f.write("#ifndef __WINE_VULKAN_LOADER_THUNKS_H\n") f.write("#define __WINE_VULKAN_LOADER_THUNKS_H\n\n") f.write("enum unix_call\n") f.write("{\n") f.write(" unix_init,\n") f.write(" unix_is_available_instance_function,\n") f.write(" unix_is_available_device_function,\n") for vk_func in self.registry.funcs.values(): if not vk_func.needs_exposing(): continue if vk_func.loader_thunk_type == ThunkType.NONE: continue f.write(" unix_{0},\n".format(vk_func.name)) f.write(" unix_count,\n") f.write("};\n\n") for vk_func in self.registry.funcs.values(): if not vk_func.needs_exposing(): continue if vk_func.loader_thunk_type == ThunkType.NONE: continue f.write("struct {0}_params\n".format(vk_func.name)) f.write("{\n"); for p in vk_func.params: f.write(" {0};\n".format(p.definition(is_member=True))) if vk_func.extra_param: f.write(" void *{0};\n".format(vk_func.extra_param)) if vk_func.type != "void": f.write(" {0} result;\n".format(vk_func.type)) f.write("};\n\n"); f.write("#endif /* __WINE_VULKAN_LOADER_THUNKS_H */\n") def generate_vulkan_h(self, f): self._generate_copyright(f) f.write("#ifndef __WINE_VULKAN_H\n") f.write("#define __WINE_VULKAN_H\n\n") f.write("#include \n") f.write("#include \n\n") f.write("/* Define WINE_VK_HOST to get 'host' headers. */\n") f.write("#ifdef WINE_VK_HOST\n") f.write("#define VKAPI_CALL\n") f.write('#define WINE_VK_ALIGN(x)\n') f.write("#endif\n\n") f.write("#ifndef VKAPI_CALL\n") f.write("#define VKAPI_CALL __stdcall\n") f.write("#endif\n\n") f.write("#ifndef VKAPI_PTR\n") f.write("#define VKAPI_PTR VKAPI_CALL\n") f.write("#endif\n\n") f.write("#ifndef WINE_VK_ALIGN\n") f.write("#define WINE_VK_ALIGN DECLSPEC_ALIGN\n") f.write("#endif\n\n") # The overall strategy is to define independent constants and datatypes, # prior to complex structures and function calls to avoid forward declarations. for const in self.registry.consts: # For now just generate things we may not need. The amount of parsing needed # to get some of the info is tricky as you need to figure out which structure # references a certain constant. f.write(const.definition()) f.write("\n") for define in self.registry.defines: f.write(define.definition()) for handle in self.registry.handles: # For backward compatibility also create definitions for aliases. # These types normally don't get pulled in as we use the new types # even in legacy functions if they are aliases. if handle.is_required() or handle.is_alias(): f.write(handle.definition()) f.write("\n") for base_type in self.registry.base_types: f.write(base_type.definition()) f.write("\n") for bitmask in self.registry.bitmasks: f.write(bitmask.definition()) f.write("\n") # Define enums, this includes values for some of the bitmask types as well. for enum in self.registry.enums.values(): if enum.required: f.write(enum.definition()) for fp in self.registry.funcpointers: if fp.required: f.write(fp.definition()) f.write("\n") # This generates both structures and unions. Since structures # may depend on other structures/unions, we need a list of # decoupled structs. # Note: unions are stored in structs for dependency reasons, # see comment in parsing section. structs = VkStruct.decouple_structs(self.registry.structs) for struct in structs: LOGGER.debug("Generating struct: {0}".format(struct.name)) f.write(struct.definition(align=True)) f.write("\n") for func in self.registry.funcs.values(): if not func.is_required(): LOGGER.debug("Skipping PFN definition for: {0}".format(func.name)) continue f.write("typedef {0};\n".format(func.pfn(prefix="PFN", call_conv="VKAPI_PTR"))) f.write("\n") f.write("#ifndef VK_NO_PROTOTYPES\n") for func in self.registry.funcs.values(): if not func.is_required(): LOGGER.debug("Skipping API definition for: {0}".format(func.name)) continue LOGGER.debug("Generating API definition for: {0}".format(func.name)) f.write("{0};\n".format(func.prototype(call_conv="VKAPI_CALL"))) f.write("#endif /* VK_NO_PROTOTYPES */\n\n") f.write("#endif /* __WINE_VULKAN_H */\n") def generate_vulkan_driver_h(self, f): self._generate_copyright(f) f.write("#ifndef __WINE_VULKAN_DRIVER_H\n") f.write("#define __WINE_VULKAN_DRIVER_H\n\n") f.write("/* Wine internal vulkan driver version, needs to be bumped upon vulkan_funcs changes. */\n") f.write("#define WINE_VULKAN_DRIVER_VERSION {0}\n\n".format(DRIVER_VERSION)) f.write("struct vulkan_funcs\n{\n") f.write(" /* Vulkan global functions. These are the only calls at this point a graphics driver\n") f.write(" * needs to provide. Other function calls will be provided indirectly by dispatch\n") f.write(" * tables part of dispatchable Vulkan objects such as VkInstance or vkDevice.\n") f.write(" */\n") for vk_func in self.registry.funcs.values(): if not vk_func.is_driver_func(): continue pfn = vk_func.pfn() # Avoid PFN_vkVoidFunction in driver interface as Vulkan likes to put calling convention # stuff in there. For simplicity substitute with "void *". pfn = pfn.replace("PFN_vkVoidFunction", "void *") f.write(" {0};\n".format(pfn)) f.write("\n") f.write(" /* winevulkan specific functions */\n") f.write(" VkSurfaceKHR (*p_wine_get_native_surface)(VkSurfaceKHR);\n") f.write("};\n\n") f.write("extern const struct vulkan_funcs * __wine_get_vulkan_driver(UINT version);\n\n") f.write("static inline void *get_vulkan_driver_device_proc_addr(\n") f.write(" const struct vulkan_funcs *vulkan_funcs, const char *name)\n{\n") f.write(" if (!name || name[0] != 'v' || name[1] != 'k') return NULL;\n\n") f.write(" name += 2;\n\n") for vk_func in self.registry.funcs.values(): if vk_func.is_driver_func() and vk_func.is_device_func(): f.write(' if (!strcmp(name, "{0}"))\n'.format(vk_func.name[2:])) f.write(' return vulkan_funcs->p_{0};\n'.format(vk_func.name)) f.write("\n") f.write(" return NULL;\n}\n\n") f.write("static inline void *get_vulkan_driver_instance_proc_addr(\n") f.write(" const struct vulkan_funcs *vulkan_funcs, VkInstance instance, const char *name)\n{\n") f.write(" if (!name || name[0] != 'v' || name[1] != 'k') return NULL;\n\n") f.write(" name += 2;\n\n") for vk_func in self.registry.funcs.values(): if vk_func.is_driver_func() and vk_func.is_global_func() and vk_func.name != "vkGetInstanceProcAddr": f.write(' if (!strcmp(name, "{0}"))\n'.format(vk_func.name[2:])) f.write(' return vulkan_funcs->p_{0};\n'.format(vk_func.name)) f.write("\n") f.write(" if (!instance) return NULL;\n\n") for vk_func in self.registry.funcs.values(): if vk_func.is_driver_func() and (vk_func.is_instance_func() or vk_func.is_phys_dev_func()): f.write(' if (!strcmp(name, "{0}"))\n'.format(vk_func.name[2:])) f.write(' return vulkan_funcs->p_{0};\n'.format(vk_func.name)) f.write("\n") f.write(" name -= 2;\n\n") f.write(" return get_vulkan_driver_device_proc_addr(vulkan_funcs, name);\n}\n\n") f.write("#endif /* __WINE_VULKAN_DRIVER_H */\n") def generate_vulkan_spec(self, f): self._generate_copyright(f, spec_file=True) f.write("@ stdcall -private vk_icdGetInstanceProcAddr(ptr str)\n") f.write("@ stdcall -private vk_icdGetPhysicalDeviceProcAddr(ptr str)\n") f.write("@ stdcall -private vk_icdNegotiateLoaderICDInterfaceVersion(ptr)\n") # Export symbols for all Vulkan Core functions. for func in self.registry.funcs.values(): if not func.is_core_func(): continue # We support all Core functions except for VK_KHR_display* APIs. # Create stubs for unsupported Core functions. if func.is_required(): f.write(func.spec()) else: f.write("@ stub {0}\n".format(func.name)) f.write("@ stdcall -private DllRegisterServer()\n") f.write("@ stdcall -private DllUnregisterServer()\n") def generate_vulkan_loader_spec(self, f): self._generate_copyright(f, spec_file=True) # Export symbols for all Vulkan Core functions. for func in self.registry.funcs.values(): if not func.is_core_func(): continue # We support all Core functions except for VK_KHR_display* APIs. # Create stubs for unsupported Core functions. if func.is_required(): f.write(func.spec(symbol="winevulkan." + func.name)) else: f.write("@ stub {0}\n".format(func.name)) class VkRegistry(object): def __init__(self, reg_filename): # Used for storage of type information. self.base_types = None self.bitmasks = None self.consts = None self.defines = None self.enums = None self.funcpointers = None self.handles = None self.structs = None # We aggregate all types in here for cross-referencing. self.funcs = {} self.types = {} self.version_regex = re.compile( r'^' r'VK_VERSION_' r'(?P[0-9])' r'_' r'(?P[0-9])' r'$' ) # Overall strategy for parsing the registry is to first # parse all type / function definitions. Then parse # features and extensions to decide which types / functions # to actually 'pull in' for code generation. For each type or # function call we want we set a member 'required' to True. tree = ET.parse(reg_filename) root = tree.getroot() self._parse_enums(root) self._parse_types(root) self._parse_commands(root) # Pull in any required types and functions. self._parse_features(root) self._parse_extensions(root) for enum in self.enums.values(): enum.fixup_64bit_aliases() self._match_object_types() self.copyright = root.find('./comment').text def _is_feature_supported(self, feature): version = self.version_regex.match(feature) if not version: return True version = tuple(map(int, version.group('major', 'minor'))) return version <= WINE_VK_VERSION def _is_extension_supported(self, extension): # We disable some extensions as either we haven't implemented # support yet or because they are for platforms other than win32. return extension not in UNSUPPORTED_EXTENSIONS def _mark_command_required(self, command): """ Helper function to mark a certain command and the datatypes it needs as required.""" def mark_bitmask_dependencies(bitmask, types): if bitmask.requires is not None: types[bitmask.requires]["data"].required = True def mark_funcpointer_dependencies(fp, types): for m in fp.members: type_info = types[m.type] # Complex types have a matching definition e.g. VkStruct. # Not needed for base types such as uint32_t. if "data" in type_info: types[m.type]["data"].required = True def mark_struct_dependencies(struct, types): for m in struct: type_info = types[m.type] # Complex types have a matching definition e.g. VkStruct. # Not needed for base types such as uint32_t. if "data" in type_info: types[m.type]["data"].required = True if type_info["category"] == "struct" and struct.name != m.type: # Yay, recurse mark_struct_dependencies(type_info["data"], types) elif type_info["category"] == "funcpointer": mark_funcpointer_dependencies(type_info["data"], types) elif type_info["category"] == "bitmask": mark_bitmask_dependencies(type_info["data"], types) func = self.funcs[command] func.required = True # Pull in return type if func.type != "void": self.types[func.type]["data"].required = True # Analyze parameter dependencies and pull in any type needed. for p in func.params: type_info = self.types[p.type] # Check if we are dealing with a complex type e.g. VkEnum, VkStruct and others. if "data" not in type_info: continue # Mark the complex type as required. type_info["data"].required = True if type_info["category"] == "struct": struct = type_info["data"] mark_struct_dependencies(struct, self.types) elif type_info["category"] == "bitmask": mark_bitmask_dependencies(type_info["data"], self.types) def _match_object_types(self): """ Matches each handle with the correct object type. """ # Use upper case comparison for simplicity. object_types = {} for value in self.enums["VkObjectType"].values: object_name = "VK" + value.name[len("VK_OBJECT_TYPE"):].replace("_", "") object_types[object_name] = value.name for handle in self.handles: if not handle.is_required(): continue handle.object_type = object_types.get(handle.name.upper()) if not handle.object_type: LOGGER.warning("No object type found for {}".format(handle.name)) def _parse_commands(self, root): """ Parse command section containing the Vulkan function calls. """ funcs = {} commands = root.findall("./commands/") # As of Vulkan 1.1, various extensions got promoted to Core. # The old commands (e.g. KHR) are available for backwards compatibility # and are marked in vk.xml as 'alias' to the non-extension type. # The registry likes to avoid data duplication, so parameters and other # metadata need to be looked up from the Core command. # We parse the alias commands in a second pass. alias_commands = [] for command in commands: alias_name = command.attrib.get("alias") if alias_name: alias_commands.append(command) continue func = VkFunction.from_xml(command, self.types) funcs[func.name] = func for command in alias_commands: alias_name = command.attrib.get("alias") alias = funcs[alias_name] func = VkFunction.from_alias(command, alias) funcs[func.name] = func # To make life easy for the code generation, separate all function # calls out in the 4 types of Vulkan functions: # device, global, physical device and instance. device_funcs = [] global_funcs = [] phys_dev_funcs = [] instance_funcs = [] for func in funcs.values(): if func.is_device_func(): device_funcs.append(func) elif func.is_global_func(): global_funcs.append(func) elif func.is_phys_dev_func(): phys_dev_funcs.append(func) else: instance_funcs.append(func) # Sort function lists by name and store them. self.device_funcs = sorted(device_funcs, key=lambda func: func.name) self.global_funcs = sorted(global_funcs, key=lambda func: func.name) self.phys_dev_funcs = sorted(phys_dev_funcs, key=lambda func: func.name) self.instance_funcs = sorted(instance_funcs, key=lambda func: func.name) # The funcs dictionary is used as a convenient way to lookup function # calls when needed e.g. to adjust member variables. self.funcs = OrderedDict(sorted(funcs.items())) def _parse_enums(self, root): """ Parse enums section or better described as constants section. """ enums = {} self.consts = [] for enum in root.findall("./enums"): name = enum.attrib.get("name") _type = enum.attrib.get("type") if _type in ("enum", "bitmask"): enums[name] = VkEnum.from_xml(enum) else: # If no type is set, we are dealing with API constants. for value in enum.findall("enum"): # If enum is an alias, set the value to the alias name. # E.g. VK_LUID_SIZE_KHR is an alias to VK_LUID_SIZE. alias = value.attrib.get("alias") if alias: self.consts.append(VkConstant(value.attrib.get("name"), alias)) else: self.consts.append(VkConstant(value.attrib.get("name"), value.attrib.get("value"))) self.enums = OrderedDict(sorted(enums.items())) def _process_require_enum(self, enum_elem, ext=None, only_aliased=False): if "extends" in enum_elem.keys(): enum = self.types[enum_elem.attrib["extends"]]["data"] # Need to define VkEnumValues which were aliased to by another value. This is necessary # from VK spec version 1.2.135 where the provisional VK_KHR_ray_tracing extension was # added which altered VK_NV_ray_tracing's VkEnumValues to alias to the provisional # extension. aliased = False for _, t in self.types.items(): if t["category"] != "enum": continue if not t["data"]: continue for value in t["data"].values: if value.alias == enum_elem.attrib["name"]: aliased = True if only_aliased and not aliased: return if "bitpos" in enum_elem.keys(): # We need to add an extra value to an existing enum type. # E.g. VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG to VkFormatFeatureFlagBits. enum.create_bitpos(enum_elem.attrib["name"], int(enum_elem.attrib["bitpos"])) elif "offset" in enum_elem.keys(): # Extensions promoted to Core, have the extension number as part # of the enum value. Else retrieve from the extension tag. if enum_elem.attrib.get("extnumber"): ext_number = int(enum_elem.attrib.get("extnumber")) else: ext_number = int(ext.attrib["number"]) offset = int(enum_elem.attrib["offset"]) value = EXT_BASE + (ext_number - 1) * EXT_BLOCK_SIZE + offset # Deal with negative values. direction = enum_elem.attrib.get("dir") if direction is not None: value = -value enum.create_value(enum_elem.attrib["name"], str(value)) elif "value" in enum_elem.keys(): enum.create_value(enum_elem.attrib["name"], enum_elem.attrib["value"]) elif "alias" in enum_elem.keys(): enum.create_alias(enum_elem.attrib["name"], enum_elem.attrib["alias"]) elif "value" in enum_elem.keys(): # Constant with an explicit value if only_aliased: return self.consts.append(VkConstant(enum_elem.attrib["name"], enum_elem.attrib["value"])) elif "alias" in enum_elem.keys(): # Aliased constant if not only_aliased: return self.consts.append(VkConstant(enum_elem.attrib["name"], enum_elem.attrib["alias"])) @staticmethod def _require_type(type_info): if type_info.is_alias(): type_info = type_info.alias type_info.required = True if type(type_info) == VkStruct: for member in type_info.members: if "data" in member.type_info: VkRegistry._require_type(member.type_info["data"]) def _parse_extensions(self, root): """ Parse extensions section and pull in any types and commands for this extension. """ extensions = [] exts = root.findall("./extensions/extension") deferred_exts = [] skipped_exts = UNSUPPORTED_EXTENSIONS.copy() def process_ext(ext, deferred=False): ext_name = ext.attrib["name"] # Set extension name on any functions calls part of this extension as we # were not aware of the name during initial parsing. commands = ext.findall("require/command") for command in commands: cmd_name = command.attrib["name"] # Need to verify that the command is defined, and otherwise skip it. # vkCreateScreenSurfaceQNX is declared in but not defined in # . A command without a definition cannot be enabled, so it's valid for # the XML file to handle this, but because of the manner in which we parse the XML # file we pre-populate from before we check if a command is enabled. if cmd_name in self.funcs: self.funcs[cmd_name].extensions.add(ext_name) # Some extensions are not ready or have numbers reserved as a place holder. if ext.attrib["supported"] == "disabled": LOGGER.debug("Skipping disabled extension: {0}".format(ext_name)) skipped_exts.append(ext_name) return # Defer extensions with 'sortorder' as they are order-dependent for spec-parsing. if not deferred and "sortorder" in ext.attrib: deferred_exts.append(ext) return # Disable highly experimental extensions as the APIs are unstable and can # change between minor Vulkan revisions until API is final and becomes KHR # or NV. if ("KHX" in ext_name or "NVX" in ext_name) and ext_name not in ALLOWED_X_EXTENSIONS: LOGGER.debug("Skipping experimental extension: {0}".format(ext_name)) skipped_exts.append(ext_name) return # Extensions can define VkEnumValues which alias to provisional extensions. Pre-process # extensions to define any required VkEnumValues before the platform check below. for require in ext.findall("require"): # Extensions can add enum values to Core / extension enums, so add these. for enum_elem in require.findall("enum"): self._process_require_enum(enum_elem, ext, only_aliased=True) platform = ext.attrib.get("platform") if platform and platform != "win32": LOGGER.debug("Skipping extensions {0} for platform {1}".format(ext_name, platform)) skipped_exts.append(ext_name) return if not self._is_extension_supported(ext_name): LOGGER.debug("Skipping unsupported extension: {0}".format(ext_name)) skipped_exts.append(ext_name) return elif "requires" in ext.attrib: # Check if this extension builds on top of another unsupported extension. requires = ext.attrib["requires"].split(",") if len(set(requires).intersection(skipped_exts)) > 0: skipped_exts.append(ext_name) return LOGGER.debug("Loading extension: {0}".format(ext_name)) # Extensions can define one or more require sections each requiring # different features (e.g. Vulkan 1.1). Parse each require section # separately, so we can skip sections we don't want. for require in ext.findall("require"): # Extensions can add enum values to Core / extension enums, so add these. for enum_elem in require.findall("enum"): self._process_require_enum(enum_elem, ext) for t in require.findall("type"): type_info = self.types[t.attrib["name"]]["data"] self._require_type(type_info) feature = require.attrib.get("feature") if feature and not self._is_feature_supported(feature): continue required_extension = require.attrib.get("extension") if required_extension and not self._is_extension_supported(required_extension): continue # Pull in any commands we need. We infer types to pull in from the command # as well. for command in require.findall("command"): cmd_name = command.attrib["name"] self._mark_command_required(cmd_name) # Store a list with extensions. ext_info = {"name" : ext_name, "type" : ext.attrib["type"]} extensions.append(ext_info) # Process extensions, allowing for sortorder to defer extension processing for ext in exts: process_ext(ext) deferred_exts.sort(key=lambda ext: ext.attrib["sortorder"]) # Respect sortorder for ext in deferred_exts: process_ext(ext, deferred=True) # Sort in alphabetical order. self.extensions = sorted(extensions, key=lambda ext: ext["name"]) def _parse_features(self, root): """ Parse the feature section, which describes Core commands and types needed. """ for feature in root.findall("./feature"): feature_name = feature.attrib["name"] for require in feature.findall("require"): LOGGER.info("Including features for {0}".format(require.attrib.get("comment"))) for tag in require: if tag.tag == "comment": continue elif tag.tag == "command": if not self._is_feature_supported(feature_name): continue name = tag.attrib["name"] self._mark_command_required(name) elif tag.tag == "enum": self._process_require_enum(tag) elif tag.tag == "type": name = tag.attrib["name"] # Skip pull in for vk_platform.h for now. if name == "vk_platform": continue type_info = self.types[name] type_info["data"].required = True def _parse_types(self, root): """ Parse types section, which contains all data types e.g. structs, typedefs etcetera. """ types = root.findall("./types/type") base_types = [] bitmasks = [] defines = [] funcpointers = [] handles = [] structs = [] alias_types = [] for t in types: type_info = {} type_info["category"] = t.attrib.get("category", None) type_info["requires"] = t.attrib.get("requires", None) # We parse aliases in a second pass when we know more. alias = t.attrib.get("alias") if alias: LOGGER.debug("Alias found: {0}".format(alias)) alias_types.append(t) continue if type_info["category"] in ["include"]: continue if type_info["category"] == "basetype": name = t.find("name").text _type = None if not t.find("type") is None: _type = t.find("type").text tail = t.find("type").tail if tail is not None: _type += tail.strip() basetype = VkBaseType(name, _type) base_types.append(basetype) type_info["data"] = basetype # Basic C types don't need us to define them, but we do need data for them if type_info["requires"] == "vk_platform": requires = type_info["requires"] basic_c = VkBaseType(name, _type, requires=requires) type_info["data"] = basic_c if type_info["category"] == "bitmask": name = t.find("name").text _type = t.find("type").text # Most bitmasks have a requires attribute used to pull in # required '*FlagBits" enum. requires = type_info["requires"] bitmask = VkBaseType(name, _type, requires=requires) bitmasks.append(bitmask) type_info["data"] = bitmask if type_info["category"] == "define": define = VkDefine.from_xml(t) defines.append(define) type_info["data"] = define if type_info["category"] == "enum": name = t.attrib.get("name") # The type section only contains enum names, not the actual definition. # Since we already parsed the enum before, just link it in. try: type_info["data"] = self.enums[name] except KeyError as e: # Not all enums seem to be defined yet, typically that's for # ones ending in 'FlagBits' where future extensions may add # definitions. type_info["data"] = None if type_info["category"] == "funcpointer": funcpointer = VkFunctionPointer.from_xml(t) funcpointers.append(funcpointer) type_info["data"] = funcpointer if type_info["category"] == "handle": handle = VkHandle.from_xml(t) handles.append(handle) type_info["data"] = handle if type_info["category"] in ["struct", "union"]: # We store unions among structs as some structs depend # on unions. The types are very similar in parsing and # generation anyway. The official Vulkan scripts use # a similar kind of hack. struct = VkStruct.from_xml(t) structs.append(struct) type_info["data"] = struct # Name is in general within a name tag else it is an optional # attribute on the type tag. name_elem = t.find("name") if name_elem is not None: type_info["name"] = name_elem.text else: type_info["name"] = t.attrib.get("name", None) # Store all type data in a shared dictionary, so we can easily # look up information for a given type. There are no duplicate # names. self.types[type_info["name"]] = type_info # Second pass for alias types, so we can retrieve all data from # the aliased object. for t in alias_types: type_info = {} type_info["category"] = t.attrib.get("category") type_info["name"] = t.attrib.get("name") alias = t.attrib.get("alias") if type_info["category"] == "bitmask": bitmask = VkBaseType(type_info["name"], alias, alias=self.types[alias]["data"]) bitmasks.append(bitmask) type_info["data"] = bitmask if type_info["category"] == "enum": enum = VkEnum.from_alias(t, self.types[alias]["data"]) type_info["data"] = enum self.enums[enum.name] = enum if type_info["category"] == "handle": handle = VkHandle.from_alias(t, self.types[alias]["data"]) handles.append(handle) type_info["data"] = handle if type_info["category"] == "struct": struct = VkStruct.from_alias(t, self.types[alias]["data"]) structs.append(struct) type_info["data"] = struct self.types[type_info["name"]] = type_info # We need detailed type information during code generation # on structs for alignment reasons. Unfortunately structs # are parsed among other types, so there is no guarantee # that any types needed have been parsed already, so set # the data now. for struct in structs: struct.set_type_info(self.types) # Alias structures have enum values equivalent to those of the # structure which they are aliased against. we need to ignore alias # structs when populating the struct extensions list, otherwise we # will create duplicate case entries. if struct.alias: continue for structextend in struct.structextends: s = self.types[structextend]["data"] s.struct_extensions.append(struct) # Guarantee everything is sorted, so code generation doesn't have # to deal with this. self.base_types = sorted(base_types, key=lambda base_type: base_type.name) self.bitmasks = sorted(bitmasks, key=lambda bitmask: bitmask.name) self.defines = defines self.enums = OrderedDict(sorted(self.enums.items())) self.funcpointers = sorted(funcpointers, key=lambda fp: fp.name) self.handles = sorted(handles, key=lambda handle: handle.name) self.structs = sorted(structs, key=lambda struct: struct.name) def generate_vulkan_json(f): f.write("{\n") f.write(" \"file_format_version\": \"1.0.0\",\n") f.write(" \"ICD\": {\n") f.write(" \"library_path\": \".\\\\winevulkan.dll\",\n") f.write(" \"api_version\": \"{0}\"\n".format(VK_XML_VERSION)) f.write(" }\n") f.write("}\n") def set_working_directory(): path = os.path.abspath(__file__) path = os.path.dirname(path) os.chdir(path) def download_vk_xml(filename): url = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/v{0}/xml/vk.xml".format(VK_XML_VERSION) if not os.path.isfile(filename): urllib.request.urlretrieve(url, filename) def main(): parser = argparse.ArgumentParser() parser.add_argument("-v", "--verbose", action="count", default=0, help="increase output verbosity") parser.add_argument("-x", "--xml", default=None, type=str, help="path to specification XML file") args = parser.parse_args() if args.verbose == 0: LOGGER.setLevel(logging.WARNING) elif args.verbose == 1: LOGGER.setLevel(logging.INFO) else: # > 1 LOGGER.setLevel(logging.DEBUG) set_working_directory() if args.xml: vk_xml = args.xml else: vk_xml = "vk-{0}.xml".format(VK_XML_VERSION) download_vk_xml(vk_xml) registry = VkRegistry(vk_xml) generator = VkGenerator(registry) with open(WINE_VULKAN_H, "w") as f: generator.generate_vulkan_h(f) with open(WINE_VULKAN_DRIVER_H, "w") as f: generator.generate_vulkan_driver_h(f) with open(WINE_VULKAN_THUNKS_H, "w") as f: generator.generate_thunks_h(f, "wine_") with open(WINE_VULKAN_THUNKS_C, "w") as f: generator.generate_thunks_c(f) with open(WINE_VULKAN_LOADER_THUNKS_H, "w") as f: generator.generate_loader_thunks_h(f) with open(WINE_VULKAN_LOADER_THUNKS_C, "w") as f: generator.generate_loader_thunks_c(f) with open(WINE_VULKAN_JSON, "w") as f: generate_vulkan_json(f) with open(WINE_VULKAN_SPEC, "w") as f: generator.generate_vulkan_spec(f) with open(WINE_VULKAN_LOADER_SPEC, "w") as f: generator.generate_vulkan_loader_spec(f) if __name__ == "__main__": main()