from collections import namedtuple import typeid as tid import decl Param = namedtuple('Param', 'name type') Proto = namedtuple('Proto', 'name params ret') Cmd = namedtuple('Cmd', 'proto requires') class Commands: root = [] commands = {} commandfeatures = {} features = [] orderedcmds = [] used = {} aliases = {} def __init__(self, root): self.root = root for cs in root.findall("./commands"): for c in cs.findall("./command"): if "alias" in c.attrib: self.aliases[c.attrib["name"]] = c.attrib["alias"] continue proto = c.find("proto") if proto == None: print "no proto" name = proto.find("name").text ret = proto.find("type").text requires = [ret] params = [] for p in c.findall("param"): d = decl.parse_decl(p) params.append(d) if not d.type in tid.ctypes and not d.type in requires: requires.append(d.type) proto = Proto(name, params, ret) self.commands[name] = Cmd(proto, requires) self.used[name] = False def require(self, name, feature): if name in self.aliases: name = self.aliases[name] if not name in self.commands: print "COMMAND NOT FOUND " + name return [] if not self.used[name]: self.used[name] = True self.commandfeatures[name] = feature if not feature.version in self.features: self.features.append(feature.version) self.features.sort() self.orderedcmds.append(name) return self.commands[name].requires return [] def write(self, types): s = "" s += "/// Create a version number from a major, minor and patch as it is defined in\n" s += "/// [vulkan version numbers and semantics](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#fundamentals-versionnum)\n" s += "/// \n" s += "/// May be used to set the `applicationVersion`, `pEngineName` and `apiVersion` fields in [ApplicationInfo](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkApplicationInfo.htm)\n" s += "#[macro_export]\n" s += "macro_rules! make_version {\n" s += " ($major:expr, $minor:expr, $patch:expr) => ((($major) << 22) | (($minor) << 12) | ($patch))\n" s += "}\n" s += "/// Extract major number from version, created with [make_version](macro.make_version.html) or retrieved from vulkan\n" s += "#[macro_export]\n" s += "macro_rules! version_major {\n" s += " ($ver:expr) => ($ver >> 22)\n" s += "}\n" s += "/// Extract minor number from version, created with [make_version](macro.make_version.html) or retrieved from vulkan\n" s += "#[macro_export]\n" s += "macro_rules! version_minor {\n" s += " ($ver:expr) => (($ver >> 12) & 0x3ff)\n" s += "}\n" s += "/// Extract patch number from version, created with [make_version](macro.make_version.html) or retrieved from vulkan\n" s += "#[macro_export]\n" s += "macro_rules! version_patch {\n" s += " ($ver:expr) => ($ver & 0xfff)\n" s += "}\n" def const_version(f): return f[3:] for f in self.features: numbers = f.replace("VK_VERSION_", "").split("_") s += "pub const " + f[3:] + ": u32 = make_version!(" + numbers[0] + ", " + numbers[1] + ", 0);\n" def write_cmds(): def params(cmd, flags = ""): s = "" for i in range(0, len(cmd.proto.params)): p = cmd.proto.params[i] d = decl.resolve_decl(types, p) if 'u' in flags: d = decl.Decl("_" + d.name, d.type, d.decl); if 'n' in flags: s += d.name else: s += decl.write_decl(d) if i < len(cmd.proto.params) - 1: s += ", " return s def self_params(cmd): return "&self, " + params(cmd) def params_names(cmd): return params(cmd, "n") def params_unused(cmd): return params(cmd, "u") def name(cmd): return cmd.proto.name[2:] def name_ptr(cmd): return name(cmd) + "_ptr" def name_panic(cmd): return name(cmd) + "_panic" def name_pfn(cmd): return "PFN_" + cmd.proto.name def ret_type(ret): if ret != "void": return " -> " + types.format_type(ret) else: return "" extern_sys = "extern \"system\" " def fn_proto(decl, cmd, name = lambda cmd: "", params = lambda cmd: params(cmd)): return decl + "fn " + name(cmd) + "(" + params(cmd) + ")" + ret_type(cmd.proto.ret) # function that always panics, for not loaded commands def fn_typedef(cmd): return "#[doc(hidden)] pub type PFN_"+ cmd.proto.name + " = " + fn_proto(extern_sys, cmd) + ";\n" # function that always panics, for not loaded commands def fn_panic(cmd): s = "" s += fn_proto("extern \"system\" ", cmd, name_panic, params_unused) + "{\n" s += " panic!(\"extension " + cmd.proto.name + " not loaded\")\n" s += "}\n" return s # load command def load_cmd(cmd): def transmute(exp): return "mem::transmute(" + exp + ")" def symbol(cmd): return "vk_dl.symbol::(\"" + cmd.proto.name + "\")" def expect(cmd): return "expect(\"could not find symbol for " + cmd.proto.name + "\")" version = const_version(self.commandfeatures[cmd.proto.name].version) proto_panic = fn_proto(extern_sys, cmd); s = "" if self.commandfeatures[cmd.proto.name].entry == "core": s = "" s += "let " + name_ptr(cmd) + " = if feature < " + version + " {\n" s += " " + name_panic(cmd) + " as " + name_pfn(cmd) + "\n" s += "} else {\n" s += " " + transmute(symbol(cmd) + "." + expect(cmd)) + "\n" s += "};\n" else: s += "let mut " + name_ptr(cmd) + " = " + name_panic(cmd) + " as " + name_pfn(cmd) + ";\n" s += "if feature >= " + version + " {\n" s += " if let Ok(fnptr) = " + symbol(cmd) + " {\n" s += " " + name_ptr(cmd) + " = mem::transmute(fnptr);\n" s += " }\n" #TODO: is_loaded(extension_name) #s += " else {\n" #s += " println!(\"" + name_ptr(cmd) + "\");\n" #s += " }\n" s += "}\n" return s # vulkan command as call to member of function pointer wrapper def call_ptr(caller, cmd): semi = "" if ret_type(cmd.proto.ret) == "()": semi = ";" return "(" + caller + "." + name_ptr(cmd) + ")(" + params_names(cmd) + ")" + semi + "\n" def call_member(cmd): s = "" s += fn_proto("#[doc(hidden)] pub ", cmd, name, self_params) + "{\n" s += " " + call_ptr("self", cmd) s += "}\n" return s # vulkan command as call to global function in the nobs_vk namespace (core commands only) def call_static(cmd): semi = "" if ret_type(cmd.proto.ret) == "()": semi = ";" s = "" s += fn_proto("#[doc(hidden)] pub ", cmd, name) + "{\n" s += " unsafe {\n" s += " let ptr = vklib.expect(\"Vulkan core not initialized, make sure to have a valid instance of nobs_vk::VkLib\");\n" s += " " + call_ptr("(*ptr)", cmd) s += " }\n" s += "}\n" return s # execute lambda on every command and concat strings def for_each_command(doit): def for_each(doit, entry): s = "" for name in self.orderedcmds: if not self.used[name]: continue if self.commandfeatures[name].entry != entry: continue s += doit(self.commands[name]) return s s = "" s += for_each(doit, "core") s += for_each(doit, "instance") s += for_each(doit, "device") return s def indent(text, prefix): def prefixed_lines(): for line in text.splitlines(True): yield (prefix + line) return ''.join(prefixed_lines()) # struct definition s = "" s += "\n" s += "\n" s += for_each_command(lambda cmd: fn_typedef(cmd)) s += "\n" s += "\n" s += "/// Vulkan library initialization struct\n" s += "///\n" s += "/// This struct is used to initialize vulkan commands and holds function pointers to them.\n" s += "/// Function pointers are initialized during construction.\n" s += "///\n" s += "/// After successfull instantiation the global vulkan functions are ready to use.\n" s += "/// There must always be only a single instance maximum of this struct.\n" s += "pub struct VkLib {\n" s += " #[allow(dead_code)]\n" s += " lib: shared_library::dynamic_library::DynamicLibrary,\n" s += " feature: u32,\n" s += indent(for_each_command(lambda cmd: name_ptr(cmd) + ": " + fn_proto(extern_sys, cmd) + ",\n"), " ") s += "}\n" # functions for default initialization and in case the vulkan command could not be loaded s += "\n" s += "\n" s += for_each_command(lambda cmd: fn_panic(cmd)) # default trait s += "\n" s += "\n" s += "impl Default for VkLib {\n" s += " /// Initializes all function pointers to functions that immediately panic.\n" s += " fn default() -> VkLib {\n" s += " VkLib {\n" s += " lib: shared_library::dynamic_library::DynamicLibrary::open(None).expect(\"can not open library\"),\n" s += " feature: 0,\n" s += indent(for_each_command(lambda cmd: name_ptr(cmd) + ": " + name_panic(cmd) + ",\n"), " ") s += " }\n" s += " }\n" s += "}\n" # impl new() loads commands s += "\n" s += "\n" s += "impl VkLib {\n" s += " /// Initializes commands for the newest available vulkan version\n" s += " /// \n" s += " /// ```\n" s += " /// let vk_lib = nobs_vk::VkLib::new();\n" s += " /// ```\n" s += " /// is the same as\n" s += " /// ```\n" s += " /// let vk_lib = nobs_vk::VkLib::with_feature(nobs_vk::VERSION_1_1);\n" s += " /// ```\n" s += " pub fn new() -> std::boxed::Box {\n" s += " Self::with_feature(" + const_version(self.features[-1]) + ")\n" s += " }\n" s += " /// Initializes commands for the specified vulkan feature\n" s += " /// \n" s += " /// Select a feature either with the predefined constants `VERSION_x_x`,\n" s += " /// or use the [make_version](macro.make_version.html) macro\n" s += " pub fn with_feature(feature: u32) -> std::boxed::Box {\n" # load the dynamic_library s += " #[cfg(windows)]\n" s += " fn open_lib() -> shared_library::dynamic_library::DynamicLibrary {\n" s += " shared_library::dynamic_library::DynamicLibrary::open(Some(std::path::Path::new(\"vulkan-1.dll\"))).expect(\"vulkan not found\");\n" s += " }\n" s += " #[cfg(all(unix, not(target_os = \"android\"), not(target_os = \"macos\")))]\n" s += " fn open_lib() -> shared_library::dynamic_library::DynamicLibrary {\n" s += " shared_library::dynamic_library::DynamicLibrary::open(Some(std::path::Path::new(\"libvulkan.so\"))).expect(\"vulkan not found\")\n" s += " }\n" s += " #[cfg(target_os = \"macos\")]\n" s += " fn open_lib() -> shared_library::dynamic_library::DynamicLibrary {\n" s += " shared_library::dynamic_library::DynamicLibrary::open(Some(std::path::Path::new(\"libvulkan.1.dylib\"))).expect(\"vulkan not found\")\n" s += " }\n" s += " let vk_dl = open_lib();\n" # load the commands s += "\n" s += " unsafe { \n" s += indent(for_each_command(load_cmd), " ") s += "\n" # ctor returns a box with the dynamic_library and sets the static pointer s += " let c = VkLib {\n" s += " lib: vk_dl,\n" s += " feature,\n" s += indent(for_each_command(lambda cmd: name_ptr(cmd) + ",\n"), " ") s += " };\n\n" s += " vklib = Some(std::boxed::Box::into_raw(std::boxed::Box::new(c)));\n" s += " std::boxed::Box::from_raw(vklib.unwrap())" s += " }\n" s += " }\n" s += "\n" s += "\n" s += " /// Gets the feature level with which vulkan was initialized\n" s += " ///\n" s += " /// The fature is formatted as described in [make_version](macro.make_version.html).\n" s += " pub fn get_feature(&self) -> u32 {\n" s += " self.feature\n" s += " }\n" s += "\n" s += indent(for_each_command(lambda cmd: call_member(cmd)), " ") s += "}\n" # Drop, to reset the static pointer and public function that call functions on static pointer s += "impl Drop for VkLib {\n" s += " fn drop(&mut self) {\n" s += " unsafe {\n" s += " vklib = None;\n" s += " }\n" s += " }\n" s += "}\n" s += "\n" s += "\n" s += "static mut vklib: Option<*mut VkLib> = None;\n" s += "\n" s += for_each_command(lambda cmd: call_static(cmd)) return s s += "\n" s += "\n" s += write_cmds() return s