alex/volcanic
Automated vulkan bindings
BRANCH main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
root codegen src emit loader.rs
use super::*;
pub(super) fn emit_loader_module(out: &mut String, ctx: &Context, vk: &Registry) {
let selected_commands = selected_command_names(ctx);
let commands: IndexMap<_, _> = vk
.commands
.iter()
.map(|command| (command.name.as_str(), command))
.collect();
let handle_parents = handle_parent_map(vk);
let dispatchable_handles = dispatchable_handle_names(vk);
let mut global = Vec::new();
let mut instance = Vec::new();
let mut device = Vec::new();
for command in &vk.commands {
if !selected_commands.contains(command.name.as_str())
|| !select::api_matches(command.api.as_deref())
{
continue;
}
match command_scope(command, &commands, &handle_parents, &dispatchable_handles) {
CommandScope::Global => global.push(command),
CommandScope::Instance => instance.push(command),
CommandScope::Device => device.push(command),
}
}
out.push_str("#![allow(clippy::too_many_arguments)]\n\n");
out.push_str("use super::*;\n");
out.push_str("use core::ffi::CStr;\n\n");
out.push_str("#[cfg(feature = \"core_1_0\")]\nmod library;\n");
out.push_str("#[cfg(feature = \"core_1_0\")]\npub use library::{Loader, InstanceCommands, DeviceCommands};\n\n");
emit_load_error(out);
emit_loader_helpers(out);
emit_loader_struct(
out,
ctx,
&commands,
"CmdsGlobal",
CommandScope::Global,
&global,
);
emit_loader_struct(
out,
ctx,
&commands,
"CmdsInstance",
CommandScope::Instance,
&instance,
);
emit_loader_struct(
out,
ctx,
&commands,
"CmdsDevice",
CommandScope::Device,
&device,
);
}
fn emit_load_error(out: &mut String) {
out.push_str(
r#"#[derive(Debug)]
pub enum LoadError {
Library {
path: std::path::PathBuf,
source: libloading::Error,
},
Symbol {
name: &'static CStr,
source: libloading::Error,
},
MissingBootstrap {
name: &'static CStr,
},
}
impl core::fmt::Display for LoadError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Library { path, source } => write!(formatter, "failed to open Vulkan loader `{}`: {source}", path.display()),
Self::Symbol { name, source } => write!(
formatter,
"failed to load Vulkan loader symbol `{}`: {source}",
name.to_str().unwrap_or("<invalid symbol>")
),
Self::MissingBootstrap { name } => write!(
formatter,
"Vulkan loader did not return required bootstrap symbol `{}`",
name.to_str().unwrap_or("<invalid symbol>")
),
}
}
}
impl std::error::Error for LoadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Library { source, .. } | Self::Symbol { source, .. } => Some(source),
Self::MissingBootstrap { .. } => None,
}
}
}
"#,
);
}
fn emit_loader_helpers(out: &mut String) {
out.push_str(
r#"#[cfg(feature = "core_1_0")]
unsafe fn load_get_instance_proc_addr(
library: &libloading::Library,
) -> Result<PFN_vkGetInstanceProcAddr, LoadError> {
unsafe { library.get::<PFN_vkGetInstanceProcAddr>(c"vkGetInstanceProcAddr") }
.map(|symbol| *symbol)
.map_err(|source| LoadError::Symbol {
name: c"vkGetInstanceProcAddr",
source,
})
}
#[cfg(feature = "core_1_0")]
unsafe fn load_global_command<T: Copy>(
get_instance_proc_addr: PFN_vkGetInstanceProcAddr,
name: &'static CStr,
) -> Option<T> {
unsafe { cast_vk_function(get_instance_proc_addr(core::ptr::null_mut(), name.as_ptr())) }
}
#[cfg(feature = "core_1_0")]
unsafe fn load_instance_command<T: Copy>(
get_instance_proc_addr: PFN_vkGetInstanceProcAddr,
instance: VkInstance,
name: &'static CStr,
) -> Option<T> {
unsafe { cast_vk_function(get_instance_proc_addr(instance, name.as_ptr())) }
}
#[cfg(feature = "core_1_0")]
unsafe fn load_device_command<T: Copy>(
get_device_proc_addr: PFN_vkGetDeviceProcAddr,
device: VkDevice,
name: &'static CStr,
) -> Option<T> {
unsafe { cast_vk_function(get_device_proc_addr(device, name.as_ptr())) }
}
#[cfg(feature = "core_1_0")]
unsafe fn cast_vk_function<T: Copy>(function: PFN_vkVoidFunction) -> Option<T> {
function.map(|function| {
assert_eq!(core::mem::size_of::<T>(), core::mem::size_of_val(&function));
unsafe { core::mem::transmute_copy(&function) }
})
}
"#,
);
}
pub(super) fn selected_command_names(ctx: &Context) -> IndexSet<&str> {
ctx.ownership
.types
.keys()
.filter_map(|name| name.strip_prefix("PFN_"))
.collect()
}
pub(super) fn emit_loader_struct(
out: &mut String,
ctx: &Context,
commands: &IndexMap<&str, &Command>,
struct_name: &str,
scope: CommandScope,
struct_commands: &[&Command],
) {
out.push_str(" #[derive(Clone, Copy, Default)]\n");
out.push_str(&format!(" pub struct {struct_name} {{\n"));
for command in struct_commands {
let pfn = format!("PFN_{}", command.name);
emit_cfg(out, ctx.type_condition(&pfn), " ");
out.push_str(&format!(" pub {}: Option<{pfn}>,\n", command.name));
}
out.push_str(" }\n\n");
out.push_str(&format!(" impl {struct_name} {{\n"));
emit_loader_load_method(out, ctx, scope, struct_commands);
for command in struct_commands {
emit_loader_method(out, ctx, commands, command);
}
out.push_str(" }\n\n");
}
pub(super) fn emit_loader_load_method(
out: &mut String,
ctx: &Context,
scope: CommandScope,
struct_commands: &[&Command],
) {
out.push_str(" #[cfg(feature = \"core_1_0\")]\n");
match scope {
CommandScope::Global => {
out.push_str(
" pub unsafe fn load(library: &libloading::Library) -> Result<Self, LoadError> {\n",
);
out.push_str(
" let get_instance_proc_addr = unsafe { load_get_instance_proc_addr(library)? };\n",
);
out.push_str(" Ok(Self {\n");
for command in struct_commands {
emit_loader_load_field(
out,
ctx,
command,
"load_global_command(get_instance_proc_addr, ",
")",
Some("get_instance_proc_addr"),
);
}
}
CommandScope::Instance => {
out.push_str(" pub unsafe fn load(\n");
out.push_str(" library: &libloading::Library,\n");
out.push_str(" instance: VkInstance,\n");
out.push_str(" ) -> Result<Self, LoadError> {\n");
out.push_str(
" let get_instance_proc_addr = unsafe { load_get_instance_proc_addr(library)? };\n",
);
out.push_str(" Ok(Self {\n");
for command in struct_commands {
emit_loader_load_field(
out,
ctx,
command,
"load_instance_command(get_instance_proc_addr, instance, ",
")",
None,
);
}
}
CommandScope::Device => {
out.push_str(" pub unsafe fn load(\n");
out.push_str(" library: &libloading::Library,\n");
out.push_str(" instance: VkInstance,\n");
out.push_str(" device: VkDevice,\n");
out.push_str(" ) -> Result<Self, LoadError> {\n");
out.push_str(
" let get_instance_proc_addr = unsafe { load_get_instance_proc_addr(library)? };\n",
);
out.push_str(
" let get_device_proc_addr: PFN_vkGetDeviceProcAddr = unsafe {\n",
);
out.push_str(
" load_instance_command(get_instance_proc_addr, instance, c\"vkGetDeviceProcAddr\")\n",
);
out.push_str(" }\n");
out.push_str(" .ok_or(LoadError::MissingBootstrap {\n");
out.push_str(" name: c\"vkGetDeviceProcAddr\",\n");
out.push_str(" })?;\n");
out.push_str(
" Ok(unsafe { Self::from_proc_addr(get_device_proc_addr, device) })\n",
);
out.push_str(" }\n");
out.push_str(" #[cfg(feature = \"core_1_0\")]\n");
out.push_str(" pub unsafe fn from_proc_addr(get_device_proc_addr: PFN_vkGetDeviceProcAddr, device: VkDevice) -> Self {\n");
out.push_str(" Self {\n");
for command in struct_commands {
emit_loader_load_field(
out,
ctx,
command,
"load_device_command(get_device_proc_addr, device, ",
")",
None,
);
}
}
}
if scope == CommandScope::Device {
out.push_str(" }\n");
} else {
out.push_str(" })\n");
}
out.push_str(" }\n");
}
pub(super) fn emit_loader_load_field(
out: &mut String,
ctx: &Context,
command: &Command,
load_prefix: &str,
load_suffix: &str,
direct_value: Option<&str>,
) {
let pfn = format!("PFN_{}", command.name);
emit_cfg(out, ctx.type_condition(&pfn), " ");
if let Some(value) = direct_value.filter(|_| command.name == "vkGetInstanceProcAddr") {
out.push_str(&format!(" {}: Some({value}),\n", command.name));
} else {
out.push_str(&format!(
" {}: unsafe {{ {load_prefix}c\"{}\"{load_suffix} }},\n",
command.name, command.name
));
}
}
pub(super) fn emit_loader_method(
out: &mut String,
ctx: &Context,
commands: &IndexMap<&str, &Command>,
command: &Command,
) {
let pfn = format!("PFN_{}", command.name);
let signature = resolve_command_signature(command, commands);
let ret = rust_decl_type(signature.proto.as_ref().unwrap(), true);
let ret = if ret == "()" {
String::new()
} else {
format!(" -> {ret}")
};
let params = params(&signature.params);
let args = call_args(&signature.params);
emit_cfg(out, ctx.type_condition(&pfn), " ");
if params.is_empty() {
out.push_str(&format!(
" pub unsafe fn {}(&self){ret} {{\n",
command.name
));
} else {
out.push_str(&format!(
" pub unsafe fn {}(&self, {params}){ret} {{\n",
command.name
));
}
out.push_str(&format!(
" unsafe {{ (self.{0}.expect(\"Vulkan command {0} is not loaded\"))({args}) }}\n",
command.name
));
out.push_str(" }\n");
}
pub(super) fn command_scope(
command: &Command,
commands: &IndexMap<&str, &Command>,
handle_parents: &IndexMap<String, String>,
dispatchable_handles: &IndexSet<String>,
) -> CommandScope {
match command.name.as_str() {
"vkGetInstanceProcAddr" => return CommandScope::Global,
"vkGetDeviceProcAddr" => return CommandScope::Instance,
_ => {}
}
let signature = resolve_command_signature(command, commands);
let Some(first_param) = signature
.params
.iter()
.find(|param| select::api_matches(param.api.as_deref()))
else {
return CommandScope::Global;
};
let handle = first_param.base.as_str();
if !dispatchable_handles.contains(handle) {
return CommandScope::Global;
}
command_scope_for_handle(handle, handle_parents)
}
pub(super) fn command_scope_for_handle(
handle: &str,
handle_parents: &IndexMap<String, String>,
) -> CommandScope {
match handle {
"VkInstance" | "VkPhysicalDevice" => return CommandScope::Instance,
"VkDevice" => return CommandScope::Device,
_ => {}
}
let mut current = handle;
let mut seen = IndexSet::new();
while let Some(parent) = handle_parents.get(current) {
if !seen.insert(current.to_string()) {
break;
}
match parent.as_str() {
"VkDevice" => return CommandScope::Device,
"VkInstance" | "VkPhysicalDevice" => return CommandScope::Instance,
_ => current = parent,
}
}
CommandScope::Global
}
pub(super) fn handle_parent_map(vk: &Registry) -> IndexMap<String, String> {
vk.types
.iter()
.filter(|ty| ty.category == "handle")
.filter(|ty| select::api_matches(ty.api.as_deref()))
.filter_map(|ty| {
ty.parent
.as_ref()
.map(|parent| (ty.name.clone(), parent.clone()))
})
.collect()
}
pub(super) fn dispatchable_handle_names(vk: &Registry) -> IndexSet<String> {
let handle_aliases: IndexMap<_, _> = vk
.types
.iter()
.filter(|ty| ty.category == "handle")
.filter_map(|ty| {
ty.alias
.as_ref()
.map(|alias| (ty.name.clone(), alias.clone()))
})
.collect();
let mut handles: IndexSet<_> = vk
.types
.iter()
.filter(|ty| ty.category == "handle")
.filter(|ty| select::api_matches(ty.api.as_deref()))
.filter(|ty| {
ty.handle_macro
.as_deref()
.is_some_and(|name| name != "VK_DEFINE_NON_DISPATCHABLE_HANDLE")
})
.map(|ty| ty.name.clone())
.collect();
loop {
let mut changed = false;
for (name, alias) in &handle_aliases {
if !handles.contains(name) && handles.contains(alias) {
handles.insert(name.clone());
changed = true;
}
}
if !changed {
break;
}
}
handles
}