1189 lines
43 KiB
Python
Executable File
1189 lines
43 KiB
Python
Executable File
#!/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"
|
|
|
|
|
|
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:
|
|
# <type category="define" name="some_name">some_value</type>
|
|
# 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:
|
|
# <type category="define"><name>some_name</name>some_value</type>
|
|
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
|
|
|
|
# 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_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 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
|
|
|
|
|
|
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:
|
|
# <type>void</type>* pUserData,
|
|
# Parsing of the tail (anything past </type>) is tricky since there
|
|
# can be other data on the next line like: const <type>int</type>..
|
|
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:
|
|
# <member><type>char</type><name>extensionName</name>[<enum>VK_MAX_EXTENSION_NAME_SIZE</enum>]</member>
|
|
# <member><type>uint32_t</type><name>foo</name>[4]</member>
|
|
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
|
|
|
|
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:
|
|
# <param>const <type>char</type>* <name>pLayerName</name></param>
|
|
|
|
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 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 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_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 <windef.h>\n")
|
|
f.write("#include <stdint.h>\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")
|
|
|
|
|
|
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)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|