#!/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 },
"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()