diff --git a/except.hpp b/except.hpp index a66a3c3..604cdb3 100644 --- a/except.hpp +++ b/except.hpp @@ -49,10 +49,18 @@ namespace cruft::vk { >; if constexpr (returns_vkresult) { - try_code (func (maybe_native (args)...)); + try_code ( + std::invoke ( + std::forward (func), + maybe_native (std::forward (args))... + ) + ); return; } else { - return func (maybe_native (args)...); + return std::invoke ( + std::forward (func), + maybe_native (std::forward (args))... + ); } } @@ -117,7 +125,9 @@ namespace cruft::vk { } - //--------------------------------------------------------------------- + ///-------------------------------------------------------------------- + /// Safely calls a function that returns an array of Handle objects, + /// and returns a vector of wrapped objects. template < typename ReturnT, template class ContainerT = std::vector, diff --git a/icd/vendor.cpp b/icd/vendor.cpp index 6a3d9c7..6c57dae 100644 --- a/icd/vendor.cpp +++ b/icd/vendor.cpp @@ -1,49 +1,81 @@ -#include "vendor.hpp" - -#include - -using cruft::vk::icd::vendor; - - -/////////////////////////////////////////////////////////////////////////////// -template <> -cruft::vk::icd::icd_t -json::tree::io::deserialise (json::tree::node const &obj) -{ - return { - .file_format_version = obj["file_format_version"].as_string (), - .icd = { - .library_path = obj["ICD"]["library_path"].as_string ().native (), - .api_version = obj["ICD"]["api_version"].as_string (), - }, - }; -} - - -/////////////////////////////////////////////////////////////////////////////// -cruft::vk::icd::global_table const *cruft::vk::icd::g_table; - - - -/////////////////////////////////////////////////////////////////////////////// -vendor::vendor (icd_t const &_icd): - vendor (cruft::library (_icd.icd.library_path)) -{ ; } - - -//----------------------------------------------------------------------------- -vendor::vendor (::cruft::library &&_library) - : m_library (std::move (_library)) - , m_get_proc ( - m_library.symbol ("vk_icdGetInstanceProcAddr") - ) -{ - #define LOADFN(name) \ - vtable.name = reinterpret_cast< \ - decltype(vtable.name) \ - > ( \ - m_get_proc(nullptr, #name) \ - ); - - MAP_INSTANCE_COMMANDS (LOADFN) +#include "vendor.hpp" + +#include + +#include +#include + +using cruft::vk::icd::vendor; + + +#define MAP_ICD_COMMANDS(FUNC,...) MAP0(FUNC,\ + vk_icdNegotiateLoaderICDInterfaceVersion,\ + vk_icdGetInstanceProcAddr,\ + vk_icdGetPhysicalDeviceProcAddr) + + +/////////////////////////////////////////////////////////////////////////////// +template <> +cruft::vk::icd::icd_t +json::tree::io::deserialise (json::tree::node const &obj) +{ + return { + .file_format_version = obj["file_format_version"].as_string (), + .icd = { + .library_path = obj["ICD"]["library_path"].as_string ().native (), + .api_version = obj["ICD"]["api_version"].as_string (), + }, + }; +} + + +/////////////////////////////////////////////////////////////////////////////// +vendor::vendor (icd_t const &_icd): + vendor (cruft::library (_icd.icd.library_path)) +{ ; } + + +//----------------------------------------------------------------------------- +vendor::vendor (::cruft::library &&_library) + : m_library (std::move (_library)) +{ + // Negotiate needs to be called before anything else. But we load all the + // ICD calls at once for simplicity. + #define GET(NAME) vtable.NAME = m_library.symbol (#NAME); + MAP_ICD_COMMANDS (GET) + #undef GET + + version = 2; + switch (auto err = vtable.vk_icdNegotiateLoaderICDInterfaceVersion (&version); err) { + case VK_ERROR_INCOMPATIBLE_DRIVER: + static constexpr char incompatible_message[] = "Incompatible Vulkan ICD interface"; + LOG_ERROR ("%! %!", incompatible_message, version); + throw std::runtime_error (incompatible_message); + + default: + static constexpr char unknown_message[] = "Unknown Vulkan ICD interface response"; + LOG_ERROR ( + "%! %!", + unknown_message, + static_cast>(err) + ); + + throw std::runtime_error (unknown_message); + + case VK_SUCCESS: + LOG_INFO ("vk::icd version %!", version); + break; + } + + // Only load the instance table after we've queried all the icd functions. + itable = { + #define LOADFN(NAME) \ + .NAME = reinterpret_cast< \ + decltype(itable.NAME) \ + > ( \ + vtable.vk_icdGetInstanceProcAddr (nullptr, #NAME) \ + ), + + MAP_INSTANCE_COMMANDS (LOADFN) + }; } \ No newline at end of file diff --git a/icd/vendor.hpp b/icd/vendor.hpp index 1fc7c77..0e76883 100644 --- a/icd/vendor.hpp +++ b/icd/vendor.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -24,6 +25,12 @@ namespace cruft::vk::icd { enumerate (void); + struct vendor_table { + VkResult (*vk_icdNegotiateLoaderICDInterfaceVersion)(u32*) = nullptr; + void* (*vk_icdGetInstanceProcAddr) (VkInstance, char const*) = nullptr; + void* (*vk_icdGetPhysicalDeviceProcAddr) (VkInstance, char const*) = nullptr; + }; + class vendor { public: vendor (icd_t const&); @@ -33,9 +40,8 @@ namespace cruft::vk::icd { ::cruft::library m_library; public: - global_table vtable; - - using get_proc_t = void* (*)(VkInstance, char const*); - get_proc_t const m_get_proc; + vendor_table vtable; + instance_table itable; + u32 version = 0; }; } diff --git a/icd/vendor_posix.cpp b/icd/vendor_posix.cpp index 3f3b945..d99135c 100644 --- a/icd/vendor_posix.cpp +++ b/icd/vendor_posix.cpp @@ -41,6 +41,9 @@ cruft::vk::icd::enumerate (void) for (size_t i = 0; i < words.we_wordc; ++i) { try { for (auto const &path: fs::directory_iterator (words.we_wordv[i])) { + if (path.is_directory ()) + continue; + found.push_back (from_json (*json::tree::parse (path))); } } catch (std::exception const &e) { diff --git a/icd/vtable.cpp b/icd/vtable.cpp index 309b3a4..72f00a5 100644 --- a/icd/vtable.cpp +++ b/icd/vtable.cpp @@ -4,4 +4,5 @@ using cruft::vk::icd::instance_table; /////////////////////////////////////////////////////////////////////////////// - +cruft::vk::icd::vendor_table const *cruft::vk::icd::v_table = nullptr; +cruft::vk::icd::instance_table const *cruft::vk::icd::i_table = nullptr; diff --git a/object.hpp b/object.hpp index 04c1fed..cd09e44 100644 --- a/object.hpp +++ b/object.hpp @@ -90,16 +90,18 @@ namespace cruft::vk { /////////////////////////////////////////////////////////////////////////// - /// a vulkan object that is obtained by listings from a parent object. + /// A vulkan object that is obtained by listings from a parent object. template struct enumerated : public object { using object::object; + /// Returns a vector of available objects given a parent object. static std::vector find (const ParentT &parent) { return error::try_handles ( - enum_traits>::enumerate, parent.native () + enum_traits>::enumerate, + parent.native () ); } }; diff --git a/tools/info.cpp b/tools/info.cpp index fcade9a..7232657 100644 --- a/tools/info.cpp +++ b/tools/info.cpp @@ -30,7 +30,8 @@ main (int, char**) std::cout << "[ "; for (auto const &i: cruft::vk::icd::enumerate ()) { cruft::vk::icd::vendor v (i); - cruft::vk::icd::g_table = &v.vtable; + cruft::vk::icd::i_table = &v.itable; + cruft::vk::icd::v_table = &v.vtable; cruft::vk::instance instance; diff --git a/tools/spec.py b/tools/spec.py index 2e1954f..b201196 100644 --- a/tools/spec.py +++ b/tools/spec.py @@ -3,6 +3,7 @@ import sys import logging from typing import List, Dict, Set, TextIO +import pprint import xml.etree.ElementTree @@ -392,32 +393,27 @@ class Constant(Type): class Command(Type): class Param(Type): name: str - """An appropriate title for this parameter""" + """An string that can be used to refer to the parameter variable""" type: str """The name of this parameter's dependant type""" param: str """ The components of this type for a C definition (ie, includes - pointer, const, and other decorations + pointer, const, other decorations, _and_ the variable name (which must + be the same as self.name for a useful system). """ - def __init__(self, node, **kwargs): - assert node.tag == 'param' - + def __init__(self, name:str, type:str, param:str, depends:List[str]): super().__init__( - name=node.find('name').text, - depends=[node.find('type').text], - **kwargs + name=name, + depends=depends, ) - self.type = node.find('type').text + self.type = type + self.param = param - self.param = "" - for i in node.iter(): - self.param += i.text or "" - self.param += i.tail or "" - # normalise whitespace - self.param = " ".join(self.param.split()) + def __repr__(self) -> str: + return f"{{ name: '{self.name}', type: '{self.type}', param: '{self.param}' }}" def is_pointer(self): return '*' in self.param @@ -429,6 +425,9 @@ class Command(Type): self.params = params self.depends += depends or [] + def __repr__(self) -> str: + return f"{{ name: '{self.name}', result: '{self.result}', param: {self.params} }}" + def declare(self): return 'extern "C" %(result)s %(name)s (%(params)s) noexcept;' % { 'name': rename(self.name), @@ -436,14 +435,6 @@ class Command(Type): 'params': ", ".join(p.param for p in self.params) } - def is_global(self, reg: Registry): - if not self.params: - return True - - first_name = self.params[0].type - first_obj = reg.types[first_name] - return not isinstance(first_obj, Handle) - def is_instance(self, reg: Registry): assert reg @@ -454,11 +445,11 @@ class Command(Type): first_obj = reg.types[first_name] if not isinstance(first_obj, Handle): - return False + return True - instance = first_obj.has_parent('VkInstance', reg) - device = first_obj.has_parent('VkPhysicalDevice', reg) - return instance and not device + instance = first_obj.name == 'VkInstance' + physical = first_obj.name == 'VkPhysicalDevice' + return instance or physical def is_device(self, reg: Registry): if not self.params: @@ -469,24 +460,18 @@ class Command(Type): if not isinstance(first_obj, Handle): return False - return first_obj.has_parent('VkPhysicalDevice', reg) + for i in ['VkDevice', 'VkQueue', 'VkCommandBuffer']: + if first_obj.has_parent(i, reg): + return True + + return False ############################################################################### class Require(object): - def __init__(self, root): - self.values = [] - self.depends = [] - - 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: - raise RuntimeError("Unknown requires node") + def __init__(self, values, depends: List[str]): + self.values = values or [] + self.depends = depends or [] def apply(self, reg: Registry, extnumber=None): required = [] @@ -559,7 +544,7 @@ class Extension(Type): for node in root: if node.tag == 'require': - self.requires.append(Require(node)) + self.requires.append(parse_require(node)) else: raise RuntimeError("Unknown extension node") @@ -660,6 +645,28 @@ def parse_enums(reg: Registry, root): else: owner[valuename] = Constant(node) +#------------------------------------------------------------------------------ +def parse_param(root) -> Command.Param: + assert root.tag == 'param' + + param = "" + for i in root.iter(): + param += i.text or "" + param += i.tail or "" + # normalise whitespace + param = " ".join(param.split()) + + name = root.find('name').text + type = root.find('type').text + depends = [root.find('type').text] + + return Command.Param( + name=name, + type=type, + param=param, + depends=depends, + ) + # ----------------------------------------------------------------------------- def parse_commands(reg: Registry, root): @@ -677,7 +684,7 @@ def parse_commands(reg: Registry, root): name = proto.find('name').text result = proto.find('type').text - params = [Command.Param(p) for p in node.findall('./param')] + params = [parse_param(p) for p in node.findall('./param')] depends = [result] for p in params: depends += p.depends @@ -686,6 +693,25 @@ def parse_commands(reg: Registry, root): # ----------------------------------------------------------------------------- +def parse_require(root) -> Require: + assert root.tag == 'require' + + values = [] + depends = [] + + for node in root: + if node.tag == 'enum': + values.append(node) + elif node.tag in ['command', 'type']: + depends.append(node.attrib['name']) + elif node.tag in ['comment']: + pass + else: + raise RuntimeError("Unknown requires node") + + return Require(values=values, depends=depends) + +##----------------------------------------------------------------------------- def parse_feature(reg: Registry, root): assert root.tag == 'feature' @@ -695,7 +721,7 @@ def parse_feature(reg: Registry, root): requires = [] for node in root: if 'require' == node.tag: - requires.append(Require(node)) + requires.append(parse_require(node)) else: raise RuntimeError("Unhandled feature node") @@ -769,7 +795,6 @@ def write_icd(dst: TextIO, q: List[Type], reg: Registry): commands = [i for i in q if isinstance(i, Command)] collections = { - 'global': Command.is_global, 'instance': Command.is_instance, 'device': Command.is_device, } @@ -789,9 +814,13 @@ def write_icd(dst: TextIO, q: List[Type], reg: Registry): """) for name, test in collections.items(): + curr = [i for i in commands if test(i, reg)] + next = [i for i in commands if not test(i, reg)] + commands = next + dst.write(f""" #define MAP_{name.upper()}_COMMANDS(FUNC) \ - MAP0(FUNC,{",".join(i.name for i in commands if i.is_global(reg))}) + MAP0(FUNC,{",".join([i.name for i in curr])}) struct {name}_table{{ """) @@ -799,7 +828,7 @@ def write_icd(dst: TextIO, q: List[Type], reg: Registry): # Generate the vtable entries for instance methods dst.writelines(( f"{obj.result} (*{obj.name}) ({','.join(p.param for p in obj.params)}) = nullptr;" - for obj in commands if test(obj, reg) + for obj in curr )) dst.write(""" @@ -807,7 +836,9 @@ def write_icd(dst: TextIO, q: List[Type], reg: Registry): """) dst.write(""" - extern cruft::vk::icd::global_table const *g_table [[maybe_unused]]; + struct vendor_table; + extern cruft::vk::icd::vendor_table const *v_table; + extern cruft::vk::icd::instance_table const *i_table; } """) @@ -823,30 +854,88 @@ def write_dispatch(dst: TextIO, q: List[Type], reg: Registry): #pragma GCC diagnostic ignored "-Wunused-parameter" + template struct indirect { - void *handle; - void const *table; + HandleT handle; + TableT table; }; """) + implementations = { + 'vkCreateInstance': """ + auto res = std::make_unique> (); + auto err = cruft::vk::icd::i_table->vkCreateInstance (pCreateInfo, pAllocator, &res->handle); + if (err != VK_SUCCESS) + return err; + + #define GET(NAME) res->table.NAME = reinterpret_cast (cruft::vk::icd::v_table->vk_icdGetInstanceProcAddr (res->handle, #NAME)); + MAP_INSTANCE_COMMANDS(GET) + #undef GET + + #define GET(NAME) if (!res->table.NAME) res->table.NAME = reinterpret_cast (res->table.vkGetInstanceProcAddr (res->handle, #NAME)); + MAP_INSTANCE_COMMANDS(GET) + #undef GET + + *pInstance = (VkInstance)res.release (); + return err; + """, + + 'vkCreateDevice': """ + (void)physicalDevice; + (void)pCreateInfo; + (void)pAllocator; + (void)pDevice; + unimplemented (); + """, + + 'vkEnumeratePhysicalDevices': """ + auto const entry = reinterpret_cast< + indirect const* + > (instance); + + if (!pPhysicalDeviceCount || !pPhysicalDevices) { + return (entry->table.vkEnumeratePhysicalDevices)( + entry->handle, pPhysicalDeviceCount, pPhysicalDevices + ); + } + + std::vector res (*pPhysicalDeviceCount); + auto err = (entry->table.vkEnumeratePhysicalDevices)( + entry->handle, pPhysicalDeviceCount, res.data () + ); + res.resize (*pPhysicalDeviceCount); + + if (err != VK_SUCCESS) + return err; + + for (uint32_t i = 0; i < *pPhysicalDeviceCount; ++i) { + auto wrapped = std::make_unique> (); + wrapped->handle = res[i]; + + #define GET(NAME) wrapped->table.NAME = wrapped->table.NAME ?: reinterpret_cast (vkGetInstanceProcAddr (instance, #NAME)); + MAP_INSTANCE_COMMANDS(GET) + #undef GET + + pPhysicalDevices[i] = (VkPhysicalDevice)wrapped.release (); + } + + return err; + """ + } + for obj in (i for i in q if isinstance(i, Command)): first_arg = reg.types[obj.params[0].type] - if obj.is_global(reg): - dst.write(f""" - extern "C" - {obj.result} - {rename(obj.name)} ({", ".join(p.param for p in obj.params)}) noexcept {{ - return cruft::vk::icd::g_table->{obj.name} ({", ".join(p.name for p in obj.params)}); - }} - """) - continue - elif obj.is_instance(reg): + instance_forward = f""" + return cruft::vk::icd::i_table->{obj.name} ({", ".join(p.name for p in obj.params)}); + """ + + if obj.is_instance(reg): table = 'instance' elif obj.is_device(reg): table = 'device' else: - raise Exception("Unhandled command type") + raise Exception(f"Unhandled command type for {obj}") if obj.params: last_param = obj.params[-1] @@ -855,15 +944,20 @@ def write_dispatch(dst: TextIO, q: List[Type], reg: Registry): else: is_creating = False + if obj.is_instance(reg) and obj.params[0].type not in ['VkInstance', 'VkPhysicalDevice']: + forwarding = f"return ::cruft::vk::icd::i_table->{obj.name} ({', '.join (i.name for i in obj.params)});" + else: + forwarding = f""" + auto const entry = reinterpret_cast const*> ({obj.params[0].name}); + + return (entry->table.{obj.name})( + {", ".join(['entry->handle'] + [p.name for p in obj.params[1:]])} + ); + """ + dst.write(f""" extern "C" {obj.result} {rename(obj.name)} ({", ".join(p.param for p in obj.params)}) noexcept {{ - auto const entry = reinterpret_cast ({obj.params[0].name}); - auto const *table = reinterpret_cast (entry->table); - - return (table->{obj.name})( - reinterpret_cast (entry->handle) - {", ".join([''] + [p.name for p in obj.params[1:]])} - ); + {implementations.get(obj.name, forwarding)} }} """) @@ -912,6 +1006,53 @@ def main(): reg.types['VK_DEFINE_HANDLE'] = AliasType("VK_DEFINE_HANDLE", "void*") reg.types['VK_NULL_HANDLE'] = AliasValue("VK_NULL_HANDLE", "nullptr") + #reg.types['vk_icdGetInstanceProcAddr'] = Command( + # name='vk_icdGetInstanceProcAddr', + # result='void*', + # params=[ + # Command.Param( + # name='instance', + # type='VkInstance', + # param='VkInstance instance', + # depends=['VkInstance'] + # ), + # Command.Param( + # name='pName', + # type='void*', + # param='char const *pName', + # depends=[] + # ), + # ] + #) + #reg.types['vk_icdGetPhysicalDeviceProcAddr'] = Command( + # name='vk_icdGetPhysicalDeviceProcAddr', + # result='void*', + # params=[ + # Command.Param( + # name='instance', + # type='VkInstance', + # param='VkInstance instance', + # depends=['VkInstance'] + # ), + # Command.Param( + # name='pName', + # type='void*', + # param='char const *pName', + # depends=[] + # ), + # ] + #) + + #icd = Feature( + # "__nerdcruft_icd", + # requires=[ + # Require(None, ["vk_icdGetInstanceProcAddr"]), + # Require(None, ["vk_icdGetPhysicalDeviceProcAddr"]) + # ] + #) + #reg.features['__nerdcruft_icd'] = icd + #reg.types['__nerdcruft_icd'] = reg.features['__nerdcruft_icd'] + # Request serialisation of all features #features = [Feature(n) for n in root.findall('./feature')] #features = dict((f.name,f) for f in features)