#!/usr/bin/python3 # Wine Vulkan generator # # Copyright 2017-2018 Roderick Colenbrander # # 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 re import sys import xml.etree.ElementTree as ET from collections import OrderedDict from collections.abc import Sequence from enum import Enum # This script generates code for a Wine Vulkan ICD driver from Vulkan's vk.xml. # Generating the code is like 10x worse than OpenGL, which is mostly a calling # convention passthrough. # # The script parses vk.xml and maps functions and types to helper objects. These # helper objects simplify the xml parsing and map closely to the Vulkan types. # The code generation utilizes the helper objects during code generation and # most of the ugly work is carried out by these objects. # # Vulkan ICD challenges: # - Vulkan ICD loader (vulkan-1.dll) relies on a section at the start of # 'dispatchable handles' (e.g. VkDevice, VkInstance) for it to insert # its private data. It uses this area to stare its own dispatch tables # for loader internal use. This means any dispatchable objects need wrapping. # # - Vulkan structures have different alignment between win32 and 32-bit Linux. # This means structures with alignment differences need conversion logic. # Often structures are nested, so the parent structure may not need any # conversion, but some child may need some. # # vk.xml parsing challenges: # - Contains type data for all platforms (generic Vulkan, Windows, Linux,..). # Parsing of extension information required to pull in types and functions # we really want to generate. Just tying all the data together is tricky. # # - Extensions can affect core types e.g. add new enum values, bitflags or # additional structure chaining through 'pNext' / 'sType'. # # - Arrays are used all over the place for parameters or for structure members. # Array length is often stored in a previous parameter or another structure # member and thus needs careful parsing. LOGGER = logging.Logger("vulkan") LOGGER.addHandler(logging.StreamHandler()) # Filenames to create. WINE_VULKAN_H = "../../include/wine/vulkan.h" WINE_VULKAN_DRIVER_H = "../../include/wine/vulkan_driver.h" WINE_VULKAN_THUNKS_C = "vulkan_thunks.c" WINE_VULKAN_THUNKS_H = "vulkan_thunks.h" # Functions part of our winevulkan graphics driver interface. # DRIVER_VERSION should be bumped on any change to driver interface # in FUNCTION_OVERRIDES DRIVER_VERSION = 1 # Table of functions for which we have a special implementation. # This are regular device / instance functions for which we need # to more work compared to a regular thunk or because they are # part of the driver interface. # - dispatch set whether we need a function pointer in the device # / instance dispatch table. # - driver sets whether the api is part of the driver interface. # - thunk sets whether to create a thunk in vulkan_thunks.c. FUNCTION_OVERRIDES = { # Global functions "vkCreateInstance" : {"dispatch" : False, "driver" : True, "thunk" : False}, "vkEnumerateInstanceExtensionProperties" : {"dispatch" : False, "driver" : True, "thunk" : False}, "vkGetInstanceProcAddr": {"dispatch" : False, "driver" : True, "thunk" : False}, # Instance functions "vkDestroyInstance" : {"dispatch" : True, "driver" : True, "thunk" : False }, "vkEnumerateDeviceExtensionProperties" : {"dispatch" : True, "driver" : False, "thunk" : False}, "vkEnumeratePhysicalDevices" : {"dispatch" : True, "driver" : False, "thunk" : False}, } class VkBaseType(object): def __init__(self, name, _type, requires=None): """ Vulkan base type class. VkBaseType is mostly used by Vulkan to define its own base types like VkFlags through typedef out of e.g. uint32_t. Args: name (:obj:'str'): Name of the base type. _type (:obj:'str'): Underlaying type requires (:obj:'str', optional): Other types required. Often bitmask values pull in a *FlagBits type. """ self.name = name self.type = _type self.requires = requires self.required = False def definition(self): text = "typedef {0} {1};\n".format(self.type, self.name) return text class VkConstant(object): def __init__(self, name, value): self.name = name self.value = value def definition(self): text = "#define {0} {1}\n".format(self.name, self.value) return text class VkDefine(object): def __init__(self, name, value): self.name = name self.value = value @staticmethod def from_xml(define): name_elem = define.find("name") if name_elem is None: # some_value # At the time of writing there is only 1 define of this category # 'VK_DEFINE_NON_DISPATCHABLE_HANDLE'. name = define.attrib.get("name") # We override behavior of VK_DEFINE_NON_DISPATCHABLE handle as the default # definition various between 64-bit (uses pointers) and 32-bit (uses uint64_t). # This complicates TRACEs in the thunks, so just use uint64_t. if name == "VK_DEFINE_NON_DISPATCHABLE_HANDLE": value = "#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;" else: value = define.text return VkDefine(name, value) # With a name element the structure is like: # some_namesome_value name = name_elem.text # VK_API_VERSION is a deprecated constant and we don't really want to parse it or generate # code for it. However the vk.xml feature section still references it. if name == "VK_API_VERSION": return VkDefine(name, "") # The body of the define is basically unstructured C code. It is not meant for easy parsing. # Some lines contain deprecated values or comments, which we try to filter out. value = "" for line in define.text.splitlines(): # Skip comments or deprecated values. if "//" in line: continue value += line for child in define: value += child.text if child.tail is not None: value += child.tail return VkDefine(name, value.rstrip(' ')) def definition(self): if self.value is None: return "" # Nothing to do as the value was already put in the right form during parsing. return "{0}\n".format(self.value) class VkEnum(object): def __init__(self, name, values): self.name = name self.values = values self.required = False @staticmethod def from_xml(enum): name = enum.attrib.get("name") values = [] for v in enum.findall("enum"): # Value is either a value or a bitpos, only one can exist. value = v.attrib.get("value") if value is None: # bitmask value = 1 << int(v.attrib.get("bitpos")) values.append(VkEnumValue(v.attrib.get("name"), value, hex=True)) else: # Some values are in hex form. We want to preserve the hex representation # at least when we convert back to a string. Internally we want to use int. if "0x" in value: values.append(VkEnumValue(v.attrib.get("name"), int(value, 0), hex=True)) else: values.append(VkEnumValue(v.attrib.get("name"), int(value, 0))) # vulkan.h contains a *_MAX_ENUM value set to 32-bit at the time of writing, # which is to prepare for extensions as they can add values and hence affect # the size definition. max_name = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2',name).upper() + "_MAX_ENUM" values.append(VkEnumValue(max_name, 0x7fffffff, hex=True)) return VkEnum(name, values) def add(self, value): """ Add a value to enum. """ self.values.append(value) def definition(self): text = "typedef enum {0}\n{{\n".format(self.name) # Print values sorted, values can have been added in a random order. values = sorted(self.values, key=lambda value: value.value) for value in values: text += " {0},\n".format(value.definition()) text += "}} {0};\n\n".format(self.name) return text class VkEnumValue(object): def __init__(self, name, value, hex=False): self.name = name self.value = value self.hex = hex def __repr__(self): return "{0}={1}".format(self.name, self.value) def definition(self): """ Convert to text definition e.g. VK_FOO = 1 """ # Hex is commonly used for FlagBits and sometimes within # a non-FlagBits enum for a bitmask value as well. if self.hex: return "{0} = 0x{1:08x}".format(self.name, self.value) else: return "{0} = {1}".format(self.name, self.value) class VkFunction(object): def __init__(self, _type=None, name=None, params=[], extension=None): self.extension = extension self.name = name self.type = _type self.params = params # For some functions we need some extra metadata from FUNCTION_OVERRIDES. func_info = FUNCTION_OVERRIDES.get(self.name, None) self.dispatch = func_info["dispatch"] if func_info is not None else True self.driver = func_info["driver"] if func_info is not None else False self.thunk_needed = func_info["thunk"] if func_info is not None else True # Required is set while parsing which APIs and types are required # and is used by the code generation. self.required = False @staticmethod def from_xml(command, types): proto = command.find("proto") func_name = proto.find("name").text func_type = proto.find("type").text params = [] for param in command.findall("param"): vk_param = VkParam.from_xml(param, types) params.append(vk_param) return VkFunction(_type=func_type, name=func_name, params=params) def is_device_func(self): # If none of the other, it must be a device function return not self.is_global_func() and not self.is_instance_func() def is_driver_func(self): """ Returns if function is part of Wine driver interface. """ return self.driver def is_global_func(self): # Treat vkGetInstanceProcAddr as a global function as it # can operate with NULL for vkInstance. if self.name == "vkGetInstanceProcAddr": return True # Global functions are not passed a dispatchable object. elif self.params[0].is_dispatchable(): return False return True def is_instance_func(self): # Instance functions are passed VkInstance or VkPhysicalDevice. if self.params[0].type in ["VkInstance", "VkPhysicalDevice"]: return True return False def is_required(self): return self.required def needs_dispatch(self): return self.dispatch def needs_thunk(self): return self.thunk_needed def pfn(self, call_conv=None, conv=False): """ Create function pointer. """ if call_conv is not None: pfn = "{0} ({1} *p_{2})(".format(self.type, call_conv, self.name) else: pfn = "{0} (*p_{1})(".format(self.type, self.name) for i, param in enumerate(self.params): if param.const: pfn += param.const + " " pfn += param.type if conv and param.needs_conversion(): pfn += "_host" if param.is_pointer(): pfn += " " + param.pointer if param.array_len is not None: pfn += "[{0}]".format(param.array_len) if i < len(self.params) - 1: pfn += ", " pfn += ")" return pfn def prototype(self, call_conv=None, prefix=None, postfix=None): """ Generate prototype for given function. Args: call_conv (str, optional): calling convention e.g. WINAPI prefix (str, optional): prefix to append prior to function name e.g. vkFoo -> wine_vkFoo postfix (str, optional): text to append after function name but prior to semicolon e.g. DECLSPEC_HIDDEN """ proto = "{0}".format(self.type) if call_conv is not None: proto += " {0}".format(call_conv) if prefix is not None: proto += " {0}{1}(".format(prefix, self.name) else: proto += " {0}(".format(self.name) # Add all the paremeters. proto += ", ".join([p.definition() for p in self.params]) if postfix is not None: proto += ") {0}".format(postfix) else: proto += ")" return proto def stub(self, call_conv=None, prefix=None): stub = self.prototype(call_conv=call_conv, prefix=prefix) stub += "\n{\n" stub += " {0}".format(self.trace(message="stub: ", trace_func="FIXME")) if self.type == "VkResult": stub += " return VK_ERROR_OUT_OF_HOST_MEMORY;\n" elif self.type == "VkBool32": stub += " return VK_FALSE;\n" stub += "}\n\n" return stub def trace(self, message=None, trace_func=None): """ 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() 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(param.name) else: trace += ", {0}".format(param.name) trace += ");\n" return trace class VkFunctionPointer(object): def __init__(self, _type, name, members): self.name = name self.members = members self.type = _type self.required = False @staticmethod def from_xml(funcpointer): members = [] begin = None for t in funcpointer.findall("type"): # General form: # void* pUserData, # Parsing of the tail (anything past ) is tricky since there # can be other data on the next line like: const int.. const = begin _type = t.text lines = t.tail.split(",\n") if lines[0][0] == "*": pointer = "*" name = lines[0][1:].strip() else: pointer = None name = lines[0].strip() # Filter out ); if it is contained. name = name.partition(");")[0] # If tail encompasses multiple lines, assign the second line to begin # for the next line. try: begin = lines[1].strip() except IndexError: begin = None members.append(VkMember(const=const, _type=_type, pointer=pointer, name=name)) _type = funcpointer.text name = funcpointer.find("name").text return VkFunctionPointer(_type, name, members) def definition(self): text = "{0} {1})(\n".format(self.type, self.name) first = True if len(self.members) > 0: for m in self.members: if first: text += " " + m.definition() first = False else: text += ",\n " + m.definition() else: # Just make the compiler happy by adding a void parameter. text += "void" text += ");\n" return text class VkHandle(object): def __init__(self, name, _type, parent): self.name = name self.type = _type self.parent = parent self.required = False @staticmethod def from_xml(handle): name = handle.find("name").text _type = handle.find("type").text parent = handle.attrib.get("parent") # Most objects have a parent e.g. VkQueue has VkDevice. return VkHandle(name, _type, parent) def definition(self): """ Generates handle definition e.g. VK_DEFINE_HANDLE(vkInstance) """ return "{0}({1})\n".format(self.type, self.name) 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" class VkMember(object): def __init__(self, const=None, _type=None, pointer=None, name=None, array_len=None, dyn_array_len=None, optional=False, extension_structs=None): self.const = const self.name = name self.pointer = pointer self.type = _type self.type_info = None self.array_len = array_len self.dyn_array_len = dyn_array_len self.optional = optional self.extension_structs = extension_structs def __eq__(self, other): """ Compare member based on name against a string. This method is for convenience by VkStruct, which holds a number of members and needs quick checking if certain members exist. """ if self.name == other: return True return False def __repr__(self): return "{0} {1} {2} {3} {4} {5}".format(self.const, self.type, self.pointer, self.name, self.array_len, self.dyn_array_len) @staticmethod def from_xml(member): """ Helper function for parsing a member tag within a struct or union. """ name_elem = member.find("name") type_elem = member.find("type") const = member.text.strip() if member.text else None member_type = None pointer = None array_len = None if type_elem is not None: member_type = type_elem.text if type_elem.tail is not None: pointer = type_elem.tail.strip() if type_elem.tail.strip() != "" else None # Name of other member within, which stores the number of # elements pointed to be by this member. dyn_array_len = member.get("len", None) if "validextensionstructs" in member.attrib: extension_structs = member.get("validextensionstructs").split(",") else: extension_structs = None # Some members are optional, which is important for conversion code e.g. not dereference NULL pointer. optional = True if member.get("optional") else False # Usually we need to allocate memory for dynamic arrays. We need to do the same in a few other cases # like for VkCommandBufferBeginInfo.pInheritanceInfo. Just threat such cases as dynamic arrays of # size 1 to simplify code generation. if dyn_array_len is None and pointer is not None: dyn_array_len = 1 # Some members are arrays, attempt to parse these. Formats include: # charextensionName[VK_MAX_EXTENSION_NAME_SIZE] # uint32_tfoo[4] if name_elem.tail and name_elem.tail[0] == '[': LOGGER.debug("Found array type") enum_elem = member.find("enum") if enum_elem is not None: array_len = enum_elem.text else: # Remove brackets around length array_len = name_elem.tail.strip("[]") return VkMember(const=const, _type=member_type, pointer=pointer, name=name_elem.text, array_len=array_len, dyn_array_len=dyn_array_len, optional=optional, extension_structs=extension_structs) def definition(self, align=False, conv=False): """ Generate prototype for given function. Args: align (bool, optional): Enable alignment if a type needs it. This adds WINE_VK_ALIGN(8) to a member. conv (bool, optional): Enable conversion if a type needs it. This appends '_host' to the name. """ text = "" if self.is_const(): text += "const " if conv and self.is_struct(): text += "{0}_host".format(self.type) else: text += self.type if self.is_pointer(): text += " {0}{1}".format(self.pointer, self.name) else: if align and self.needs_alignment(): text += " WINE_VK_ALIGN(8) " + self.name else: text += " " + self.name if self.is_static_array(): text += "[{0}]".format(self.array_len) return text def is_const(self): return self.const is not None def is_dynamic_array(self): """ Returns if the member is an array element. Vulkan uses this for dynamically sized arrays for which there is a 'count' parameter. """ return self.dyn_array_len is not None def is_handle(self): return self.type_info["category"] == "handle" def is_pointer(self): return self.pointer is not None def is_static_array(self): """ Returns if the member is an array. Vulkan uses this often for fixed size arrays in which the length is part of the member. """ return self.array_len is not None def is_struct(self): return self.type_info["category"] == "struct" def is_union(self): return self.type_info["category"] == "union" def needs_alignment(self): """ Check if this member needs alignment for 64-bit data. Various structures need alignment on 64-bit variables due to compiler differences on 32-bit between Win32 and Linux. """ if self.is_pointer(): return False elif self.type == "size_t": return False elif self.type in ["uint64_t", "VkDeviceSize"]: return True elif self.is_struct(): struct = self.type_info["data"] return struct.needs_alignment() elif self.is_handle(): # Dispatchable handles are pointers to objects, while # non-dispatchable are uint64_t and hence need alignment. handle = self.type_info["data"] return False if handle.is_dispatchable() else True return False def set_type_info(self, type_info): """ Helper function to set type information from the type registry. This is needed, because not all type data is available at time of parsing. """ self.type_info = type_info class VkParam(object): """ Helper class which describes a parameter to a function call. """ def __init__(self, type_info, const=None, pointer=None, name=None, array_len=None, dyn_array_len=None): self.const = const self.name = name self.array_len = array_len self.dyn_array_len = dyn_array_len self.pointer = pointer self.type_info = type_info self.type = type_info["name"] # For convenience self.handle = type_info["data"] if type_info["category"] == "handle" else None self.struct = type_info["data"] if type_info["category"] == "struct" else None self._set_format_string() def __repr__(self): return "{0} {1} {2} {3} {4}".format(self.const, self.type, self.pointer, self.name, self.array_len, self.dyn_array_len) @staticmethod def from_xml(param, types): """ Helper function to create VkParam from xml. """ # Parameter parsing is slightly tricky. All the data is contained within # a param tag, but some data is within subtags while others are text # before or after the type tag. # Common structure: # const char* pLayerName name_elem = param.find("name") array_len = None name = name_elem.text # Tail contains array length e.g. for blendConstants param of vkSetBlendConstants if name_elem.tail is not None: array_len = name_elem.tail.strip("[]") # Name of other parameter in function prototype, which stores the number of # elements pointed to be by this parameter. dyn_array_len = param.get("len", None) const = param.text.strip() if param.text else None type_elem = param.find("type") pointer = type_elem.tail.strip() if type_elem.tail.strip() != "" else None # Since we have parsed all types before hand, this should not happen. type_info = types.get(type_elem.text, None) if type_info is None: LOGGER.err("type info not found for: {0}".format(type_elem.text)) return VkParam(type_info, const=const, pointer=pointer, name=name, array_len=array_len, dyn_array_len=dyn_array_len) def _set_format_string(self): """ Internal helper function to be used by constructor to set format string. """ # Determine a format string used by code generation for traces. # 64-bit types need a conversion function. self.format_conv = None if self.is_static_array() or self.is_pointer(): self.format_str = "%p" else: if self.type_info["category"] == "bitmask": self.format_str = "%#x" elif self.type_info["category"] == "enum": self.format_str = "%d" elif self.is_handle(): # We use uint64_t for non-dispatchable handles as opposed to pointers # for dispatchable handles. if self.handle.is_dispatchable(): self.format_str = "%p" else: self.format_str = "0x%s" self.format_conv = "wine_dbgstr_longlong({0})" elif self.type == "float": self.format_str = "%f" elif self.type == "int": self.format_str = "%d" elif self.type == "int32_t": self.format_str = "%d" elif self.type == "size_t": self.format_str = "0x%s" self.format_conv = "wine_dbgstr_longlong({0})" elif self.type in ["uint32_t", "VkBool32"]: self.format_str = "%u" elif self.type in ["uint64_t", "VkDeviceSize"]: self.format_str = "0x%s" self.format_conv = "wine_dbgstr_longlong({0})" elif self.type == "HANDLE": self.format_str = "%p" elif self.type in ["VisualID", "xcb_visualid_t", "RROutput"]: # Don't care about Linux specific types. self.format_str = "" else: LOGGER.warn("Unhandled type: {0}".format(self.type_info)) def definition(self, postfix=None): """ Return prototype for the parameter. E.g. 'const char *foo' """ proto = "" if self.const: proto += self.const + " " proto += self.type if self.is_pointer(): proto += " {0}{1}".format(self.pointer, self.name) else: proto += " " + self.name # Allows appeninding something to the variable name useful for # win32 to host conversion. if postfix is not None: proto += postfix if self.is_static_array(): proto += "[{0}]".format(self.array_len) return proto def direction(self): """ Returns parameter direction: input, output, input_output. Parameter direction in Vulkan is not straight-forward, which this function determines. """ return self._direction def format_string(self): return self.format_str def is_const(self): return self.const is not None def is_dynamic_array(self): return self.dyn_array_len is not None def is_dispatchable(self): if not self.is_handle(): return False return self.handle.is_dispatchable() def is_handle(self): return self.handle is not None def is_pointer(self): return self.pointer is not None def is_static_array(self): return self.array_len is not None def is_struct(self): return self.struct is not None class VkStruct(Sequence): """ Class which represents the type union and struct. """ def __init__(self, name, members, returnedonly, union=False): self.name = name self.members = members self.returnedonly = returnedonly self.required = False self.union = union self.type_info = None # To be set later. def __getitem__(self, i): return self.members[i] def __len__(self): return len(self.members) @staticmethod def from_xml(struct): # Unions and structs are the same parsing wise, but we need to # know which one we are dealing with later on for code generation. union = True if struct.attrib["category"] == "union" else False name = struct.attrib.get("name", None) # 'Output' structures for which data is filled in by the API are # marked as 'returnedonly'. returnedonly = True if struct.attrib.get("returnedonly") else False members = [] for member in struct.findall("member"): vk_member = VkMember.from_xml(member) members.append(vk_member) return VkStruct(name, members, returnedonly, union=union) @staticmethod def decouple_structs(structs): """ Helper function which decouples a list of structs. Structures often depend on other structures. To make the C compiler happy we need to define 'substructures' first. This function analyzes the list of structures and reorders them in such a way that they are decoupled. """ tmp_structs = list(structs) # Don't modify the original structures. decoupled_structs = [] while (len(tmp_structs) > 0): for struct in tmp_structs: dependends = False if not struct.required: tmp_structs.remove(struct) continue for m in struct: if not (m.is_struct() or m.is_union()): continue found = False # Check if a struct we depend on has already been defined. for s in decoupled_structs: if s.name == m.type: found = True break if not found: # Check if the struct we depend on is even in the list of structs. # If found now, it means we haven't met all dependencies before we # can operate on the current struct. # When generating 'host' structs we may not be able to find a struct # as the list would only contain the structs requiring conversion. for s in tmp_structs: if s.name == m.type: dependends = True break if dependends == False: decoupled_structs.append(struct) tmp_structs.remove(struct) return decoupled_structs def definition(self, align=False, conv=False, postfix=None): """ Convert structure to textual definition. Args: align (bool, optional): enable alignment to 64-bit for win32 struct compatibility. conv (bool, optional): enable struct conversion if the struct needs it. postfix (str, optional): text to append to end of struct name, useful for struct renaming. """ if self.union: text = "typedef union {0}".format(self.name) else: text = "typedef struct {0}".format(self.name) if postfix is not None: text += postfix text += "\n{\n" for m in self: if align and m.needs_alignment(): text += " {0};\n".format(m.definition(align=align)) elif conv and m.needs_conversion(): text += " {0};\n".format(m.definition(conv=conv)) else: text += " {0};\n".format(m.definition()) if postfix is not None: text += "}} {0}{1};\n\n".format(self.name, postfix) else: text += "}} {0};\n\n".format(self.name) return text 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 m.needs_alignment(): return True return False def set_type_info(self, types): """ Helper function to set type information from the type registry. This is needed, because not all type data is available at time of parsing. """ for m in self.members: type_info = types[m.type] m.set_type_info(type_info) class VkGenerator(object): def __init__(self, registry): self.registry = registry def generate_thunks_c(self, f, prefix): f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n") f.write("#include \"config.h\"\n") f.write("#include \"wine/port.h\"\n\n") f.write("#include \"wine/debug.h\"\n") f.write("#include \"wine/vulkan.h\"\n") f.write("#include \"wine/vulkan_driver.h\"\n") f.write("#include \"vulkan_private.h\"\n\n") f.write("WINE_DEFAULT_DEBUG_CHANNEL(vulkan);\n\n") # Create thunks for instance functions. # Global functions don't go through the thunks. for vk_func in self.registry.funcs.values(): if not vk_func.is_required(): continue if vk_func.is_global_func(): continue # We don't support device functions yet as other plumbing # is needed first. if vk_func.is_device_func(): continue if not vk_func.needs_thunk(): continue f.write("static " + vk_func.stub(prefix=prefix, call_conv="WINAPI")) f.write("static const struct vulkan_func vk_instance_dispatch_table[] =\n{\n") for vk_func in self.registry.instance_funcs: if not vk_func.is_required(): continue if not vk_func.needs_dispatch(): LOGGER.debug("skipping {0} in instance dispatch table".format(vk_func.name)) continue f.write(" {{\"{0}\", &{1}{0}}},\n".format(vk_func.name, prefix)) 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 pName=%s in instance table\\n\", 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_thunks_h(self, f, prefix): f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n") f.write("#ifndef __WINE_VULKAN_THUNKS_H\n") f.write("#define __WINE_VULKAN_THUNKS_H\n\n") f.write("/* For use by vk_icdGetInstanceProcAddr / vkGetInstanceProcAddr */\n") f.write("void *wine_vk_get_instance_proc_addr(const char *name) DECLSPEC_HIDDEN;\n\n") # Generate prototypes for device and instance functions requiring a custom implementation. f.write("/* Functions for which we have custom implementations outside of the thunks. */\n") for vk_func in self.registry.funcs.values(): if not vk_func.is_required() or vk_func.is_global_func() or vk_func.needs_thunk(): continue f.write("{0};\n".format(vk_func.prototype("WINAPI", prefix="wine_", postfix="DECLSPEC_HIDDEN"))) f.write("\n") f.write("/* For use by vkInstance and children */\n") f.write("struct vulkan_instance_funcs\n{\n") for vk_func in self.registry.instance_funcs: if not vk_func.is_required(): continue if not vk_func.needs_dispatch() or vk_func.is_driver_func(): LOGGER.debug("skipping {0} in vulkan_instance_funcs".format(vk_func.name)) continue f.write(" {0};\n".format(vk_func.pfn(conv=False))) f.write("};\n\n") f.write("#define ALL_VK_INSTANCE_FUNCS() \\\n") first = True for vk_func in self.registry.instance_funcs: if not vk_func.is_required(): continue if not vk_func.needs_dispatch() or vk_func.is_driver_func(): LOGGER.debug("skipping {0} in ALL_VK_INSTANCE_FUNCS".format(vk_func.name)) continue if first: f.write(" USE_VK_FUNC({0})".format(vk_func.name)) first = False else: f.write("\\\n USE_VK_FUNC({0})".format(vk_func.name)) f.write("\n\n") f.write("#endif /* __WINE_VULKAN_THUNKS_H */\n") def generate_vulkan_h(self, f): f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n") f.write("#ifndef __WINE_VULKAN_H\n") f.write("#define __WINE_VULKAN_H\n\n") f.write("#include \n") f.write("#include \n\n") f.write("#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("/* Callers can override WINE_VK_ALIGN if they want 'host' headers. */\n") f.write("#ifndef WINE_VK_ALIGN\n") f.write("#define WINE_VK_ALIGN DECLSPEC_ALIGN\n") f.write("#endif\n\n") # The overall strategy is to define independent constants and datatypes, # prior to complex structures and function calls to avoid forward declarations. for const in self.registry.consts: # For now just generate things we may not need. The amount of parsing needed # to get some of the info is tricky as you need to figure out which structure # references a certain constant. f.write(const.definition()) f.write("\n") for define in self.registry.defines: f.write(define.definition()) for handle in self.registry.handles: if handle.required: f.write(handle.definition()) f.write("\n") for base_type in self.registry.base_types: f.write(base_type.definition()) f.write("\n") for bitmask in self.registry.bitmasks: f.write(bitmask.definition()) f.write("\n") # Define enums, this includes values for some of the bitmask types as well. for enum in self.registry.enums.values(): if enum.required: f.write(enum.definition()) for fp in self.registry.funcpointers: if fp.required: f.write(fp.definition()) f.write("\n") # This generates both structures and unions. Since structures # may depend on other structures/unions, we need a list of # decoupled structs. # Note: unions are stored in structs for dependency reasons, # see comment in parsing section. structs = VkStruct.decouple_structs(self.registry.structs) for struct in structs: LOGGER.debug("Generating struct: {0}".format(struct.name)) f.write(struct.definition(align=True)) for func in self.registry.funcs.values(): if not func.is_required(): LOGGER.debug("Skipping API definition for: {0}".format(func.name)) continue LOGGER.debug("Generating API definition for: {0}".format(func.name)) f.write("{0};\n".format(func.prototype(call_conv="VKAPI_CALL"))) f.write("\n") f.write("#endif /* __WINE_VULKAN_H */\n") def generate_vulkan_driver_h(self, f): f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n") f.write("#ifndef __WINE_VULKAN_DRIVER_H\n") f.write("#define __WINE_VULKAN_DRIVER_H\n\n") f.write("/* Wine internal vulkan driver version, needs to be bumped upon vulkan_funcs changes. */\n") f.write("#define WINE_VULKAN_DRIVER_VERSION {0}\n\n".format(DRIVER_VERSION)) f.write("struct vulkan_funcs\n{\n") f.write(" /* Vulkan global functions. These are the only calls at this point a graphics driver\n") f.write(" * needs to provide. Other function calls will be provided indirectly by dispatch\n") f.write(" * tables part of dispatchable Vulkan objects such as VkInstance or vkDevice.\n") f.write(" */\n") for vk_func in self.registry.funcs.values(): if not vk_func.is_required() or not vk_func.is_driver_func(): continue pfn = vk_func.pfn() # Avoid PFN_vkVoidFunction in driver interface as Vulkan likes to put calling convention # stuff in there. For simplicity substitute with "void *". pfn = pfn.replace("PFN_vkVoidFunction", "void *") f.write(" {0};\n".format(pfn)) f.write("};\n\n") f.write("extern const struct vulkan_funcs * CDECL __wine_get_vulkan_driver(HDC hdc, UINT version);\n\n") f.write("#endif /* __WINE_VULKAN_DRIVER_H */\n") class VkRegistry(object): def __init__(self, reg_filename): # Used for storage of type information. self.base_types = None self.bitmasks = None self.consts = None self.defines = None self.enums = None self.funcpointers = None self.handles = None self.structs = None # We aggregate all types in here for cross-referencing. self.funcs = {} self.types = {} # Overall strategy for parsing the registry is to first # parse all type / function definitions. Then parse # features and extensions to decide which types / functions # to actually 'pull in' for code generation. For each type or # function call we want we set a member 'required' to True. tree = ET.parse(reg_filename) root = tree.getroot() self._parse_enums(root) self._parse_types(root) self._parse_commands(root) # Pull in any required types and functions. self._parse_features(root) def _mark_command_required(self, command): """ Helper function to mark a certain command and the datatypes it needs as required.""" def mark_bitmask_dependencies(bitmask, types): if bitmask.requires is not None: types[bitmask.requires]["data"].required = True def mark_funcpointer_dependencies(fp, types): for m in fp.members: type_info = types[m.type] # Complex types have a matching definition e.g. VkStruct. # Not needed for base types such as uint32_t. if "data" in type_info: types[m.type]["data"].required = True def mark_struct_dependencies(struct, types): for m in struct: type_info = types[m.type] # Complex types have a matching definition e.g. VkStruct. # Not needed for base types such as uint32_t. if "data" in type_info: types[m.type]["data"].required = True if type_info["category"] == "struct": # Yay, recurse mark_struct_dependencies(type_info["data"], types) elif type_info["category"] == "funcpointer": mark_funcpointer_dependencies(type_info["data"], types) elif type_info["category"] == "bitmask": mark_bitmask_dependencies(type_info["data"], types) func = self.funcs[command] func.required = True # Pull in return type if func.type != "void": self.types[func.type]["data"].required = True # Analyze parameter dependencies and pull in any type needed. for p in func.params: type_info = self.types[p.type] # Check if we are dealing with a complex type e.g. VkEnum, VkStruct and others. if "data" not in type_info: continue # Mark the complex type as required. type_info["data"].required = True if type_info["category"] == "struct": struct = type_info["data"] mark_struct_dependencies(struct, self.types) def _parse_commands(self, root): """ Parse command section containing the Vulkan function calls. """ funcs = {} commands = root.findall("./commands/") for command in commands: func = VkFunction.from_xml(command, self.types) funcs[func.name] = func # To make life easy for the code generation, separate all function # calls out in the 3 types of vulkan functions: device, global and instance. device_funcs = [] global_funcs = [] instance_funcs = [] for func in funcs.values(): if func.is_device_func(): device_funcs.append(func) elif func.is_global_func(): global_funcs.append(func) else: instance_funcs.append(func) # Sort function lists by name and store them. self.device_funcs = sorted(device_funcs, key=lambda func: func.name) self.global_funcs = sorted(global_funcs, key=lambda func: func.name) self.instance_funcs = sorted(instance_funcs, key=lambda func: func.name) # The funcs dictionary is used as a convenient way to lookup function # calls when needed e.g. to adjust member variables. self.funcs = OrderedDict(sorted(funcs.items())) def _parse_enums(self, root): """ Parse enums section or better described as constants section. """ enums = {} self.consts = [] for enum in root.findall("./enums"): name = enum.attrib.get("name") _type = enum.attrib.get("type") if _type in ("enum", "bitmask"): enums[name] = VkEnum.from_xml(enum) else: # If no type is set, we are dealing with API constants. values = [] for value in enum.findall("enum"): self.consts.append(VkConstant(value.attrib.get("name"), value.attrib.get("value"))) self.enums = OrderedDict(sorted(enums.items())) def _parse_features(self, root): """ Parse the feature section, which describes Core commands and types needed. """ requires = root.findall("./feature/require") for require in requires: LOGGER.info("Including features for {0}".format(require.attrib.get("comment"))) for tag in require: # Only deal with command. Other values which appear are enum and type for pulling in some # constants and macros. Tricky to parse, so don't bother right now, we will generate them # anyway for now. name = tag.attrib["name"] if tag.tag == "command": self._mark_command_required(name) elif tag.tag == "enum": # We could pull in relevant constants here. Unfortunately # this only gets half of them pulled in as others indirectly # get pulled in through structures. Constants don't harm us, # so don't bother. pass elif tag.tag == "type": # Pull in types which may not have been pulled in through commands. # Skip pull in for vk_platform.h for now. if name == "vk_platform": continue type_info = self.types[name] type_info["data"].required = True def _parse_types(self, root): """ Parse types section, which contains all data types e.g. structs, typedefs etcetera. """ types = root.findall("./types/type") base_types = [] bitmasks = [] defines = [] funcpointers = [] handles = [] structs = [] for t in types: type_info = {} type_info["category"] = t.attrib.get("category", None) if type_info["category"] in ["include"]: continue if type_info["category"] == "basetype": name = t.find("name").text _type = t.find("type").text basetype = VkBaseType(name, _type) base_types.append(basetype) type_info["data"] = basetype if type_info["category"] == "bitmask": name = t.find("name").text _type = t.find("type").text # Most bitmasks have a requires attribute used to pull in # required '*FlagBits" enum. requires = t.attrib.get("requires") bitmask = VkBaseType(name, _type, requires=requires) bitmasks.append(bitmask) type_info["data"] = bitmask if type_info["category"] == "define": name = t.attrib.get("name") define = VkDefine.from_xml(t) defines.append(define) type_info["data"] = define if type_info["category"] == "enum": name = t.attrib.get("name") # The type section only contains enum names, not the actual definition. # Since we already parsed the enum before, just link it in. try: type_info["data"] = self.enums[name] except KeyError as e: # Not all enums seem to be defined yet, typically that's for # ones ending in 'FlagBits' where future extensions may add # definitions. type_info["data"] = None if type_info["category"] == "funcpointer": funcpointer = VkFunctionPointer.from_xml(t) funcpointers.append(funcpointer) type_info["data"] = funcpointer if type_info["category"] == "handle": handle = VkHandle.from_xml(t) handles.append(handle) type_info["data"] = handle if type_info["category"] in ["struct", "union"]: # We store unions among structs as some structs depend # on unions. The types are very similar in parsing and # generation anyway. The official vulkan scripts use # a similar kind of hack. struct = VkStruct.from_xml(t) structs.append(struct) type_info["data"] = struct # Name is in general within a name tag else it is an optional # attribute on the type tag. name_elem = t.find("name") if name_elem is not None: type_info["name"] = name_elem.text else: type_info["name"] = t.attrib.get("name", None) # Store all type data in a shared dictionary, so we can easily # look up information for a given type. There are no duplicate # names. self.types[type_info["name"]] = type_info # We need detailed type information during code generation # on structs for alignment reasons. Unfortunately structs # are parsed among other types, so there is no guarantee # that any types needed have been parsed already, so set # the data now. for struct in structs: struct.set_type_info(self.types) # Guarantee everything is sorted, so code generation doesn't have # to deal with this. self.base_types = sorted(base_types, key=lambda base_type: base_type.name) self.bitmasks = sorted(bitmasks, key=lambda bitmask: bitmask.name) self.defines = defines self.funcpointers = sorted(funcpointers, key=lambda fp: fp.name) self.handles = sorted(handles, key=lambda handle: handle.name) self.structs = sorted(structs, key=lambda struct: struct.name) def main(): parser = argparse.ArgumentParser() parser.add_argument("-v", "--verbose", action="count", default=0, help="increase output verbosity") 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) registry = VkRegistry("vk.xml") generator = VkGenerator(registry) with open(WINE_VULKAN_H, "w") as f: generator.generate_vulkan_h(f) with open(WINE_VULKAN_DRIVER_H, "w") as f: generator.generate_vulkan_driver_h(f) with open(WINE_VULKAN_THUNKS_H, "w") as f: generator.generate_thunks_h(f, "wine_") with open(WINE_VULKAN_THUNKS_C, "w") as f: generator.generate_thunks_c(f, "wine_") if __name__ == "__main__": main()