From 751967619b1c18656bf2cc8f81f8943cd63236c2 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Mon, 11 Jun 2018 14:55:49 +1000 Subject: [PATCH] spec: rewrite header generator to use Khronos repo directly --- .gitmodules | 3 + CMakeLists.txt | 6 +- except.cpp | 6 +- fwd.hpp | 48 +-- ostream.cpp | 12 +- ostream.hpp | 2 +- specs | 1 + tools/spec.py | 929 +++++++++++++++++++++++++++---------------------- 8 files changed, 542 insertions(+), 465 deletions(-) create mode 100644 .gitmodules create mode 160000 specs mode change 100755 => 100644 tools/spec.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..461e355 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "specs"] + path = specs + url = https://github.com/KhronosGroup/Vulkan-Docs.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 975069b..06810b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,13 +27,15 @@ COMMENT COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/spec.py" - "${CMAKE_CURRENT_SOURCE_DIR}/vk.xml" + "${CMAKE_CURRENT_SOURCE_DIR}/specs/xml/vk.xml" "${CMAKE_CURRENT_BINARY_DIR}/vk.hpp" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tools/spec.py" - "${CMAKE_CURRENT_SOURCE_DIR}/vk.xml" + "${CMAKE_CURRENT_SOURCE_DIR}/specs/xml/vk.xml" ) +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/specs/include/vulkan") + ##----------------------------------------------------------------------------- list (APPEND sources diff --git a/except.cpp b/except.cpp index 2c8a8c3..5d9afba 100644 --- a/except.cpp +++ b/except.cpp @@ -53,7 +53,9 @@ using cruft::vk::error_code; VK_ERROR_INCOMPATIBLE_DISPLAY_KHR, \ VK_ERROR_OUT_OF_POOL_MEMORY_KHR, \ VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR, \ - VK_ERROR_INVALID_SHADER_NV) + VK_ERROR_INVALID_SHADER_NV, \ + VK_ERROR_FRAGMENTATION_EXT, \ + VK_ERROR_NOT_PERMITTED_EXT) /////////////////////////////////////////////////////////////////////////////// @@ -87,6 +89,8 @@ to_string (VkResult res) // EXT case VK_ERROR_VALIDATION_FAILED_EXT: return "validation failed"; + case VK_ERROR_FRAGMENTATION_EXT: return "too much fragmentation"; + case VK_ERROR_NOT_PERMITTED_EXT: return "not permitted"; // KHR case VK_ERROR_SURFACE_LOST_KHR: return "surface lost"; diff --git a/fwd.hpp b/fwd.hpp index 0e37d35..3bfe611 100644 --- a/fwd.hpp +++ b/fwd.hpp @@ -18,7 +18,7 @@ #ifndef CRUFT_VK_FWD_HPP #define CRUFT_VK_FWD_HPP -#include "./vk.hpp" +#include "vk.hpp" #include @@ -176,7 +176,6 @@ namespace cruft::vk { VkQueryResultFlagBits, \ VkSparseMemoryBindFlagBits, \ VkValidationCacheHeaderVersionEXT, \ - VkDeviceGroupPresentModeFlagBitsKHX, \ VkColorSpaceKHR, \ VkPresentModeKHR, \ VkCommandPoolResetFlagBits, \ @@ -199,10 +198,8 @@ namespace cruft::vk { VkFenceCreateFlagBits, \ VkDisplayEventTypeEXT, \ VkDescriptorPoolCreateFlagBits, \ - VkPeerMemoryFeatureFlagBitsKHX, \ VkCoverageModulationModeNV, \ VkObjectType, \ - VkMemoryAllocateFlagBitsKHX, \ VkPipelineCacheHeaderVersion, \ VkDiscardRectangleModeEXT, \ VkDeviceEventTypeEXT, \ @@ -220,7 +217,6 @@ namespace cruft::vk { VkExtent3D, \ VkViewport, \ VkRect2D, \ - VkRect3D, \ VkClearRect, \ VkComponentMapping, \ VkPhysicalDeviceProperties, \ @@ -329,12 +325,6 @@ namespace cruft::vk { VkDisplaySurfaceCreateInfoKHR, \ VkDisplayPresentInfoKHR, \ VkSurfaceCapabilitiesKHR, \ - VkAndroidSurfaceCreateInfoKHR, \ - VkMirSurfaceCreateInfoKHR, \ - VkViSurfaceCreateInfoNN, \ - VkWaylandSurfaceCreateInfoKHR, \ - VkWin32SurfaceCreateInfoKHR, \ - VkXlibSurfaceCreateInfoKHR, \ VkXcbSurfaceCreateInfoKHR, \ VkSurfaceFormatKHR, \ VkSwapchainCreateInfoKHR, \ @@ -351,9 +341,6 @@ namespace cruft::vk { VkExternalImageFormatPropertiesNV, \ VkExternalMemoryImageCreateInfoNV, \ VkExportMemoryAllocateInfoNV, \ - VkImportMemoryWin32HandleInfoNV, \ - VkExportMemoryWin32HandleInfoNV, \ - VkWin32KeyedMutexAcquireReleaseInfoNV, \ VkDeviceGeneratedCommandsFeaturesNVX, \ VkDeviceGeneratedCommandsLimitsNVX, \ VkIndirectCommandsTokenNVX, \ @@ -391,55 +378,24 @@ namespace cruft::vk { VkExternalMemoryImageCreateInfoKHR, \ VkExternalMemoryBufferCreateInfoKHR, \ VkExportMemoryAllocateInfoKHR, \ - VkImportMemoryWin32HandleInfoKHR, \ - VkExportMemoryWin32HandleInfoKHR, \ - VkMemoryWin32HandlePropertiesKHR, \ - VkMemoryGetWin32HandleInfoKHR, \ VkImportMemoryFdInfoKHR, \ VkMemoryFdPropertiesKHR, \ VkMemoryGetFdInfoKHR, \ - VkWin32KeyedMutexAcquireReleaseInfoKHR, \ VkPhysicalDeviceExternalSemaphoreInfoKHR, \ VkExternalSemaphorePropertiesKHR, \ VkExportSemaphoreCreateInfoKHR, \ - VkImportSemaphoreWin32HandleInfoKHR, \ - VkExportSemaphoreWin32HandleInfoKHR, \ - VkD3D12FenceSubmitInfoKHR, \ - VkSemaphoreGetWin32HandleInfoKHR, \ VkImportSemaphoreFdInfoKHR, \ VkSemaphoreGetFdInfoKHR, \ VkPhysicalDeviceExternalFenceInfoKHR, \ VkExternalFencePropertiesKHR, \ VkExportFenceCreateInfoKHR, \ - VkImportFenceWin32HandleInfoKHR, \ - VkExportFenceWin32HandleInfoKHR, \ - VkFenceGetWin32HandleInfoKHR, \ VkImportFenceFdInfoKHR, \ VkFenceGetFdInfoKHR, \ - VkPhysicalDeviceMultiviewFeaturesKHX, \ - VkPhysicalDeviceMultiviewPropertiesKHX, \ - VkRenderPassMultiviewCreateInfoKHX, \ VkSurfaceCapabilities2EXT, \ VkDisplayPowerInfoEXT, \ VkDeviceEventInfoEXT, \ VkDisplayEventInfoEXT, \ VkSwapchainCounterCreateInfoEXT, \ - VkPhysicalDeviceGroupPropertiesKHX, \ - VkMemoryAllocateFlagsInfoKHX, \ - VkBindBufferMemoryInfoKHX, \ - VkBindImageMemoryInfoKHX, \ - VkDeviceGroupRenderPassBeginInfoKHX, \ - VkDeviceGroupCommandBufferBeginInfoKHX, \ - VkDeviceGroupSubmitInfoKHX, \ - VkDeviceGroupBindSparseInfoKHX, \ - VkDeviceGroupPresentCapabilitiesKHX, \ - VkImageSwapchainCreateInfoKHX, \ - VkBindImageMemorySwapchainInfoKHX, \ - VkAcquireNextImageInfoKHX, \ - VkDeviceGroupPresentInfoKHX, \ - VkDeviceGroupDeviceCreateInfoKHX, \ - VkDeviceGroupSwapchainCreateInfoKHX, \ - VkDescriptorUpdateTemplateEntryKHR, \ VkDescriptorUpdateTemplateCreateInfoKHR, \ VkXYColorEXT, \ VkHdrMetadataEXT, \ @@ -447,8 +403,6 @@ namespace cruft::vk { VkPastPresentationTimingGOOGLE, \ VkPresentTimesInfoGOOGLE, \ VkPresentTimeGOOGLE, \ - VkIOSSurfaceCreateInfoMVK, \ - VkMacOSSurfaceCreateInfoMVK, \ VkViewportWScalingNV, \ VkPipelineViewportWScalingStateCreateInfoNV, \ VkViewportSwizzleNV, \ diff --git a/ostream.cpp b/ostream.cpp index a623fed..879deef 100644 --- a/ostream.cpp +++ b/ostream.cpp @@ -15,13 +15,15 @@ * 2017, Danny Robson */ -#include "./ostream.hpp" +#include "ostream.hpp" -#include "./instance.hpp" -#include "./physical_device.hpp" +#include "instance.hpp" +#include "physical_device.hpp" + +#include #include -#include +#include /////////////////////////////////////////////////////////////////////////////// @@ -40,6 +42,7 @@ operator<< (std::ostream &os, VkExtent3D val) } +#if 0 //----------------------------------------------------------------------------- std::ostream& operator<< (std::ostream &os, VkQueueFlags val) @@ -54,6 +57,7 @@ operator<< (std::ostream &os, VkQueueFlags val) return os << "[ " << util::make_infix (util::make_view (names, names + cursor)) << " ]"; } +#endif /////////////////////////////////////////////////////////////////////////////// diff --git a/ostream.hpp b/ostream.hpp index f102d54..3b56a3d 100644 --- a/ostream.hpp +++ b/ostream.hpp @@ -27,7 +27,7 @@ /////////////////////////////////////////////////////////////////////////////// -std::ostream& operator<< (std::ostream&, VkQueueFlags); +//std::ostream& operator<< (std::ostream&, VkQueueFlags); std::ostream& operator<< (std::ostream&, VkExtent2D); std::ostream& operator<< (std::ostream&, VkExtent3D); std::ostream& operator<< (std::ostream&, VkPhysicalDeviceType); diff --git a/specs b/specs new file mode 160000 index 0000000..ee13fc3 --- /dev/null +++ b/specs @@ -0,0 +1 @@ +Subproject commit ee13fc355fae06ffcd36ee09a98f12132d95dfc0 diff --git a/tools/spec.py b/tools/spec.py old mode 100755 new mode 100644 index c99c65e..00a3309 --- a/tools/spec.py +++ b/tools/spec.py @@ -1,282 +1,299 @@ #!/usr/bin/env python3 +import sys import logging +from typing import List, Dict, Set import xml.etree.ElementTree as ET import re + ############################################################################### -def camel_to_snake(name): - name = re.sub('([a-z])([A-Z])', r'\1_\2', name) - return name.lower() - - -def remove_namespace(name): - name = re.sub('^VK_', '', name) - name = re.sub('^[vV][kK]', '', name) +def rename(name:str): return name ############################################################################### -def rename(name): - return name - name = remove_namespace(name) - name = camel_to_snake(name) - return name +class registry: + def __init__(self): + self.types = {} + self.extensions = {} + self.features = {} + self.types['API Constants'] = unscoped('API Constants') -##----------------------------------------------------------------------------- -def rename_enum(type, value): - return value - value = rename(value) - value = re.sub("^%s_" % type, '', value) - return value + self.applied = set() + + def _serialise(self, name:str, queued:Set[str]): + if name in queued: + return [] + + result = [] + obj = self.types[name] + + for d in obj.depends: + if d == name: + continue + result += self._serialise(d, queued) + + assert name not in queued + queued.add(name) + result += [obj] + + return result + + def serialise(self, platform:str): + required = [] + + for (_,f) in self.features.items(): + required += f.apply(reg) + required.append(f.name) + + for e in self.extensions: + required += self.extensions[e].apply(self, platform) + + queued = set() + result = [] + + for r in required: + result += self._serialise(r, queued) + + return result ############################################################################### class type(object): - def __init__(self, name): + """ + The base class for all object defined in the Vulkan API. + + This includes (but is not limited to) types, like structures; and values, + like constants. + """ + def __init__(self, name:str, depends:List[str] = []): self.name = name + self.depends = [] + depends def depends(self): - return [] + return self.depends def declare(self): return "" - def define(self,types): + def define(self,reg): return "" -##----------------------------------------------------------------------------- -class basetype(type): - def __init__(self, node): - assert(node.tag == 'type') - assert(node.attrib['category'] == 'basetype') - - super().__init__(node.find('name').text) - self._type = node.find('type').text - - def depends(self): - return [self._type] - - def define(self, types): - return "using %(name)s = %(type)s;" % { - 'name': rename(self.name), - 'type': self._type - } - - -##----------------------------------------------------------------------------- -class handle(type): - def __init__(self, node): - assert (node.tag == "type") - assert (node.attrib['category'] == 'handle') - - super().__init__(node.find('name').text) +############################################################################### +class aliastype(type): + def __init__(self, name:str, target:str): + super().__init__(name, depends=[target]) + self.target = target def declare(self): - return "using %(name)s = struct _%(name)s*;" % { - 'name': rename(self.name) + return "using %(name)s = %(target)s;" % { + "name": self.name, + "target": self.target + } + + +class aliasvalue(type): + def __init__(self, name:str, target:str): + super().__init__(name, depends=[target]) + self.target = target + self.value = target + + def declare(self): + return "constexpr auto %(name)s = %(target)s;" % { + "name": self.name, + "target": self.target } ##----------------------------------------------------------------------------- -class constant(type): - def __init__(self,node): - assert(node.tag == 'enum') +class placeholder(type): + def __init__(self, name:str): + super().__init__(name) + + + +##----------------------------------------------------------------------------- +class unscoped(type): + def __init__(self, name:str): + super().__init__(name) + self.values = [] + + def declare(self): + return "\n".join(t.declare() for t in self.values) + + + def define(self,reg): + return "\n".join(t.define(reg.types) for t in self.values) + + + +############################################################################### +class include(type): + def __init__(self, node): + assert node.tag == 'type' + assert node.attrib['category'] == 'include' super().__init__(node.attrib['name']) - self._value = node.attrib['value'] - - def define(self, types): - return "constexpr auto %(name)s = %(value)s;" % { - 'name': self.name, - 'value': self._value - } - - -##----------------------------------------------------------------------------- -# a high level 'bitmask' enum that doesn't necessarily define anything. -# inherits the values from something else instead. super fucking weird.. -class bitmask(type): - def __init__(self, node): - assert(node.tag == 'type') - assert(node.attrib['category'] == 'bitmask') - - super().__init__(node.find('name').text) - - self._requires = node.attrib.get('requires', None) - self._type = node.find('type').text - - def depends(self): - if self._requires: - return [self._type, self._requires] - else: - return [self._type] + self.directive = node.text def declare(self): - return "" - - def define(self, types): - if not self._requires: - return "using %(name)s = %(type)s;" % { - 'name': rename(self.name), - 'type': rename(self._type) - } - - return "using %(name)s = %(requires)s;" % { - 'name': rename(self.name), - 'requires': rename(self._requires) - } - - members = types[self._requires].values - - return "enum class %(name)s : %(type)s { %(members)s };" % { - 'name': rename(self.name), - 'type': rename(self._type), - 'members': "\n".join("%(name)s = %(value)s," % x for x in members) - } + return self.directive or "#include <%s>" % self.name -##----------------------------------------------------------------------------- -# an enum with bit values that will be used by a bitmask enum. weirdly... -class bitflag(type): +class define(type): def __init__(self, node): - assert(node.tag == "enums") - assert(node.attrib['type'] == 'bitmask') + assert node.tag == 'type' + assert node.attrib['category'] == 'define' - name = node.attrib['name'] - super ().__init__(name) - - self._depends = node.attrib.get('requires', None) - - self.values = [] - for v in node.findall("./enum"): - if 'value' in v.attrib: - self.add(v.attrib['name'], v.attrib['value']) - elif 'bitpos' in v.attrib: - self.add(v.attrib['name'], '1 << %s' % v.attrib['bitpos']) - else: - assert False, "unhandled bitmask type" - - def add(self, name, value=None): - self.values.append({'name': name, 'value': value}) - - def values(self): - return self.values - - def depends(self): - if self._depends: - return ['VkFlags',self._depends] - else: - return ['VkFlags'] - - def declare(self): - return "" - - def define(self,types): - values = [] - for v in self.values: - if 'value' in v: - values.append( - "%(name)s = %(value)s" % { - 'name': rename(v['name']), - 'value': v['value'] - } - ) - else: - values.append(rename(v['name'])) - - return """ - enum %(name)s : %(vkflags)s { %(members)s }; - """ % { - 'name': rename(self.name), - 'vkflags': rename('VkFlags'), - 'members': ", ".join(values) - } - - -##----------------------------------------------------------------------------- -class enum(type): - def __init__(self, node): - assert(node.tag == "enums") - - name = node.attrib['name'] + name = node.attrib.get('name') or node.find('name').text super().__init__(name) - self.values = list(map( - lambda x: { - 'name' : x.attrib['name'], - 'value': x.attrib['value'] - }, - node.findall('./enum') - )) - - def add(self,name,value=None): - self.values.append({'name': name, 'value': value}) + self.directive = "".join(node.itertext()) def declare(self): - return "" - - def define(self,types): - values = [] - for v in self.values: - if v['value']: - values.append( - "%(name)s = %(value)s" % { - 'name': rename(v['name']), - 'value': v['value'] - } - ) - else: - values.append(rename(v['name'])) - - attribute = '[[nodiscard]]' if self.name == 'VkResult' else '' - - return "enum %(attribute)s %(name)s : int32_t { %(values)s };" % ({ - 'name': rename(self.name), - 'attribute': attribute, - 'values': ", ".join(values), - }) + return self.directive -##----------------------------------------------------------------------------- -class funcpointer(type): - def __init__(self, node): - assert(node.tag == 'type') - assert(node.attrib['category'] == "funcpointer") +class bitmask(type): + def __init__(self,node): + assert node.tag == 'type' + assert node.attrib['category'] == 'bitmask' name = node.find('name').text - super().__init__(name) + type = node.find('type').text + + super().__init__(name,depends=[type]) + + self.type = type + self.requires = node.attrib.get('requires') + if self.requires: + self.depends.append(self.requires) + + def declare(self): + return "using %(name)s = %(type)s;" % { + "name": self.name, + "type": self.type + } + + def define(self, reg:registry): + return self.declare(); + + if not self.requires: + return self.declare() + + return "using %(name)s = %(requires)s;" % { + "name": self.name, + "requires": self.requires + } + + source = reg.types[self.requires] + members = ["%(k)s = %(v)s" % {"k":k, "v":v.value} for (k,v) in source.values.items()] + + return """enum %(name)s : %(type)s { + %(members)s + }""" % { + "name": self.name, + "type": self.type, + "members": ",\n".join(members) + } + + +class handle(type): + def __init__(self, node): + assert node.tag == 'type' + assert node.attrib['category'] == 'handle' + + name = node.find('name').text + type = node.find('type').text + + super().__init__(name, depends=[type]) + self.type = type + + def declare(self): + return "struct %(name)s_t; using %(name)s = %(name)s_t*;" % { + "name": self.name, + "type": self.type + } + + +class enum(type): + def __init__(self,node): + assert node.tag == 'type' + assert node.attrib['category'] == 'enum' + + name = node.attrib['name'] + super().__init__(name,depends=["VkEnum"]) + + self.values = {} + + def __setitem__(self, key:str, value): + assert isinstance(value, constant) or isinstance(value, aliasvalue) + self.values[key] = value + + def declare(self): + return "" + return "enum %(name)s : int32_t;" % { + "name": self.name + } + + def define(self,reg:registry): + values = ("%(name)s = %(value)s" % { "name": k, "value": v.value } for (k,v) in self.values.items()) + + return "enum %(name)s : int32_t { %(values)s };" % { + "name": self.name, + "values": ", ".join(values) + } + + +class basetype(aliastype): + """ + Represents fundamental types that aliases of system provided types and used + extensively by the base API. eg, VkBool32 + """ + + def __init__(self, node): + assert node.tag == 'type' + assert node.attrib['category'] == 'basetype' + + super().__init__( + node.find('name').text, + node.find('type').text + ) + + +class funcpointer(type): + def __init__(self,node): + assert node.tag == 'type' + assert node.attrib['category'] == 'funcpointer' + + name = node.find('name').text + self.params = list(map(lambda x: x.text, node.findall('./type'))) self.text = "".join(node.itertext()) - def depends(self): - return ['VkBool32'] + self.params + super().__init__(name, depends=['VkBool32']+self.params) def declare(self): return self.text - def define(self,types): - return ""; - -##----------------------------------------------------------------------------- class pod(type): def __init__(self,node): - assert(node.tag == 'type') - assert(node.attrib['category'] in ['struct', 'union']) + assert node.tag == 'type' + assert node.attrib['category'] in ['struct', 'union'] super().__init__(node.attrib['name']) self._node = node self._category = node.attrib['category'] # sometimes there are enums hiding in the member fields being used as array sizes - self._depends = [] - self._depends += list(e.text for e in node.findall('.//enum')) - self._depends += list(t.text for t in node.findall('.//type')) + self.depends += list(e.text for e in node.findall('.//enum')) + self.depends += list(t.text for t in node.findall('.//type')) self._members = [] @@ -297,16 +314,13 @@ class pod(type): self._members.append({'code': code, 'type': type, 'name': name}) - def depends(self): - return self._depends - def declare(self): return "%(category)s %(name)s;" % { 'category': self._category, 'name': rename(self.name) } - def define(self,types): + def define(self,reg:registry): return "%(category)s %(name)s {\n%(members)s\n};" % { 'category': self._category, 'name': rename(self.name), @@ -314,263 +328,326 @@ class pod(type): } -##----------------------------------------------------------------------------- class struct(pod): def __init__(self,node): super().__init__(node) -##----------------------------------------------------------------------------- class union(pod): def __init__(self,node): super().__init__(node) -############################################################################### -def parse_types(nodes): - types = [] +class constant(type): + def __init__(self,node,**kwargs): + assert node.tag == 'enum' - for n in nodes: - # we need to parse too much unstructured text to actually do anything - # useful with these declarations - for t in n.findall("./type[@category='handle']"): - types.append(handle(t)) + name = node.attrib['name'] + super().__init__(name) - for t in n.findall('./type[@category="bitmask"]'): - types.append(bitmask(t)) + if 'offset' in node.attrib: + assert 'extends' in node.attrib - for t in n.findall('./type[@category="basetype"]'): - types.append(basetype(t)) + number = int(kwargs['extnumber']) + offset = int(node.attrib['offset']) + self.value = 1000000000 + 1000 * number + offset - for t in n.findall("./type[@category='funcpointer']"): - types.append(funcpointer(t)) - - for t in n.findall("./type[@category='union']"): - types.append(union(t)) - - for t in n.findall("./type[@category='struct']"): - types.append(struct(t)) - - return types - - -############################################################################### -def parse_enums(nodes): - enums = [] - - for n in nodes: - if n.attrib['name'] == "API Constants": - for c in n.findall('./enum'): - enums.append(constant(c)) - elif n.attrib['type'] == 'bitmask': - enums.append(bitflag(n)) - elif n.attrib['type'] == 'enum': - enums.append(enum(n)) + if 'dir' in node.attrib: + self.value *= -1 + elif 'value' in node.attrib: + self.value = node.attrib['value'] + elif 'bitpos' in node.attrib: + self.value = "1 << %s" % node.attrib['bitpos'] else: - assert False, "unhandled enum type" + raise "Unknown constant value type" - return enums + def declare(self): + return "constexpr auto %(name)s = %(value)s;" % { + "name": self.name, + "value": self.value + } -############################################################################### class command(type): def __init__(self, node): - assert(node.tag == "command") + assert node.tag == "command" proto = node.find('proto') - super().__init__(proto.find('name').text) + name = proto.find('name').text + super().__init__(name) - self._result = proto.find('type').text - self._params = [] - self._depends = [] + self.result = proto.find('type').text + self.depends += [self.result] + + self.params = [] for p in node.findall('./param'): - self._depends.append(p.find('type').text) - self._params.append("".join(p.itertext())) + self.depends.append(p.find('type').text) + self.params.append("".join(p.itertext())) - def depends(self): - return [self._result] + self._depends + pass def declare(self): return "%(result)s %(name)s (%(params)s) noexcept;" % { 'name': rename(self.name), - 'result': self._result, - 'params': ", ".join(self._params) + 'result': self.result, + 'params': ", ".join(self.params) } -##----------------------------------------------------------------------------- -def parse_commands(nodes): - commands = [] +class require(object): + def __init__(self, root): + self.values = [] + self.depends = [] - for n in nodes: - for c in n.findall('./command'): - commands.append(command(c)) - - return commands - - -############################################################################### -def parse_extension(types, node): - number = int(node.attrib['number']) - 1 - assert(number >= 0) - r = node.find('require') - - for enum in r.findall('./enum'): - if 'extends' in enum.attrib: - target = types[enum.attrib['extends']] - name = enum.attrib['name'] - if 'offset' in enum.attrib: - offset = int(enum.attrib['offset']) - value = 1000000000 + 1000 * number + offset - if 'dir' in enum.attrib: - value *= -1 - target.add(name, "%i" % value) - elif 'bitpos' in enum.attrib: - target.add(name, '1 << %s' % (enum.attrib['bitpos'])) - elif 'value' in enum.attrib: - target.add(name, enum.attrib['value']) + for node in root: + if node.tag == 'enum': + self.values.append(node) + elif node.tag in ['command', 'type']: + self.depends.append(node.attrib['name']) + elif node.tag in ['comment']: + pass else: - assert False, 'unhandled enum extension' - for command in r.findall('./command'): - if not command.attrib['name'] in types: - raise "unknown command" + raise "Unknown requires node" + + def apply(self,reg:registry,extnumber=None): + required = [] + required += self.depends + + for value in self.values: + name = value.attrib['name'] + + if len(value.attrib) == 1: + assert 'name' in value.attrib + required.append(name) + continue + + if not 'extends' in value.attrib: + obj = constant(value) + owner = reg.types['API Constants'] + owner.values.append(obj) + continue + + owner = reg.types[value.attrib['extends']] + + if 'alias' in value.attrib: + owner[name] = aliasvalue(name, value.attrib['alias']) + required.append(owner.name) + elif value.tag == 'enum': + owner[name] = constant(value,extnumber=extnumber or int(value.attrib.get('extnumber', '0'))) + required.append(owner.name) + elif value.tag == 'command': + required.append(name) + else: + raise "Unknown type" + + return required + + +class feature(type): + def __init__(self, root): + assert root.tag == 'feature' + + name = root.attrib['name'] + super().__init__(name) + + self.requires = [] + for node in root: + if 'require' == node.tag: + self.requires.append(require(node)) + else: + raise "Unhandled feature node" + + def define(self, reg:registry): + return "#define %s" % self.name + + def apply(self,reg:registry): + logging.info("Applying feature:", self.name, file=sys.stderr) + + result = [] + for r in self.requires: + result += r.apply(reg) + + return result + + +class extension(type): + def __init__(self, root): + assert root.tag == 'extension' + + name = root.attrib['name'] + super().__init__(name) + + if 'requires' in root.attrib: + self.depends += root.attrib['requires'].split(',') + + self.number = int(root.attrib['number']) + self.platform = root.attrib.get('platform') + + self.requires = [] + + for node in root: + if node.tag == 'require': + self.requires.append(require(node)) + else: + raise "Unknown extension node" + + def apply(self, reg:registry, platform:str): + if self.name in reg.applied: + return [] + reg.applied.add(self.name) + + if self.platform and self.platform != platform: + return [] + + required = [] + + for dep in self.depends: + required = reg.extensions[dep].apply (reg, platform) + + logging.info("Applying extension:", self.name, file=sys.stderr) + for node in self.requires: + required += node.apply(reg,extnumber=self.number) + return required + ############################################################################### -def write_type(dst, all, pending, t): - logging.info("writing: %s", t.name) +def ignore_node(types:Dict[str,type], root): + pass - for d in t.depends(): - if d in pending: - write_type(dst, all, pending, pending[d]) +parse_comment = ignore_node +parse_vendorids = ignore_node +parse_platforms = ignore_node +parse_tags = ignore_node - if t.name in pending: - dst.write(t.declare()) - dst.write('\n') - dst.write(t.define(all)) - dst.write('\n') - del pending[t.name] + +def parse_types(reg:registry, root): + assert root.tag == 'types' + + for t in root.findall('type'): + name = t.attrib.get ('name') or t.find('name').text + assert name not in reg.types + + if 'alias' in t.attrib: + name = t.attrib['name'] + target = t.attrib['alias'] + + reg.types[name] = aliastype(name, target) + continue + + category = t.attrib.get ('category') + + # if we don't have a category we should have a bare type that has a + # dependency on something like a header. + # + # eg, 'Display' depends on 'X11/Xlib.h' + if not category: + reg.types[name] = placeholder (name) + else: + # Whitelist the known types so we don't accidentally instantiate + # something whacky + + supported_categories = [ + 'include', + 'define', + 'bitmask', + 'basetype', + 'handle', + 'enum', + 'funcpointer', + 'struct', + 'union' + ] + + if category in supported_categories: + obj = globals()[category] (t) + reg.types[name] = obj + else: + raise 'unhandled type' + + if 'requires' in t.attrib: + reg.types[name].depends.append(t.attrib['requires']) ##----------------------------------------------------------------------------- -import copy -def write_types(dst, types): - all = types - pending = copy.deepcopy(types) - keys = list(types.keys()) +def parse_enums(reg:registry, root): + assert root.tag == 'enums' + ownername = root.attrib['name'] + owner = reg.types[ownername] if ownername != 'API Constants' else reg.types - for k in keys: - if k in types: - write_type(dst, all, pending, types[k]) + for node in root.findall('./enum'): + valuename = node.attrib.get('name') + + assert 'requires' not in node.attrib + + if 'alias' in node.attrib: + owner[valuename] = aliasvalue(valuename,node.attrib['alias']) + else: + owner[valuename] = constant(node) ##----------------------------------------------------------------------------- -def write_root(dst, node): - dst.write (""" - #ifndef __VK_HPP - #define __VK_HPP - #include +def parse_commands(reg:registry, root): + assert root.tag == 'commands' - template - struct enable_bitops : public std::false_type { }; + for node in root.findall('./command'): + name = node.attrib.get('name') or node.find('./proto/name').text + assert name not in reg.types - #include - #include + if 'alias' in node.attrib: + reg.types[name] = aliasvalue(name, node.attrib['alias']) + continue - #if defined(__cplusplus) - extern "C" { - #endif + reg.types[name] = command(node) - struct Display; - struct VisualID; - using Window = unsigned long; - struct ANativeWindow; +##----------------------------------------------------------------------------- +def parse_feature(reg:registry, root): + assert root.tag == 'feature' - struct MirConnection; - struct MirSurface; + name = node.attrib['name'] + assert name not in reg.features - struct wl_display; - struct wl_surface; + reg.features[name] = feature(root) + reg.types[name] = reg.features[name] - using HANDLE = void*; - using HINSTANCE = HANDLE; - using HWND = HANDLE; - using SECURITY_ATTRIBUTES = HANDLE; - using DWORD = uint32_t; - using LPCWSTR = char16_t*; +##----------------------------------------------------------------------------- +def parse_extensions(reg:registry, root): + assert root.tag == 'extensions' - struct xcb_connection_t; - struct xcb_visualid_t; - using xcb_window_t = uint32_t; - using XID = unsigned long; - using RROutput = XID; + for node in root.findall('./extension'): + name = node.attrib['name'] + assert name not in reg.extensions - #define VKAPI_PTR /*VKAPI_PTR*/ - - // because, in its wisdom, the spec doesn't actually allow us to - // extract the value for VK_NULL_HANDLE from the XML we'll just - // hard code it here. - #define VK_NULL_HANDLE nullptr - - // TODO: make this correspond to a required version - #define VK_VERSION_1_0 - """) - - types = [] - types += parse_types(node.findall('./types')) - types += parse_enums(node.findall('./enums')) - types += parse_commands(node.findall('./commands')) - - types = dict((t.name,t) for t in types) - - for n in node.findall('./extensions/extension'): - parse_extension(types, n) - - write_types(dst, types) - - #dst.writelines("\n".join(map(lambda x: x.declare(), types))) - #dst.writelines("\n".join(map(lambda x: x.define(), types))) - #dst.writelines("\n".join(map(lambda x: x.declare(), commands))) - - dst.write (""" - #if defined(__cplusplus) - } - #endif - """) - - for x in types.values(): - if isinstance(x,bitflag): - dst.write(""" - template <> - struct enable_bitops<%(name)s> : - public std::true_type - { }; - """ % { 'name': x.name } - ) - - dst.write(""" - template - std::enable_if_t::value, T> - operator| (T a, T b) - { - return T ( - static_cast> (a) | - static_cast> (b) - ); - } - - #endif - """) + reg.extensions[name] = extension(node) ############################################################################### +def enqueue_type(name:str, queued:Set[str], types:Dict[str,type]): + if name in queued: + return [] + + result = [] + obj = types[name] + + for d in obj.depends: + if d == name: + continue + result += enqueue_type(name=d, queued=queued, types=types) + + assert name not in queued + queued.add(name) + result += [obj] + + return result + + import argparse ##----------------------------------------------------------------------------- if __name__ == '__main__': - #logging.getLogger().setLevel(logging.INFO) + logging.getLogger().setLevel(logging.WARNING) parser = argparse.ArgumentParser(description='Transform XML API specification into C++ headers') parser.add_argument('src', type=str, help='the path to the XML file to transform') @@ -584,4 +661,36 @@ if __name__ == '__main__': tree = ET.parse(src) root = tree.getroot() - write_root(dst, root) + reg = registry() + + types = {} + + for node in root: + target = "parse_%s" % node.tag + globals()[target](reg, node) + + reg.types['void*'] = placeholder('void*') + reg.types['nullptr'] = placeholder('nullptr') + reg.types['VkEnum'] = aliastype('VkEnum', 'int32_t') + reg.types['VK_DEFINE_NON_DISPATCHABLE_HANDLE'] = aliastype("VK_DEFINE_NON_DISPATCHABLE_HANDLE", "uint64_t") + reg.types['VK_DEFINE_HANDLE'] = aliastype("VK_DEFINE_HANDLE", "void*") + reg.types['VK_NULL_HANDLE'] = aliasvalue("VK_NULL_HANDLE", "nullptr"); + + features = [feature(n) for n in root.findall('./feature')] + features = dict((f.name,f) for f in features) + + #reg.extensions['VK_KHR_surface'].apply(reg, platform='xcb') + extensions = ["VK_KHR_swapchain", "VK_EXT_debug_report", "VK_KHR_external_memory"] + q = reg.serialise(platform='xcb') + + + dst.write("#pragma once\n") + dst.write('extern "C" {\n') + for obj in q: + dst.write(obj.declare()) + dst.write('\n') + dst.write(obj.define(reg)) + dst.write('\n') + dst.write('}\n') + + #write_types(dst, types)