#!/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 urllib.request
import xml.etree.ElementTree as ET
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.4.344"

# 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_KHR_display", # Needs WSI work.
    "VK_KHR_surface_protected_capabilities",
    "VK_LUNARG_direct_driver_loading", # Implemented in the Vulkan loader

    # Device extensions
    "VK_AMD_display_native_hdr",
    "VK_EXT_full_screen_exclusive",
    "VK_GOOGLE_display_timing",
    # Relates to external_semaphore and needs type conversions in bitflags.
    "VK_KHR_shared_presentable_image", # Needs WSI work.
    "VK_KHR_video_encode_h265", # StdVideoH265HrdParameters cannot be handled
    "VK_KHR_video_decode_h265", # by struct conversions.
    "VK_KHR_video_maintenance2", # Also affected by StdVideoH265HrdParameters.
    "VK_NV_external_memory_rdma", # Needs shared resources work.
    "VK_NV_external_compute_queue", # Has a new dispatchable handle

    # Extensions for other platforms
    "VK_EXT_metal_objects",
    "VK_EXT_physical_device_drm",
    "VK_GOOGLE_surfaceless_query",
    "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",
}
UNSUPPORTED_EXTGROUPS = (
    "VK_KHX",
    "VK_NVX",
)

# 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_EXT_map_memory_placed",
    "VK_EXT_headless_surface",
    # Extensions for other platforms
    "VK_EXT_external_memory_dma_buf",
    "VK_EXT_image_drm_format_modifier",
    "VK_EXT_metal_surface",
    "VK_KHR_external_fence_fd",
    "VK_KHR_external_memory_fd",
    "VK_EXT_external_memory_metal",
    "VK_KHR_external_semaphore_fd",
    "VK_KHR_wayland_surface",
    "VK_KHR_xlib_surface",
    "VK_MVK_macos_surface",
}

UNEXPOSED_PLATFORMS = {
    "macos",
    "metal",
    "wayland",
    "xlib",
}

# 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",
]

# Some frequently called functions skip traces and checks for performance reasons.
PERF_CRITICAL_FUNCTIONS = [
    "vkUpdateDescriptorSets",
    "vkUpdateDescriptorSetWithTemplate",
    "vkGetDescriptorEXT",
    "vkWriteResourceDescriptorsEXT",
    "vkWriteSamplerDescriptorsEXT",
]

# 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 (default: True):  set whether we need a function pointer in the device / instance dispatch table.
FUNCTION_OVERRIDES = {
    # Device functions
    "vkGetDeviceProcAddr" : {"dispatch" : False},
}

# functions for which a user driver entry must be generated
USER_DRIVER_FUNCS = {
    "vkAcquireNextImage2KHR",
    "vkAcquireNextImageKHR",
    "vkAllocateMemory",
    "vkCreateBuffer",
    "vkCreateDevice",
    "vkCreateFence",
    "vkCreateImage",
    "vkCreateInstance",
    "vkCreateSemaphore",
    "vkCreateSwapchainKHR",
    "vkCreateWin32SurfaceKHR",
    "vkDestroyDevice",
    "vkDestroyFence",
    "vkDestroyInstance",
    "vkDestroySemaphore",
    "vkDestroySurfaceKHR",
    "vkDestroySwapchainKHR",
    "vkFreeMemory",
    "vkGetDeviceBufferMemoryRequirements",
    "vkGetDeviceBufferMemoryRequirementsKHR",
    "vkGetDeviceImageMemoryRequirements",
    "vkGetDeviceProcAddr",
    "vkGetDeviceQueue",
    "vkGetDeviceQueue2",
    "vkGetFenceWin32HandleKHR",
    "vkGetInstanceProcAddr",
    "vkGetMemoryWin32HandleKHR",
    "vkGetMemoryWin32HandlePropertiesKHR",
    "vkGetPhysicalDeviceExternalBufferProperties",
    "vkGetPhysicalDeviceExternalBufferPropertiesKHR",
    "vkGetPhysicalDeviceExternalFenceProperties",
    "vkGetPhysicalDeviceExternalFencePropertiesKHR",
    "vkGetPhysicalDeviceExternalSemaphoreProperties",
    "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR",
    "vkGetPhysicalDeviceImageFormatProperties2",
    "vkGetPhysicalDeviceImageFormatProperties2KHR",
    "vkGetPhysicalDevicePresentRectanglesKHR",
    "vkGetPhysicalDeviceProperties",
    "vkGetPhysicalDeviceProperties2",
    "vkGetPhysicalDeviceProperties2KHR",
    "vkGetPhysicalDeviceSurfaceCapabilities2KHR",
    "vkGetPhysicalDeviceSurfaceCapabilitiesKHR",
    "vkGetPhysicalDeviceSurfaceFormats2KHR",
    "vkGetPhysicalDeviceSurfaceFormatsKHR",
    "vkGetPhysicalDeviceWin32PresentationSupportKHR",
    "vkGetSemaphoreWin32HandleKHR",
    "vkImportFenceWin32HandleKHR",
    "vkImportSemaphoreWin32HandleKHR",
    "vkMapMemory",
    "vkMapMemory2KHR",
    "vkQueuePresentKHR",
    "vkQueueSubmit",
    "vkQueueSubmit2",
    "vkQueueSubmit2KHR",
    "vkUnmapMemory",
    "vkUnmapMemory2KHR",
}

# functions for which the unix thunk is manually implemented
MANUAL_UNIX_THUNKS = {
    "vkAllocateCommandBuffers",
    "vkCreateCommandPool",
    "vkCreateDebugReportCallbackEXT",
    "vkCreateDebugUtilsMessengerEXT",
    "vkCreateDeferredOperationKHR",
    "vkCreateInstance",
    "vkDestroyCommandPool",
    "vkDestroyDebugReportCallbackEXT",
    "vkDestroyDebugUtilsMessengerEXT",
    "vkDestroyDeferredOperationKHR",
    "vkEnumerateDeviceLayerProperties",
    "vkEnumerateInstanceExtensionProperties",
    "vkEnumerateInstanceLayerProperties",
    "vkEnumerateInstanceVersion",
    "vkEnumeratePhysicalDeviceGroups",
    "vkEnumeratePhysicalDeviceGroupsKHR",
    "vkEnumeratePhysicalDevices",
    "vkFreeCommandBuffers",
    "vkGetCalibratedTimestampsEXT",
    "vkGetCalibratedTimestampsKHR",
    "vkGetDeviceProcAddr",
    "vkGetInstanceProcAddr",
    "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT",
    "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR",
    "vkGetPhysicalDeviceExternalFenceProperties",
    "vkGetPhysicalDeviceExternalFencePropertiesKHR",
    "vkGetSwapchainTimeDomainPropertiesEXT",
}

# loader functions which are entirely manually implemented
MANUAL_LOADER_FUNCTIONS = {
    "vkEnumerateInstanceLayerProperties",
    "vkGetDeviceProcAddr",
    "vkGetInstanceProcAddr",
}

# functions which loader thunks are manually implemented
MANUAL_LOADER_THUNKS = {
    "vkAllocateCommandBuffers",
    "vkCreateCommandPool",
    "vkCreateDevice",
    "vkCreateInstance",
    "vkDestroyCommandPool",
    "vkDestroyDevice",
    "vkDestroyInstance",
    "vkEnumerateDeviceExtensionProperties",
    "vkEnumerateInstanceExtensionProperties",
    "vkEnumerateInstanceVersion",
    "vkFreeCommandBuffers",
}

# Force making copies of structs even without chains
FORCE_STRUCT_CONVERSIONS = {
    "VkPhysicalDeviceExternalBufferInfo",
    "VkPhysicalDeviceExternalBufferInfoKHR",
    "VkPhysicalDeviceExternalFenceInfo",
    "VkPhysicalDeviceExternalFenceInfoKHR",
    "VkPhysicalDeviceExternalSemaphoreInfo",
    "VkPhysicalDeviceExternalSemaphoreInfoKHR",
    "VkCommandBufferSubmitInfo",
    "VkCommandBufferSubmitInfoKHR",
    "VkSemaphoreSubmitInfo",
    "VkSemaphoreSubmitInfoKHR",
}

STRUCT_CHAIN_CONVERSIONS = {
    # Force making copies of some struct chains
    "VkMemoryAllocateInfo": {},
    "VkBufferCreateInfo": {},
    "VkImageCreateInfo": {},
    "VkPhysicalDeviceImageFormatInfo2": {},
    "VkSemaphoreCreateInfo": {},
    "VkFenceCreateInfo": {},
    "VkSubmitInfo": {},
    "VkSubmitInfo2": {},

    # Ignore to not confuse host loader.
    "VkDeviceCreateInfo": {"strip": ["VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO"]},
    "VkInstanceCreateInfo": {"strip": ["VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO"]},
    "VkPhysicalDeviceLayeredApiPropertiesKHR": {"strip": ["VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LAYERED_API_VULKAN_PROPERTIES_KHR"]},
}

# 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"):
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM ? {length} : 0",
    ("VkWriteDescriptorSet", "pBufferInfo"):
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC || " +
            "{prefix}descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC ? {length} : 0",
}


class Direction(Enum):
    """ Parameter direction: input, output, input_output. """
    INPUT = 1
    OUTPUT = 2


class Unwrap(Enum):
    NONE = 0
    HOST = 1
    DRIVER = 2


def is_api_supported(obj):
    return "vulkan" in obj.get("api", "vulkan").split(",")


def innertext(tag):
    return (tag.text or '') + ''.join(innertext(e) for e in tag) + (tag.tail or '')

def snake_case(value):
    return re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2', value)

def convert_suffix(direction, win_type, unwrap, is_wrapped):
    if direction == Direction.OUTPUT:
        if not is_wrapped:
            return "host_to_{0}".format(win_type)
        if unwrap == Unwrap.NONE:
            return "unwrapped_host_to_{0}".format(win_type)
        return "host_to_{0}".format(win_type)
    else:
        if not is_wrapped:
            return "{0}_to_host".format(win_type)
        if unwrap == Unwrap.NONE:
            return "{0}_to_unwrapped_host".format(win_type)
        return "{0}_to_host".format(win_type)


# return any element within the list that is not yet in the `seen` set
def unique(list, key=lambda x: x, seen=set()):
    return (e for e in list if not (key(e) in seen or seen.add(key(e))))


# Arrays come in multiple formats. Known formats are:
#
# <member><type>uint32_t</type> <name>foo</name>[<enum>VK_FOO_SIZE</enum>][<enum>VK_FOO_COUNT</enum>]</member>
# <member><type>uint32_t</type> <name>foo</name>[3][4]</member>
def parse_array_lens(element):
    text = ''

    for e in element:
        if e.tag == 'name':
            if e.tail:
                text += e.tail
        elif e.tag == 'enum':
            text += e.text + e.tail
        elif e.tag != 'comment' and e.tag != 'type':
            LOGGER.warning("Ignoring tag <{0}> while trying to parse array sizes".format(e.tag))
    return [i.strip('[]') for i in re.findall(r'\[\w+\]', text)]


class Type(object):
    types = {}
    alias = {}

    def __init__(self, name, requires=[], alias=None):
        self.order = 0
        self._required = False
        self.extensions = set()
        self.requires = requires
        self.name = name

        if not alias:
            Type.types[name] = self
            Type.alias[name] = Type.alias.get(name, [])
        else:
            Type.alias[name] = alias
            Type.alias[alias] = Type.alias.get(alias, []) + [name]

    def require(self):
        if self._required:
            return
        self._required = True

        is_other = lambda t: t and t != self
        for type in filter(is_other, map(Type.get, self.requires)):
            type.require()

    def is_required(self):
        return self._required

    def set_order(self, order=0):
        if order < self.order:
            return
        self.order = order + 1

        is_other = lambda t: t and t != self
        for type in filter(is_other, map(Type.get, self.requires)):
            type.set_order(self.order)

    def definition(self, suffix=""):
        aliases = ", ".join(f"{alias}{suffix}" for alias in Type.alias.get(self.name, []))
        return f"typedef {self.name}{suffix} {aliases};\n" if len(aliases) else ""

    @staticmethod
    def get(name):
        if name in Type.types:
            return Type.types[name]
        if name in Type.alias:
            return Type.get(Type.alias[name])
        return None

    @staticmethod
    def all(klass, which=lambda t: True, by_name=False, required=True):
        types = filter(lambda t: t.is_required() if required else True, Type.types.values())
        klass = [klass] if type(klass) not in (list, tuple) else klass
        types = filter(lambda t: type(t) in klass and which(t), types)
        return sorted(types, key=lambda type: (0 if by_name else -type.order, type.name))


class Flags(Type):
    def __init__(self, name, type, requires=None):
        Type.__init__(self, name, requires=[requires] if requires else [])
        self.type = type

    def definition(self):
        type = f"typedef {self.type}" if self.type else "struct"
        return f"{type} {self.name};\n" + Type.definition(self)


class Extension(object):
    def __init__(self, name, type=None, platform=None, **kwargs):
        self.name = name
        self.type = type

        self.is_supported = name not in UNSUPPORTED_EXTENSIONS
        self.is_exposed = platform not in UNEXPOSED_PLATFORMS and \
                          name not in UNEXPOSED_EXTENSIONS
        self.is_core = name in CORE_EXTENSIONS


class Define(Type):
    def __init__(self, name, value, requires=None):
        Type.__init__(self, name, requires=requires if requires else [])
        self.value = value

    @staticmethod
    def from_xml(define):
        value = innertext(define)
        value = re.sub(r"\s*//.*$", "", value, flags=re.M)
        value = value.strip()

        if "#define VK_USE_64_BIT_PTR_DEFINES" in value:
            value = "#define VK_USE_64_BIT_PTR_DEFINES 0"

        name = define.findtext("name") or value.replace("\n", "")
        name = re.sub(r"^.*?#define\s+(\w+).*$", r"\1", name)
        name = re.sub(r"^.*?typedef\s+[^;]+?(\w+);.*$", r"\1", name)
        name = re.sub(r"^.*?struct\s+(\w+);.*$", r"\1", name)

        requires = [elem.text for elem in define.iterfind("type")]
        return Define(name, value, requires=requires)

    def definition(self):
        return self.value + '\n'


class Constant(Define):
    def __init__(self, name, value):
        Define.__init__(self, name, f"#define {name} {value}")


class Enum(Type):
    def __init__(self, values, name, bitwidth="32", **kwargs):
        Type.__init__(self, name)
        self.values = values
        self.bitwidth = int(bitwidth)
        assert self.bitwidth in (32, 64)

    @staticmethod
    def from_xml(node):
        values = filter(is_api_supported, node.findall("enum"))
        values = [EnumValue(**node.attrib) for node in values]

        enum = Enum(values, **node.attrib)
        if enum.bitwidth == 32 and not any(x.value == 0x7fffffff for x in enum.values):
            # 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.
            name = snake_case(enum.name).upper() + "_MAX_ENUM"
            enum.add(EnumValue(name=name, value="0x7fffffff"))

        return enum

    def add(self, value):
        # 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.
        if any(not value.is_alias() and value.value == prev.value for prev in self.values):
            return
        # Avoid adding duplicate aliases multiple times
        if any(x.name == value.name for x in self.values):
            return
        self.values.append(value)

    def fixup_64bit_aliases(self):
        # 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 filter(EnumValue.is_alias, self.values):
            alias = next(x for x in self.values if x.name == value.alias)
            value.hex = alias.hex
            value.value = alias.value
            value.alias = None

    def definition(self):
        # Print values sorted, values can have been added in a random order.
        values = sorted(self.values, key=lambda value: value.value)

        if self.bitwidth == 32:
            text  = f"typedef enum {self.name}\n{{\n"
            for value in values:
                text += f"    {value.definition()},\n"
            text += f"}} {self.name};\n"

        elif self.bitwidth == 64:
            text  = f"typedef VkFlags64 {self.name};\n\n"
            for value in values:
                text += f"static const {self.name} {value.definition(size=8)};\n"

        return text + Type.definition(self) + "\n"


class EnumValue(object):
    def __init__(self, name, value=None, bitpos=None, alias=None, hex=False, **kwargs):
        self.name = name
        self.alias = alias

        if alias:
            self.hex = False
            self.value = 0xffffffffffffffff
        elif bitpos:
            self.hex = True
            self.value = (1 << int(bitpos))
        else:
            self.hex = "0x" in value
            self.value = int(value, 0)

    @staticmethod
    def from_xml(element, extension):
        if offset := element.get("offset"):
            # Extensions promoted to Core, have the extension number as part
            # of the enum value. Else retrieve from the extension tag.
            number = element.get("extnumber") or extension.get("number")
            value = EXT_BASE + (int(number) - 1) * EXT_BLOCK_SIZE + int(offset)
            value *= int((element.get("dir") or "+") + "1")
            return EnumValue(**element.attrib, value=str(value))

        return EnumValue(**element.attrib)

    def is_alias(self):
        return self.alias is not None

    def definition(self, size=4):
        if self.alias:
            return f"{self.name} = {self.alias}"

        # Hex is commonly used for FlagBits and sometimes within
        # a non-FlagBits enum for a bitmask value as well.
        suffix = "ull" if size == 8 else ""
        value  = f"0x{self.value:08x}" if self.hex else self.value
        return f"{self.name} = {value}{suffix}"


class Function(Type):
    def __init__(self, _type, name, params):
        Type.__init__(self, name, requires=[_type] + [p.type_name for p in params])
        self.platforms = set()
        self.type = _type
        self.params = params

        # For some functions we need some extra metadata from FUNCTION_OVERRIDES.
        func_info = FUNCTION_OVERRIDES.get(self.name, {})
        self.dispatch = func_info.get("dispatch", True)

        # Required is set while parsing which APIs and types are required
        # and is used by the code generation.
        if func_info:
            self.require()

        if self.name in MANUAL_UNIX_THUNKS | USER_DRIVER_FUNCS:
            self.unwrap = Unwrap.NONE
        else:
            self.unwrap = Unwrap.HOST

    @staticmethod
    def from_alias(command, alias):
        func_name = command.attrib.get("name")
        return Function(alias.type, func_name, alias.params)

    @staticmethod
    def from_xml(command, pointer=False):
        proto = command.find("proto")
        func_name = proto.find("name").text
        func_type = innertext(proto).replace(func_name, "").replace('\n', '').strip()

        params = filter(is_api_supported, command.findall("param"))
        params = [Parameter.from_xml(param) for param in params]
        klass = FunctionPointer if pointer else Function
        return klass(func_type, func_name, params)

    def is_core(self):
        return not self.extensions or any(ext.is_core for ext in self.extensions)

    def is_device(self):
        # If none of the other, it must be a device function
        return not self.is_global() and not self.is_instance()

    def is_driver_func(self):
        """ Returns if function is part of Wine driver interface. """
        return self.name in USER_DRIVER_FUNCS

    def is_global(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(self):
        if self.is_global():
            return False
        # Instance functions are passed VkInstance.
        if self.params[0].type_name == "VkInstance":
            return True
        return self.is_physical_device()

    def is_physical_device(self):
        if self.is_global():
            return False
        # Physical device functions are passed VkPhysicalDevice.
        if self.params[0].type_name == "VkPhysicalDevice":
            return True
        return False

    def returns_longlong(self):
        return self.type in ["uint64_t", "VkDeviceAddress"]

    def needs_thunk(self):
        return self.needs_exposing() and self.name not in MANUAL_LOADER_FUNCTIONS

    def needs_private_thunk(self):
        return self.needs_thunk() and self.name in MANUAL_UNIX_THUNKS

    def needs_loader_thunk(self):
        return self.needs_thunk() and self.name not in MANUAL_LOADER_THUNKS

    def needs_exposing(self):
        # The function needs exposed if at-least one extension isn't both UNSUPPORTED and UNEXPOSED
        return not self.extensions or any(ext.is_exposed for ext in self.extensions)

    def is_host_instance(self):
        return self.dispatch and self.is_instance()

    def is_host_device(self):
        return self.dispatch and self.is_device()

    def is_client_instance(self):
        return self.needs_exposing() and self.is_instance()

    def is_client_physical_device(self):
        return self.needs_exposing() and self.is_physical_device()

    def is_client_device(self):
        return self.needs_exposing() and self.is_device()

    def is_perf_critical(self):
        # vkCmd* functions are frequently called, do not trace for performance
        if self.name.startswith("vkCmd") and self.type == "void":
            return True
        return self.name in PERF_CRITICAL_FUNCTIONS

    def gen_params(self):
        return ", ".join(p.as_param() for p in self.params) if len(self.params) else "void"

    def gen_pointer(self):
        name = self.name[4:] if self.name.startswith('PFN_') else self.name
        return f"typedef {self.type} (VKAPI_PTR *PFN_{name})({self.gen_params()});\n"

    def gen_prototype(self):
        return f"{self.type} VKAPI_CALL {self.name}({self.gen_params()});\n"

    def gen_wrapper(self):
        return f"{self.type} wine_{self.name}({self.gen_params()});\n"

    def body(self, conv, conversions, params_prefix=""):
        body = ""
        needs_alloc = False
        deferred_op = None

        # Declare any tmp parameters for conversion.
        for p in self.params:
            if p.needs_variable(conv, self.unwrap):
                if p.is_dynamic_array():
                    const = "const " if p.is_const() else ""
                    ptr = p.pointer[:-1] if p.pointer else ""
                    body += f"    {const}{p.type_name}{ptr} *{p.name}_host;\n"
                elif p.optional:
                    body += f"    {p.type_name} *{p.name}_host = NULL;\n"
                    needs_alloc = True
                elif p.pointer:
                    body += f"    {p.type_name} {p.pointer[:-1]}{p.name}_host;\n"
                else:
                    body += f"    {p.type_name} {p.name}_host;\n"
            if p.needs_alloc(conv, self.unwrap):
                needs_alloc = True
            if p.type_name == "VkDeferredOperationKHR" and not p.is_pointer():
                deferred_op = p.name

        if needs_alloc:
            body += "    struct conversion_context local_ctx;\n"
            body += "    struct conversion_context *ctx = &local_ctx;\n"
        body += "\n"

        if not self.is_perf_critical():
            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:
            if deferred_op is not None:
                body += "    if (params->{} == VK_NULL_HANDLE)\n".format(deferred_op)
                body += "    "
            body += "    init_conversion_context(ctx);\n"
            if deferred_op is not None:
                body += "    else\n"
                body += "        ctx = &wine_deferred_operation_from_handle(params->{})->ctx;\n".format(deferred_op)

        # Call any win_to_host conversion calls.
        unwrap = self.name not in MANUAL_UNIX_THUNKS
        for p in self.params:
            if p.needs_conversion(conv, self.unwrap, Direction.INPUT):
                body += p.copy(Direction.INPUT, conv, self.unwrap, conversions, self.params, prefix=params_prefix)
            elif p.is_dynamic_array() and p.needs_conversion(conv, self.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(self.params, 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.
        unwrap = Unwrap.NONE if self.name in MANUAL_UNIX_THUNKS else self.unwrap
        params = ", ".join([p.variable(conv, unwrap, params_prefix) for p in self.params])

        if self.name in MANUAL_UNIX_THUNKS:
            func_prefix = "wine_"
        elif self.name in USER_DRIVER_FUNCS:
            func_prefix = "vk_funcs->p_"
        else:
            func_prefix = "{0}->p_".format(self.params[0].dispatch_table(params_prefix, conv))

        # Call the host 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, self.unwrap, Direction.OUTPUT):
                body += p.copy(Direction.OUTPUT, conv, self.unwrap, conversions, self.params, prefix=params_prefix)

        if needs_alloc:
            if deferred_op is not None:
                body += "    if (params->{} == VK_NULL_HANDLE)\n".format(deferred_op)
                body += "    "
            body += "    free_conversion_context(ctx);\n"

        # Finally return the result. Performance critical functions return void to allow tail calls.
        if not self.is_perf_critical():
            body += "    return STATUS_SUCCESS;\n"

        return body

    def spec(self, prefix=None, symbol=None):
        if not self.is_required():
            return f"@ stub {self.name}\n"

        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 gen_unix_thunk(self, conversions, prefix=None, conv=False):
        thunk = ""
        if not conv:
            thunk += "#ifdef _WIN64\n"
        if self.is_perf_critical():
            thunk += "static void {0}{1}(void *args)\n".format(prefix, self.name)
        else:
            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 += f"        {p.as_member(wow64=True)};\n"
            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, conversions, params_prefix="params->")
        thunk += "}\n"
        if not conv:
            thunk += "#endif /* _WIN64 */\n"
        thunk += "\n"
        return thunk

    def gen_thunk(self):
        thunk  = f"{self.type} WINAPI {self.name}({self.gen_params()})\n"
        thunk += u"{\n"
        thunk += f"    struct {self.name}_params params;\n"

        if not self.is_perf_critical():
            thunk += u"    NTSTATUS status;\n"
        for p in self.params:
            thunk += f"    params.{p.name} = {p.name};\n"

        # Call the Unix function.
        if self.is_perf_critical():
            thunk += f"    UNIX_CALL({self.name}, &params);\n"
        else:
            thunk += f"    status = UNIX_CALL({self.name}, &params);\n"
            thunk += f"    assert(!status && \"{self.name}\");\n"

        if self.type != "void":
            thunk += u"    return params.result;\n"

        thunk += u"}\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

    def unixlib_entry(self, bitness):
        cast = "(void *)" if self.is_perf_critical() else ""
        return f"{cast}thunk{bitness}_{self.name}"


class FunctionPointer(Function):
    def __init__(self, type, name, params):
        Function.__init__(self, type, name, params)

    @staticmethod
    def from_xml(command):
        return Function.from_xml(command, pointer=True)

    def definition(self):
        return self.gen_pointer() + '\n'


class Handle(Type):
    def __init__(self, name, _type, parent):
        Type.__init__(self, name)
        self.type = _type
        self.parent = parent
        self.object_type = None

    @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 Handle(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 "vulkan_instance_from_handle({0})".format(param)
        elif self.name == "VkCommandBuffer":
            return "vulkan_command_buffer_from_handle({0})->device".format(param)
        elif self.name == "VkDevice":
            return "vulkan_device_from_handle({0})".format(param)
        elif self.name == "VkPhysicalDevice":
            return "vulkan_physical_device_from_handle({0})->instance".format(param)
        elif self.name == "VkQueue":
            return "vulkan_queue_from_handle({0})->device".format(param)
        elif self.parent in ["VkInstance", "VkPhysicalDevice"]:
            return "{0}->instance".format(param)
        elif self.parent in ["VkDevice", "VkCommandPool"]:
            return "{0}->device".format(param)
        else:
            LOGGER.error("Unhandled dispatchable parent: {0}".format(self.parent))

    def definition(self):
        return f"{self.type}({self.name})\n" + Type.definition(self)

    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 host_handle(self, name):
        """ Provide access to the host handle of a wrapped object. """

        if self.name == "VkCommandBuffer":
            return "vulkan_command_buffer_from_handle({0})->host.command_buffer".format(name)
        if self.name == "VkCommandPool":
            return "wine_cmd_pool_from_handle({0})->host.command_pool".format(name)
        if self.name == "VkDebugUtilsMessengerEXT":
            return "vulkan_debug_utils_messenger_from_handle({0})->host.debug_messenger".format(name)
        if self.name == "VkDebugReportCallbackEXT":
            return "vulkan_debug_report_callback_from_handle({0})->host.debug_callback".format(name)
        if self.name == "VkDeferredOperationKHR":
            return "wine_deferred_operation_from_handle({0})->host.deferred_operation".format(name)
        if self.name == "VkDevice":
            return "vulkan_device_from_handle({0})->host.device".format(name)
        if self.name == "VkInstance":
            return "vulkan_instance_from_handle({0})->host.instance".format(name)
        if self.name == "VkDeviceMemory":
            return "vulkan_device_memory_from_handle({0})->host.device_memory".format(name)
        if self.name == "VkPhysicalDevice":
            return "vulkan_physical_device_from_handle({0})->host.physical_device".format(name)
        if self.name == "VkQueue":
            return "vulkan_queue_from_handle({0})->host.queue".format(name)
        if self.name == "VkSurfaceKHR":
            return "vulkan_surface_from_handle({0})->host.surface".format(name)
        if self.name == "VkSwapchainKHR":
            return "vulkan_swapchain_from_handle({0})->host.swapchain".format(name)
        if self.name == "VkSemaphore":
            return "vulkan_semaphore_from_handle({0})->host.semaphore".format(name)
        if self.name == "VkFence":
            return "vulkan_fence_from_handle({0})->host.fence".format(name)

        if self.is_dispatchable():
            LOGGER.error("Unhandled host handle for: {0}".format(self.name))
        return None

    def unwrap_handle(self, name, unwrap):
        if unwrap == Unwrap.HOST:
            return self.host_handle(name)
        assert unwrap != Unwrap.NONE
        return None

    def is_wrapped(self):
        return self.host_handle("test") is not None


class VkVariable(object):
    def __init__(self, const=False, type=None, name=None, pointer=None, array_lens=[],
                 dyn_array_len=None, object_type=None, optional=False, returnedonly=False,
                 selection=None, selector=None):
        self.const = const
        self.type_name = type
        self.name = name
        self.object_type = object_type
        self.optional = optional
        self.returnedonly = returnedonly
        self.selection = selection
        self.selector = selector
        self._type = None

        self.pointer = pointer
        self.array_lens = array_lens
        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

    def __eq__(self, other):
        """ Compare member based on name against a string. """
        return self.name == other

    @property
    def type(self):
        if not self._type:
            self._type = Type.get(self.type_name)
        return self._type

    @property
    def handle(self):
        return self.type if type(self.type) is Handle else None

    @property
    def struct(self):
        return self.type if type(self.type) is Record else None

    def is_const(self):
        return "const" in self.const

    def is_pointer(self):
        return self.pointer is not None

    def is_pointer_pointer(self):
        return self.pointer and self.pointer.count('*') > 1

    def is_pointer_size(self):
        if self.type_name in ["size_t", "HWND", "HINSTANCE", "HANDLE", "LPCWSTR"]:
            return True
        if self.type_name.startswith("PFN"):
            return True
        if self.is_handle() and self.handle.is_dispatchable():
            return True
        return self.is_pointer_pointer()

    def is_handle(self):
        return type(self.type) is Handle

    def is_struct(self):
        return type(self.type) is Record and not self.struct.union

    def is_union(self):
        return type(self.type) is Record and self.struct.union

    def is_flags(self):
        return type(self.type) is Flags

    def is_enum(self):
        return type(self.type) is 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 and len(self.array_lens) == 0

    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 len(self.array_lens) > 0

    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_name == "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_name == "size_t":
            return False
        elif self.type_name in ["uint64_t", "VkDeviceAddress", "VkDeviceSize"]:
            return True
        elif self.is_flags():
            return self.type.type == "VkFlags64"
        elif self.is_enum():
            return self.type.bitwidth == 64
        elif self.is_struct() or self.is_union():
            return self.type.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 is_wrapped(self):
        """ Returns if variable needs unwrapping of handle. """

        if self.is_struct():
            return self.struct.is_wrapped()

        if self.is_handle():
            return self.handle.is_wrapped()

        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_win32_type(self):
        return (self.is_struct() or (self.is_union() and self.selector)) and self.struct.needs_win32_type()

    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, deref=False):
        if not conv or not self.needs_ptr32_type() or (not self.is_pointer() and self.type_name == "size_t"):
            return prefix + self.name

        cast_type = self.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_name
            if self.needs_win32_type():
                cast_type += "32"

            if self.is_pointer():
                cast_type += " {0}".format(self.pointer)
            elif self.is_static_array():
                cast_type += " *"

        if self.is_pointer():
            ptr = self.pointer if deref else ""
        elif self.is_static_array():
            ptr = "*" if deref else ""
        else:
            ptr = ""

        return f"{ptr}({cast_type})UlongToPtr({prefix}{self.name})"


class VkMember(VkVariable):
    def __init__(self, const=False, struct_fwd_decl=False,_type=None, pointer=None, name=None, array_lens=[],
                 dyn_array_len=None, optional=False, values=None, object_type=None, bit_width=None,
                 returnedonly=False, selection=None, selector=None):
        VkVariable.__init__(self, const=const, type=_type, name=name, pointer=pointer, array_lens=array_lens,
                            dyn_array_len=dyn_array_len, object_type=object_type, optional=optional,
                            returnedonly=returnedonly, selection=selection, selector=selector)
        self.struct_fwd_decl = struct_fwd_decl
        self.values = values
        self.bit_width = bit_width
        self.parent = None

    @staticmethod
    def from_xml(member, returnedonly):
        """ Helper function for parsing a member tag within a struct or union. """
        name_elem = member.find("name")
        type_elem = member.find("type")

        const = ""
        struct_fwd_decl = False
        member_type = None
        pointer = None
        bit_width = None

        values = member.get("values")

        if member.text:
            if "const" in member.text:
                const = "const "

            # 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_tail = None
        if name_elem.tail and name_elem.tail.strip() != "":
            name_tail = name_elem.tail.strip()

        # 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

        array_lens = parse_array_lens(member)

        object_type = member.get("objecttype", None)

        # Some members are bit field values:
        # <member><type>uint32_t</type> <name>mask</name>:8</member>
        if name_tail and name_tail[0] == ':':
            LOGGER.debug("Found bit field")
            bit_width = int(name_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_lens=array_lens, dyn_array_len=dyn_array_len, optional=optional,
                        values=values, object_type=object_type, bit_width=bit_width, returnedonly=returnedonly,
                        selection=selection, selector=selector)

    def is_next(self):
        return self.name == "pNext"

    def is_type(self):
        return self.name == "sType"

    def get_dyn_array_len(self, prefix, conv):
        if isinstance(self.dyn_array_len, int):
            return self.dyn_array_len
        length = self.dyn_array_len

        var = self.parent.members[self.parent.members.index(length)]
        length = var.value(prefix, conv, deref=True)

        expr = MEMBER_LENGTH_EXPRESSIONS.get((self.parent.name, self.name), "{length}")
        return expr.format(prefix=prefix, length=length)

    def copy(self, input, output, direction, conv, unwrap, parent_const, conversions):
        is_const = self.is_const() if self.is_pointer() else parent_const

        if self.needs_conversion(conv, unwrap, direction, False):
            if self.is_dynamic_array():
                convert = ArrayConversionFunction(self, direction, conv, unwrap)
                conversions.append(convert)

                # Array length is either a variable name (string) or an int.
                count = self.get_dyn_array_len(input, conv)
                if direction == Direction.OUTPUT:
                    value = self.value(output, conv)
                    return f"{convert.name}({input}{self.name}, {value}, {count});\n"
                else:
                    value = self.value(input, conv)
                    return f"{output}{self.name} = {convert.name}(ctx, {value}, {count});\n"

            elif self.is_static_array():
                convert = ArrayConversionFunction(self, direction, conv, unwrap)
                conversions.append(convert)

                count = self.array_lens[0]
                if len(self.array_lens) > 1:
                    LOGGER.warn("TODO: implement conversion of multidimensional static array for {0}.{1}".format(self.type_name, self.name))
                elif direction == Direction.OUTPUT:
                    # Needed by VkMemoryHeap.memoryHeaps
                    return f"{convert.name}({input}{self.name}, {output}{self.name}, {count});\n"
                else:
                    # Nothing needed this yet.
                    LOGGER.warn("TODO: implement copying of static array for {0}.{1}".format(self.type_name, self.name))

            elif self.is_handle() and self.is_wrapped():
                if direction == Direction.OUTPUT:
                    LOGGER.error("OUTPUT parameter {0}.{1} cannot be unwrapped".format(self.type_name, self.name))
                elif self.optional:
                    return "{0}{1} = {2} ? {3} : 0;\n".format(output, self.name, self.value(input, conv),
                        self.handle.unwrap_handle(self.value(input, conv), unwrap))
                else:
                    return "{0}{1} = {2};\n".format(output, self.name,
                        self.handle.unwrap_handle(self.value(input, conv), unwrap))

            elif self.is_generic_handle():
                if direction == Direction.OUTPUT:
                    LOGGER.error("OUTPUT parameter {0}.{1} cannot be unwrapped".format(self.type_name, self.name))
                return "{0}{1} = wine_vk_unwrap_handle({2}{3}, {2}{1});\n".format(output, self.name, input, self.object_type)
            else:
                convert = StructConversionFunction(self.struct, direction, conv, unwrap, is_const)
                conversions.append(convert)

                sel = f", {input}{self.selector}" if self.selector else ""
                ctx = "ctx, " if self.needs_alloc(conv, unwrap) else ""

                if direction == Direction.OUTPUT:
                    return f"{convert.name}(&{input}{self.name}, &{output}{self.name}{sel});\n"
                else:
                    return f"{convert.name}({ctx}&{input}{self.name}, &{output}{self.name}{sel});\n"

        elif self.is_static_array():
            bytes_count = " * ".join(self.array_lens + [f"sizeof({self.type_name})"])
            return "memcpy({0}{1}, {2}{1}, {3});\n".format(output, self.name, input, bytes_count)
        elif conv and direction == Direction.OUTPUT and self.is_pointer():
            return "{0}{1} = PtrToUlong({2}{1});\n".format(output, self.name, input)
        elif conv and direction == Direction.INPUT and self.is_pointer():
            return "{0}{1} = UlongToPtr({2}{1});\n".format(output, self.name, input)
        elif direction == Direction.INPUT:
            return "{0}{1} = {2};\n".format(output, self.name, self.value(input, conv))
        else:
            return "{0}{1} = {2}{1};\n".format(output, self.name, input)

    def definition(self, suffix):
        if suffix and (self.is_pointer() or self.is_pointer_size()):
            text = "PTR32 " + self.name
            for l in self.array_lens:
                text += "[{0}]".format(l)
            return text

        text = ""
        if self.is_const():
            text += "const "

        if self.is_struct_forward_declaration():
            text += "struct "

        text += self.type_name
        if suffix and self.needs_win32_type():
            text += "32"

        if self.is_pointer():
            text += " {0}{1}".format(self.pointer, self.name)
        else:
            if self.needs_alignment():
                if suffix:
                    text += " DECLSPEC_ALIGN(8) " + self.name
                else:
                    text += " WINE_VK_ALIGN(8) " + self.name
            else:
                text += " " + self.name

        for l in self.array_lens:
            text += "[{0}]".format(l)

        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 self.handle.is_wrapped() and self.is_dynamic_array():
                return True
            if conv and self.handle.is_dispatchable() and self.is_static_array():
                return True
            return unwrap != Unwrap.NONE and self.handle.is_wrapped()
        elif self.is_generic_handle():
            return unwrap != Unwrap.NONE
        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 Parameter(VkVariable):
    """ Helper class which describes a parameter to a function call. """

    def __init__(self, type, const=None, pointer=None, name=None, array_lens=None,
                 dyn_array_len=None, object_type=None, optional=False):
        VkVariable.__init__(self, const=const, type=type, name=name,
                            pointer=pointer, array_lens=array_lens, dyn_array_len=dyn_array_len,
                            object_type=object_type, optional=optional)

        self._set_format_string()

    @staticmethod
    def from_xml(param):
        # 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:
        # <param>const <type>char</type>* <name>pLayerName</name></param>

        name_elem = param.find("name")
        name = name_elem.text
        # E.g. vkCmdSetBlendConstants().
        array_lens = parse_array_lens(param)

        # 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 ""
        type_elem = param.find("type")
        pointer = type_elem.tail.strip() if type_elem.tail.strip() != "" else None
        assert not pointer or pointer.count('*') <= 2 # only pointers and pointer of pointers are supported

        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)

        return Parameter(type_elem.text, const=const, pointer=pointer, name=name, array_lens=array_lens,
                         dyn_array_len=dyn_array_len, object_type=object_type, optional=optional)

    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.is_flags():
                # Since 1.2.170 bitmasks can be 32 or 64-bit, check the basetype.
                if self.type.type == "VkFlags64":
                    self.format_str = "0x%s"
                    self.format_conv = "wine_dbgstr_longlong({0})"
                else:
                    self.format_str = "%#x"
            elif self.is_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_name == "float":
                self.format_str = "%f"
            elif self.type_name == "int":
                self.format_str = "%d"
            elif self.type_name == "int32_t":
                self.format_str = "%d"
            elif self.type_name == "size_t":
                self.format_str = "0x%s"
                self.format_conv = "wine_dbgstr_longlong({0})"
            elif self.type_name in ["uint16_t", "uint32_t", "VkBool32"]:
                self.format_str = "%u"
            elif self.type_name in ["uint64_t", "VkDeviceAddress", "VkDeviceSize"]:
                self.format_str = "0x%s"
                self.format_conv = "wine_dbgstr_longlong({0})"
            elif self.type_name == "HANDLE":
                self.format_str = "%p"
            elif self.type_name in ["VisualID", "xcb_visualid_t", "RROutput", "zx_handle_t", "NvSciBufObj", "NvSciBufAttrList", "NvSciSyncAttrList"]:
                # Don't care about specific types for non-Windows platforms.
                self.format_str = ""
            else:
                LOGGER.warn("Unhandled type: {0}".format(self.type_name))

    def get_dyn_array_len(self, params, prefix, conv):
        if isinstance(self.dyn_array_len, int):
            return self.dyn_array_len
        length = self.dyn_array_len

        if "->" in length:
            # length is a member of another struct (for example pAllocateInfo->commandBufferCount)
            (param, length) = length.split("->")
            var = params[params.index(param)]
            prefix = var.value(prefix, conv)

            var = var.struct.members[var.struct.members.index(length)]
            return var.value(f"({prefix})->", conv, deref=True)

        var = params[params.index(length)]
        return var.value(prefix, conv, deref=True)

    def copy(self, direction, conv, unwrap, conversions, params, prefix=""):
        value = self.value(prefix, conv)
        is_const = self.is_const() if self.is_pointer() else False

        if direction == Direction.INPUT:
            ctx_param = "ctx, " if self.needs_alloc(conv, unwrap) else ""
            if self.is_dynamic_array():
                convert = ArrayConversionFunction(self, direction, conv, unwrap)
                conversions.append(convert)

                length = self.get_dyn_array_len(params, prefix, conv)
                return f"    {self.name}_host = {convert.name}({ctx_param}{value}, {length});\n"

            elif self.optional:
                convert = StructConversionFunction(self.struct, direction, conv, unwrap, is_const)
                conversions.append(convert)

                ret  = f"    if ({prefix}{self.name})\n"
                ret += u"    {\n"
                ret += f"        {self.name}_host = conversion_context_alloc(ctx, sizeof(*{self.name}_host));\n"
                ret += f"        {convert.name}({ctx_param}{value}, {self.name}_host);\n"
                ret += u"    }\n"
                return ret

            elif self.is_struct():
                convert = StructConversionFunction(self.struct, direction, conv, unwrap, is_const)
                conversions.append(convert)

                return f"    {convert.name}({ctx_param}{value}, &{self.name}_host);\n"

            elif self.is_pointer_size() and self.type_name != "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():
                convert = ArrayConversionFunction(self, direction, conv, unwrap)
                conversions.append(convert)

                value = value.replace('const ', '')
                length = self.get_dyn_array_len(params, prefix, conv)
                return f"    {convert.name}({self.name}_host, {value}, {length});\n"

            elif self.is_struct():
                convert = StructConversionFunction(self.struct, direction, conv, unwrap, is_const)
                conversions.append(convert)

                ref_part = "" if self.optional else "&"
                return f"    {convert.name}({ref_part}{self.name}_host, {value});\n"

            elif self.is_pointer_size() and self.type_name != "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 as_param(self):
        ptr = self.pointer if self.is_pointer() else ""
        array = "".join(f"[{len}]" for len in self.array_lens)
        return f"{self.const}{self.type_name} {ptr}{self.name}{array}"

    def as_member(self, wow64=False):
        if wow64 and self.needs_ptr32_type():
            return f"PTR32 {self.name}"

        ptr = self.pointer if self.is_pointer() else "*" if self.is_static_array() else ""
        align = " DECLSPEC_ALIGN(8)" if self.needs_alignment() else ""
        return f"{self.const}{self.type_name}{align} {ptr}{self.name}"

    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_name != "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_pointer_pointer() and self.type_name != 'void':
            if direction == Direction.INPUT or not self.is_const():
                return conv

        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 != Unwrap.NONE 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_name == "char":
            return "str"
        if self.is_dispatchable() or self.is_pointer() or self.is_static_array():
            return "ptr"
        if self.is_flags():
            # Since 1.2.170 bitmasks can be 32 or 64-bit, check the basetype.
            if self.type.type == "VkFlags64":
                return "int64"
            else:
                return "long"
        if self.is_enum():
            return "long"
        if self.is_handle() and not self.is_dispatchable():
            return "int64"
        if self.type_name == "float":
            return "float"
        if self.type_name in ["int", "int32_t", "size_t", "uint16_t", "uint32_t", "VkBool32"]:
            return "long"
        if self.type_name in ["uint64_t", "VkDeviceSize"]:
            return "int64"

        LOGGER.error("Unhandled spec conversion for type: {0}".format(self.type_name))

    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 != Unwrap.NONE and "VkAllocationCallbacks" in self.type_name:
            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.NONE:
            unwrap_handle = None
            if self.object_type != None and self.type_name == "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 host handle to the host Vulkan calls and
                # the wine driver's handle to calls which are wrapped by the driver.
                unwrap_handle = self.handle.unwrap_handle(p, unwrap)
            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 Record(Type):
    def __init__(self, members, name, returnedonly=False, structextends=None, category="struct", **kwargs):
        Type.__init__(self, name, requires=[m.type_name for m in members])
        self.union = category == "union"
        self.members = members
        self.returnedonly = returnedonly
        self.structextends = structextends.split(",") if structextends else []
        self._struct_extensions = None
        self.chain_type = None
        self.chain_next = None

        for m in self.members:
            if m.is_type():
                self.chain_type = m
            if m.is_next():
                self.chain_next = m
            m.parent = self

    @staticmethod
    def from_xml(struct):
        returnedonly = "returnedonly" in struct.attrib
        members = filter(is_api_supported, struct.findall("member"))
        members = [VkMember.from_xml(member, returnedonly) for member in members]
        return Record(members, **struct.attrib)

    @property
    def struct_extensions(self):
        if self._struct_extensions is None:
            self._struct_extensions = []

            def is_struct_extension(struct):
                if not struct.extensions or any(ext.is_exposed for ext in struct.extensions):
                    return self.name in struct.structextends
                return False

            for struct in Type.all(Record, is_struct_extension, by_name=True):
                self._struct_extensions.append(struct)

        return self._struct_extensions

    def definition(self, suffix=""):
        kind = "union" if self.union else "struct"
        text = f"typedef {kind} {self.name}{suffix}\n{{\n"
        for m in self.members:
            text += f"    {m.definition(suffix)};\n"
        text += f"}} {self.name}{suffix};\n"
        return text + Type.definition(self, suffix) + '\n'

    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_name:
                continue
            if m.needs_alignment():
                return True
        return False

    def is_wrapped(self):
        """ Returns if struct members need unwrapping of handle. """

        for m in self.members:
            if self.name == m.type_name:
                continue
            if m.is_wrapped():
                return True
        return False

    def needs_extensions_conversion(self, conv, direction):
        """ Check if struct contains extensions chain that needs to be converted """

        stripped = STRUCT_CHAIN_CONVERSIONS.get(self.name, {}).get("strip", [])
        struct_extensions = filter(lambda s: s.chain_type.values not in stripped, self.struct_extensions)

        if direction == Direction.INPUT and self.name in STRUCT_CHAIN_CONVERSIONS:
            return any(struct_extensions)

        if not self.chain_next:
            return False
        is_const = self.chain_next.is_const()
        # VkOpticalFlowSessionCreateInfoNV is missing const in its pNext pointer
        if self.name in ["VkOpticalFlowSessionCreateInfoNV",
                         "VkDescriptorBufferBindingInfoEXT"]:
            is_const = True

        for e in struct_extensions:
            if e.needs_conversion(conv, Unwrap.HOST, 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, Unwrap.HOST, 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

        # avoid some unneded conversions
        if direction == Direction.INPUT and self.name == "VkPerformanceValueDataINTEL":
            return False
        if direction == Direction.OUTPUT and re.match("^VkClusterAccelerationStructure",self.name):
            return False

        if direction == Direction.INPUT and self.name in FORCE_STRUCT_CONVERSIONS:
            return True

        needs_output_copy = False

        for m in self.members:
            if self.name == m.type_name:
                continue

            if m.is_generic_handle() and not check_extensions:
                continue

            if m.is_next():
                # 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_name == "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_name:
                continue
            if m.needs_alloc(conv, unwrap):
                return True

        return False

    def needs_win32_type(self):
        # VkAllocationCallbacks never needs conversion
        if self.name == "VkAllocationCallbacks":
            return False

        for m in self.members:
            if self.name == m.type_name:
                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_win32_type():
                return True


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
        self.const = const

        name = "convert_{0}_".format(self.type)
        win_type = "win32" if self.conv else "win64"
        name += convert_suffix(direction, win_type, unwrap, struct.is_wrapped())
        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.is_type() or m.is_next():
                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.is_type() or m.is_next():
                return True
            if struct.returnedonly and not m.needs_conversion(
                    self.conv, self.unwrap, Direction.INPUT, self.const):
                return False
        return True

    def definition(self, conversions):
        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_win32_type():
            win_type += "32"
        if self.direction == Direction.OUTPUT and self.const:
            win_type = "const " + win_type

        if self.conv:
            body += "static 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 void {0}(".format(self.name)

            params = ["const {0} *in".format(self.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"

        needs_extensions = self.operand.needs_extensions_conversion(self.conv, self.direction)

        if self.direction == Direction.OUTPUT and not any([any([self.member_needs_copy(ext, m) for m in ext.members]) for ext in self.operand.struct_extensions]):
            needs_extensions = False
        if len(self.operand.struct_extensions) == 0:
            needs_extensions = False

        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.members:
            if not self.member_needs_copy(self.operand, m):
                continue
            if m.is_next() and (needs_extensions or self.conv):
                body += f"    out->{m.name} = 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, self.const, conversions)

        if needs_extensions:
            if self.conv and self.direction == Direction.INPUT:
                body += f"\n    for (in_header = UlongToPtr(in->{self.operand.chain_next.name}); in_header; in_header = UlongToPtr(in_header->{self.operand.chain_next.name}))\n"
            else:
                body += f"\n    for (in_header = (void *)in->{self.operand.chain_next.name}; in_header; in_header = (void *)in_header->{self.operand.chain_next.name})\n"
            body += "    {\n"
            body += f"        switch (in_header->{self.operand.chain_type.name})\n"
            body += "        {\n"

            ident = "            "

            if self.direction == Direction.INPUT and self.type in STRUCT_CHAIN_CONVERSIONS \
                    and "strip" in STRUCT_CHAIN_CONVERSIONS[self.type]:
                for i in STRUCT_CHAIN_CONVERSIONS[self.type]["strip"]:
                    body += "        case {0}:\n".format(i)
                body += ident + "break;\n"

            for ext in self.operand.struct_extensions:
                if self.direction == Direction.OUTPUT and not any([self.member_needs_copy(ext, m) for m in ext.members]):
                    continue

                win_type = ext.name + "32" if self.conv and ext.needs_win32_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 += f"        case {ext.chain_type.values}:\n"
                body += u"        {\n"
                if self.direction == Direction.INPUT:
                    body += f"            {out_type} *out_ext = conversion_context_alloc(ctx, sizeof(*out_ext));\n"
                elif self.conv:
                    body += f"            {out_type} *out_ext = find_next_struct32(out_header, {ext.chain_type.values});\n"
                else:
                    body += f"            {out_type} *out_ext = find_next_struct(out_header, {ext.chain_type.values});\n"

                if any(self.member_needs_copy(ext, m) for m in ext.members if not m.is_type() and not m.is_next()):
                    body += f"            {in_type} *in_ext = ({in_type} *)in_header;\n"

                for m in ext.members:
                    if m.is_type():
                        body += f"            out_ext->{m.name} = {ext.chain_type.values};\n"
                        continue
                    if not self.member_needs_copy(ext, m):
                        continue
                    if m.is_next():
                        body += f"            out_ext->{m.name} = NULL;\n"
                        continue
                    if m.is_generic_handle():
                        body += f"            out_ext->{m.name} = in_ext->{m.name};\n"
                        continue
                    body += ident + m.copy("in_ext->", "out_ext->", self.direction, self.conv, Unwrap.HOST, self.const, conversions)

                if self.direction == Direction.INPUT:
                    body += ident + f"out_header->{ext.chain_next.name} = (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 + f"FIXME(\"Unhandled {self.operand.chain_type.name} %u.\\n\", in_header->{self.operand.chain_type.name});\n"
            body += "            break;\n"
            body += "        }\n"
            body += "    }\n"
        elif self.conv and self.direction == Direction.INPUT and self.operand.chain_next:
            body += f"    if (in->{self.operand.chain_next.name})\n"
            body += f"        FIXME(\"Unexpected {self.operand.chain_next.name}\\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_name
        self.conv = conv
        self.unwrap = unwrap

        if array.is_static_array() and direction == Direction.INPUT:
            LOGGER.error("Static array input conversion is not supported")

        win_type = "win32" if self.conv else "win64"
        ptr = "pointer_" if array.pointer_array else ""

        self.name = f"convert_{self.type}_{ptr}array_" + convert_suffix(direction, win_type, unwrap, array.is_wrapped())

    def __eq__(self, other):
        return self.name == other.name

    def definition(self, conversions):
        """ 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)
        pointer_array = self.array.pointer_array or self.array.is_pointer_pointer()

        win_type = self.type
        if self.conv:
            if self.array.needs_win32_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:
            if self.conv and pointer_array:
                out = "PTR32 *out"
            else:
                out = "{0} {1}out".format(win_type, pointer_part)
            params = ["const {0} {1}in".format(self.type, pointer_part), out, "uint32_t count"]
            return_type = None
        elif self.conv and 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 self.array.struct.chain_next

        # Generate function prototype.
        if return_type:
            body += "static {0}{1} {2}{3}(".format(
                "const " if self.array.is_const() else "", return_type, pointer_part, self.name)
        else:
            body += "static 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:
            pointer = self.array.pointer.replace('const*', '*').replace(' *', '*')
            body += "    {0} {1}out;\n".format(return_type, pointer)
        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
                is_const = self.array.is_const() if self.array.is_pointer() else False

                convert = StructConversionFunction(struct, self.direction, self.conv, self.unwrap, is_const)
                ctx = "ctx, " if self.direction == Direction.INPUT and struct.needs_alloc(self.conv, self.unwrap) else ""
                inp = f"({win_type} *)UlongToPtr(in[i])" if self.conv else "in[i]"

                if not pointer_array:
                    conversions.append(convert)
                    body += f"        {convert.name}({ctx}&in[i], &out[i]);\n"

                elif struct.needs_conversion(self.conv, self.unwrap, self.direction, False):
                    conversions.append(convert)
                    body += u"        if (in[i])\n"
                    body += u"        {\n"
                    body += u"            out[i] = conversion_context_alloc(ctx, sizeof(*out[i]));\n"
                    body += f"            {convert.name}({ctx}{inp}, out[i]);\n"
                    body += u"        }\n"
                    body += u"        else\n"
                    body += u"            out[i] = NULL;\n"
                else:
                    body += u"        out[i] = UlongToPtr(in[i]);\n"

            elif self.array.is_handle():
                if 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 self.unwrap == Unwrap.NONE or not handle.is_wrapped():
                    body += "        out[i] = {0};\n".format(input)
                elif self.direction == Direction.INPUT:
                    body += "        out[i] = {0};\n".format(handle.unwrap_handle(input, self.unwrap))
                else:
                    LOGGER.warning("Unhandled handle output conversion")
            elif pointer_array:
                if self.direction == Direction.INPUT:
                    body += "        out[i] = UlongToPtr(in[i]);\n"
                else:
                    body += "        out[i] = PtrToUlong(in[i]);\n"
            else:
                body += "        out[i] = in[i];\n"

            body += "    }\n"

        if return_type:
            body += "\n    return {0}out;\n".format("(void *)" if pointer_array else "")
        body += "}\n"

        if not self.conv:
            body += "#endif /* _WIN64 */\n"

        body += "\n"

        return body


class Generator(object):
    def __init__(self, vk_xml, video_xml):
        # We aggregate all types in here for cross-referencing.
        self.surface_extensions = []

        # 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(vk_xml)
        root = tree.getroot()
        # The video XML currently only has enums, types, and part of the
        # extension data in it.
        # All of the relevant extensions and commands are in vk.xml.
        video_tree = ET.parse(video_xml)
        video_root = video_tree.getroot()

        self.copyright = root.find('./comment').text
        self.video_copyright = video_root.find('./comment').text

        root.extend(video_root)
        root.extend(ET.parse("winevk.xml").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 Type.all(Enum):
            enum.fixup_64bit_aliases()

        self._match_object_types()

    def _generate_copyright(self, f, spec_file=False):
        f.write("# " if spec_file else "/* ")
        f.write("Automatically generated from Vulkan vk.xml and video.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.copyright.splitlines()])
        lines.extend(["and from Vulkan video.xml file covered",
            "by the following copyright and permission notice:"])
        lines.extend([l.rstrip(" ") for l in self.video_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 <stdlib.h>\n\n")

        f.write("#include \"vulkan_private.h\"\n\n")

        f.write("WINE_DEFAULT_DEBUG_CHANNEL(vulkan);\n\n")

        # Create thunks for instance and device functions.
        # Global functions don't go through the thunks.
        thunks = ""
        conversions = []

        for func in Type.all(Function, Function.needs_thunk):
            thunks += func.gen_unix_thunk(conversions, prefix="thunk64_")
            thunks += func.gen_unix_thunk(conversions, prefix="thunk32_", conv=True)

        # iterate over each of the conversion function dependencies first
        def enum_conversions(conversions, seen=set()):
            for conversion in unique(conversions, key=lambda conv: conv.name, seen=seen):
                dependencies = []
                definition = conversion.definition(dependencies)
                yield from enum_conversions(dependencies, seen)
                yield definition, conversion

        conv_helpers = ""
        win32_structs = []
        for definition, conversion in enum_conversions(conversions):
            if isinstance(conversion, StructConversionFunction):
                for e in conversion.operand.struct_extensions:
                    if not e.needs_win32_type():
                        continue
                    win32_structs.append(e)
                if conversion.operand.needs_win32_type():
                    # 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.
                    win32_structs.append(conversion.operand)
            conv_helpers += definition

        win32_structs = sorted(win32_structs, key=lambda struct: (-struct.order, struct.name))
        for struct in unique(win32_structs, key=lambda struct: struct.name):
            f.write(struct.definition(suffix="32"))

        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 Type.all(Handle, Handle.is_wrapped, by_name=True):
            f.write("    case {}:\n".format(handle.object_type))
            if handle.is_dispatchable():
                f.write("        return (uint64_t) (uintptr_t) ")
                f.write(handle.host_handle("(({}) (uintptr_t) handle)".format(handle.name)))
            else:
                f.write("        return (uint64_t) ")
                f.write(handle.host_handle("handle"))
            f.write(";\n");
        f.write("    default:\n")
        f.write("       return handle;\n")
        f.write("    }\n")
        f.write("}\n\n")

        f.write(conv_helpers)
        f.write(thunks)

        f.write("BOOL wine_vk_is_type_wrapped(VkObjectType type)\n")
        f.write("{\n")
        f.write("    return FALSE")
        for handle in Type.all(Handle, Handle.is_wrapped, by_name=True):
            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 func in Type.all(Function, Function.needs_thunk):
            f.write(f"    {func.unixlib_entry(64)},\n")
        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("    wow64_init_vulkan,\n")
        f.write("    vk_is_available_instance_function32,\n")
        f.write("    vk_is_available_device_function32,\n")
        for func in Type.all(Function, Function.needs_thunk):
            f.write(f"    {func.unixlib_entry(32)},\n")
        f.write("};\n")
        f.write("C_ASSERT(ARRAYSIZE(__wine_unix_call_funcs) == unix_count);\n")

    def generate_thunks_h(self, f):
        self._generate_copyright(f)

        f.write("#ifndef __WINE_VULKAN_THUNKS_H\n")
        f.write("#define __WINE_VULKAN_THUNKS_H\n\n")

        major, minor, patch = VK_XML_VERSION.split('.')
        f.write("#define WINE_VK_VERSION VK_API_VERSION_{0}_{1}\n\n".format(major, minor))

        # 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 func in Type.all(Function, Function.needs_private_thunk):
            f.write(func.gen_wrapper())
        f.write("\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 func in Type.all(Function, Function.needs_loader_thunk):
            f.write(func.gen_thunk())

        f.write("static const struct vulkan_func vk_device_dispatch_table[] =\n{\n")
        for func in Type.all(Function, Function.is_client_device):
            f.write(f"    {{\"{func.name}\", {func.name}}},\n")
        f.write("};\n\n")

        f.write("static const struct vulkan_func vk_phys_dev_dispatch_table[] =\n{\n")
        for func in Type.all(Function, Function.is_client_physical_device):
            f.write(f"    {{\"{func.name}\", {func.name}}},\n")
        f.write("};\n\n")

        f.write("static const struct vulkan_func vk_instance_dispatch_table[] =\n{\n")
        for func in Type.all(Function, Function.is_client_instance):
            f.write(f"    {{\"{func.name}\", {func.name}}},\n")
        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 func in Type.all(Function, Function.needs_thunk):
            f.write(f"    unix_{func.name},\n")
        f.write("    unix_count,\n")
        f.write("};\n\n")

        def function_params(func):
            ret = f"struct {func.name}_params\n"
            ret += "{\n"
            for param in func.params:
                ret += f"    {param.as_member()};\n"
            if func.type != "void":
                ret += f"    {func.type} result;\n"
            ret += "};\n\n"
            return ret

        for func in Type.all(Function, Function.needs_thunk):
            f.write(function_params(func))
        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 <windef.h>\n")
        f.write("#include <stdint.h>\n\n")

        f.write("#ifdef WINE_UNIX_LIB\n")
        f.write("#define VK_NO_PROTOTYPES\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")

        f.write("#ifndef WINE_VULKAN_NO_X11_TYPES\n")
        f.write("typedef unsigned long Window;\n")
        f.write("typedef unsigned long VisualID;\n")
        f.write("typedef struct _XDisplay Display;\n")
        f.write("#endif\n")

        for type in Type.all((Constant, Define), required=False):
            f.write(type.definition())
        f.write("\n")

        for handle in Type.all(Handle, by_name=True):
            # 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.
            f.write(handle.definition())
        f.write("\n")

        for flags in Type.all(Flags):
            f.write(flags.definition())
        f.write("\n")

        # Define enums, this includes values for some of the bitmask types as well.
        for enum in Type.all(Enum, by_name=True):
            f.write(enum.definition())

        for type in Type.all((Record, FunctionPointer)):
            if type.name != "SECURITY_ATTRIBUTES":
                f.write(type.definition())

        for func in Type.all(Function):
            f.write(func.gen_pointer())
        f.write("\n")

        f.write("#ifndef VK_NO_PROTOTYPES\n")
        for func in Type.all(Function):
            f.write(func.gen_prototype())
        f.write("#endif /* VK_NO_PROTOTYPES */\n\n")

        f.write("#define ALL_VK_DEVICE_FUNCS")
        for func in Type.all(Function, Function.is_host_device):
            f.write(f" \\\n    USE_VK_FUNC({func.name})")
        f.write("\n\n")

        f.write("#define ALL_VK_CLIENT_DEVICE_EXTS")
        for ext in self.extensions:
            if ext.type == "device" and ext.is_exposed:
                f.write(f" \\\n    USE_VK_EXT({ext.name})")
        f.write("\n\n")

        f.write("#define ALL_VK_DEVICE_EXTS ALL_VK_CLIENT_DEVICE_EXTS")
        for ext in self.extensions:
            if ext.type == "device" and not ext.is_exposed:
                f.write(f" \\\n    USE_VK_EXT({ext.name})")
        f.write("\n\n")

        f.write("#define ALL_VK_INSTANCE_FUNCS")
        for func in Type.all(Function, Function.is_host_instance):
            f.write(f" \\\n    USE_VK_FUNC({func.name})")
        f.write("\n\n")

        f.write("#define ALL_VK_CLIENT_INSTANCE_EXTS")
        for ext in self.extensions:
            if ext.type == "instance" and ext.is_exposed:
                f.write(f" \\\n    USE_VK_EXT({ext.name})")
        f.write("\n\n")

        f.write("#define ALL_VK_INSTANCE_EXTS ALL_VK_CLIENT_INSTANCE_EXTS")
        for ext in self.extensions:
            if ext.type == "instance" and not ext.is_exposed:
                f.write(f" \\\n    USE_VK_EXT({ext.name})")
        f.write("\n\n")

        f.write("#endif /* __WINE_VULKAN_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")
        for func in Type.all(Function, Function.is_core, required=False):
            f.write(func.spec())
        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)

        for func in Type.all(Function, Function.is_core, required=False):
            f.write(func.spec(symbol=f"winevulkan.{func.name}"))

    def _match_object_types(self):
        """ Matches each handle with the correct object type. """
        # Use upper case comparison for simplicity.
        object_types = {}
        for value in Type.get("VkObjectType").values:
            object_name = "VK" + value.name[len("VK_OBJECT_TYPE"):].replace("_", "")
            object_types[object_name] = value.name

        for handle in Type.all(Handle):
            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. """
        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 filter(is_api_supported, commands):
            alias_name = command.attrib.get("alias")
            if alias_name:
                alias_commands.append(command)
                continue

            Function.from_xml(command)

        for command in alias_commands:
            alias_name = command.attrib.get("alias")
            alias = Type.get(alias_name)
            Function.from_alias(command, alias)

    def _parse_enums(self, root):
        """ Parse enums section or better described as constants section. """
        enums = {}
        for enum in filter(is_api_supported, root.findall("./enums")):
            name = enum.attrib.get("name")
            _type = enum.attrib.get("type")

            if _type in ("enum", "bitmask"):
                enum_obj = Enum.from_xml(enum)
                enums[name] = enum_obj
            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.
                    Constant(value.get("name"), value.get("alias") or value.get("value"))

    def process_enum_alias(self, enum_elem, extension):
        if extends := enum_elem.get("extends"):
            # Need to define EnumValues 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 EnumValues to alias to the provisional
            # extension.
            if any(value.alias == enum_elem.get("name") for enum in Type.all(Enum) for value in enum.values):
                Type.get(extends).add(EnumValue.from_xml(enum_elem, extension))
        elif alias := enum_elem.get("alias"):
            Constant(enum_elem.get("name"), alias)

    def process_enum_value(self, enum_elem, extension=None):
        if extends := enum_elem.get("extends"):
            Type.get(extends).add(EnumValue.from_xml(enum_elem, extension))
        elif value := enum_elem.get("value"):
            Constant(enum_elem.get("name"), value)

    def _parse_extensions(self, root):
        """ Parse extensions section and pull in any types and commands for this extension. """
        exts = root.findall("./extensions/extension")
        exts = {ext.attrib["name"]: ext for ext in exts}


        # return a dict of required extensions for an extension
        def required_extensions(ext):
            features = [req.attrib.get("feature") for req in ext.findall("require")]
            requires = ext.attrib.get("requires", "").split(",")
            depends = re.split(r'\W+', ext.attrib.get("depends", ""))
            return {name: exts[name] for name in set(requires + depends + features) & exts.keys()}

        # return a set of required features for an extension
        def required_versions(ext):
            features = [req.attrib.get("feature") for req in ext.findall("require")]
            depends = re.split(r'\W+', ext.attrib.get("depends", ""))
            versions = filter(None, set(depends + features) - exts.keys())
            regex = re.compile(r"VERSION_(\d)_(\d)$")
            return (tuple(map(int, regex.search(name).groups())) for name in versions)

        # iterate over each of the extension dependencies first
        def enum_extensions(extensions, seen=set()):
            for name in unique(extensions.keys(), seen=seen):
                depends = required_extensions(extensions[name])
                yield from enum_extensions(depends, seen)
                yield name, extensions[name], depends.keys()

        # transitively iterate over required extensions to flag unsupported extensions
        for name, ext, depends in enum_extensions(exts):
            if depends & UNSUPPORTED_EXTENSIONS:
                UNSUPPORTED_EXTENSIONS.add(name)
            elif ext.attrib.get("platform", "win32") not in UNEXPOSED_PLATFORMS | {"win32"}:
                UNSUPPORTED_EXTENSIONS.add(name)
            # Some extensions are not ready or have numbers reserved as a place holder
            # or are only supported for VulkanSC.
            elif not set(ext.attrib.get("supported").split(",")) & {"vulkan"}:
                UNSUPPORTED_EXTENSIONS.add(name)
            # 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.
            elif not name in ALLOWED_X_EXTENSIONS and name.startswith(UNSUPPORTED_EXTGROUPS):
                UNSUPPORTED_EXTENSIONS.add(name)


        extensions = []

        def process_ext(ext):
            extension = Extension(**ext.attrib)

            # Set extension name on any functions calls part of this extension as we
            # were not aware of the name during initial parsing.
            for command in ext.findall("require/command"):
                name = command.get("name")
                Type.get(name).extensions.add(extension)

            if not extension.is_supported:
                return

            # Extensions can define EnumValues which alias to provisional extensions. Pre-process
            # extensions to define any required EnumValues 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_enum_alias(enum_elem, ext)

            LOGGER.debug("Loading extension: {0}".format(extension.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_enum_value(enum_elem, ext)

                for t in require.findall("type"):
                    # video.xml uses "type" to include various headers,
                    # including stdint.h (which we include manually) and
                    # specific vk_video/* headers (which we don't use).
                    # We don't even parse <type category="include"> tags.
                    # Therefore just skip any types that aren't found.
                    if type := Type.get(t.get("name")):
                        type.extensions.add(extension)
                        type.require()

                # Pull in any commands we need. We infer types to pull in from the command
                # as well.
                for command in require.findall("command"):
                    name = command.get("name")
                    Type.get(name).require()


            # Store a list with extensions.
            # Video extensions are for some reason split up across vk.xml and
            # video.xml.
            # vk.xml has the actual extension definition and most of the
            # dependent type and enum definitions.
            # video.xml has an <extension> whose "name" is the target header
            # file and whose <require> has the remaining type and enum
            # definitions. We parse those above for the <require> part, but we
            # don't want to add the extension as a real extension here.
            # Checking for the existence of "type" seems to achieve this.
            if "type" in ext.attrib:
                extensions.append(extension)

        for ext in exts.values():
            process_ext(ext)

        # 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 filter(is_api_supported, root.findall("./feature")):
            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
                    if type := Type.get(tag.get("name")):
                        type.require()
                    if tag.tag == "enum":
                        self.process_enum_value(tag)

    def _parse_types(self, root):
        """ Parse types section, which contains all data types e.g. structs, typedefs etcetera. """
        types = root.findall("./types/type")

        for t in filter(is_api_supported, types):
            category, requires = t.get("category"), t.get("requires")
            name = t.findtext("name") or t.findtext("proto/name") or t.get("name")

            if alias := t.get("alias"):
                Type(name, alias=alias)
                continue

            if category == "basetype":
                Define.from_xml(t)
            elif category == "bitmask":
                Flags(name, t.findtext("type"), requires=requires)
            elif category == "define":
                Define.from_xml(t)
            elif category == "funcpointer":
                FunctionPointer.from_xml(t)
            elif category == "handle":
                Handle.from_xml(t)
            elif category in ["struct", "union"]:
                Record.from_xml(t)
            elif name not in Type.types:
                Type(name)

        # 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 type in Type.types.values():
            type.set_order()

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(dst_filename, src_filename):
    url = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/v{0}/xml/{1}".format(VK_XML_VERSION, src_filename)
    if not os.path.isfile(dst_filename):
        urllib.request.urlretrieve(url, dst_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")
    parser.add_argument("-X", "--video-xml", default=None, type=str, help="path to specification video 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 "XDG_CACHE_HOME" in os.environ:
        cache = os.path.expandvars("$XDG_CACHE_HOME/wine")
    else:
        cache = os.path.expandvars("$HOME/.cache/wine")
    os.makedirs(cache, exist_ok=True)

    if args.xml:
        vk_xml = args.xml
    else:
        vk_xml = os.path.join(cache, "vk-{0}.xml".format(VK_XML_VERSION))
        download_vk_xml(vk_xml, "vk.xml")

    if args.video_xml:
        video_xml = args.video_xml
    else:
        video_xml = os.path.join(cache, "video-{0}.xml".format(VK_XML_VERSION))
        download_vk_xml(video_xml, "video.xml")

    generator = Generator(vk_xml, video_xml)

    with open(WINE_VULKAN_H, "w") as f:
        generator.generate_vulkan_h(f)

    with open(WINE_VULKAN_THUNKS_H, "w") as f:
        generator.generate_thunks_h(f)

    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()
