use std::ffi::{CStr, c_void};
use crate::error::LoadError;
pub unsafe trait Loader: Send + Sync {
unsafe fn load(&self, name: &CStr) -> *const c_void;
}
pub struct LibloadingLoader {
lib: libloading::Library,
}
impl LibloadingLoader {
pub fn new() -> Result<Self, LoadError> {
let lib = unsafe { load_vulkan_library()? };
Ok(Self { lib })
}
}
unsafe impl Loader for LibloadingLoader {
unsafe fn load(&self, name: &CStr) -> *const c_void {
unsafe {
self.lib
.get::<*const c_void>(name.to_bytes_with_nul())
.map(|sym| *sym)
.unwrap_or(std::ptr::null())
}
}
}
unsafe fn load_vulkan_library() -> Result<libloading::Library, LoadError> {
#[cfg(target_os = "windows")]
const LIB_NAMES: &[&str] = &["vulkan-1.dll"];
#[cfg(target_os = "linux")]
const LIB_NAMES: &[&str] = &["libvulkan.so.1", "libvulkan.so"];
#[cfg(target_os = "android")]
const LIB_NAMES: &[&str] = &["libvulkan.so.1", "libvulkan.so"];
#[cfg(target_os = "macos")]
const LIB_NAMES: &[&str] = &["libvulkan.1.dylib", "libMoltenVK.dylib"];
let mut last_err = None;
for name in LIB_NAMES {
match unsafe { libloading::Library::new(name) } {
Ok(lib) => return Ok(lib),
Err(e) => last_err = Some(e),
}
}
Err(LoadError::Library(
last_err.expect("LIB_NAMES is non-empty"),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_loader_returns_null() {
struct TestNullLoader;
unsafe impl Loader for TestNullLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
std::ptr::null()
}
}
let loader = TestNullLoader;
let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
assert!(ptr.is_null());
}
#[test]
fn load_vulkan_library_returns_error_on_missing_platform() {
let result = LoadError::MissingEntryPoint;
assert_eq!(
result.to_string(),
"vkGetInstanceProcAddr not found in Vulkan library"
);
}
#[test]
#[cfg(not(miri))] fn libloading_loader_new_returns_error_message_on_missing_lib() {
let err = unsafe { libloading::Library::new("__nonexistent_vulkan_lib__") };
assert!(err.is_err());
let load_err = LoadError::Library(err.unwrap_err());
let msg = load_err.to_string();
assert!(msg.contains("failed to load Vulkan library"));
}
#[test]
fn custom_loader_returns_non_null() {
struct FixedLoader;
unsafe impl Loader for FixedLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
0xDEAD as *const c_void
}
}
let loader = FixedLoader;
let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
assert!(!ptr.is_null());
assert_eq!(ptr as usize, 0xDEAD);
}
fn _assert_loader_is_send_sync<T: Loader>() {}
#[test]
fn loader_trait_requires_send_sync() {
struct TestLoader;
unsafe impl Loader for TestLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
std::ptr::null()
}
}
_assert_loader_is_send_sync::<TestLoader>();
}
#[test]
#[cfg(not(miri))] fn libloading_loader_new_error_is_load_error_library() {
let lib_err = unsafe { libloading::Library::new("__no_such_lib__") }.unwrap_err();
let err = LoadError::Library(lib_err);
match &err {
LoadError::Library(_) => {}
LoadError::MissingEntryPoint => panic!("expected Library variant"),
}
}
#[test]
#[cfg(not(miri))] fn libloading_loader_new_exercises_load_path() {
match LibloadingLoader::new() {
Ok(loader) => {
let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
let _ = ptr;
let unknown = unsafe { loader.load(c"vkNotARealFunction_XYZ") };
assert!(unknown.is_null(), "unknown symbol should return null");
}
Err(e) => {
assert!(
matches!(e, LoadError::Library(_)),
"expected LoadError::Library, got {e}"
);
assert!(e.to_string().contains("failed to load Vulkan library"));
}
}
}
#[test]
fn loader_is_object_safe() {
struct TestLoader;
unsafe impl Loader for TestLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
0xABCD as *const c_void
}
}
let loader: Box<dyn Loader> = Box::new(TestLoader);
let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
assert_eq!(ptr as usize, 0xABCD);
}
#[test]
fn loader_behind_arc_works() {
use std::sync::Arc;
struct TestLoader;
unsafe impl Loader for TestLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
0x1234 as *const c_void
}
}
let loader: Arc<dyn Loader> = Arc::new(TestLoader);
let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
assert_eq!(ptr as usize, 0x1234);
assert_eq!(Arc::strong_count(&loader), 1);
}
#[test]
fn loader_resolves_different_names_independently() {
use std::sync::atomic::{AtomicUsize, Ordering};
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
struct CountingLoader;
unsafe impl Loader for CountingLoader {
unsafe fn load(&self, name: &CStr) -> *const c_void {
CALL_COUNT.fetch_add(1, Ordering::SeqCst);
match name.to_bytes() {
b"vkGetInstanceProcAddr" => 0x1000 as *const c_void,
b"vkGetDeviceProcAddr" => 0x2000 as *const c_void,
_ => std::ptr::null(),
}
}
}
CALL_COUNT.store(0, Ordering::SeqCst);
let loader = CountingLoader;
let gipa = unsafe { loader.load(c"vkGetInstanceProcAddr") };
let gdpa = unsafe { loader.load(c"vkGetDeviceProcAddr") };
let unknown = unsafe { loader.load(c"vkUnknown") };
assert_eq!(gipa as usize, 0x1000);
assert_eq!(gdpa as usize, 0x2000);
assert!(unknown.is_null());
assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 3);
}
#[test]
#[ignore] fn libloading_loader_new_succeeds() {
let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
assert!(!ptr.is_null(), "vkGetInstanceProcAddr should be non-null");
}
#[test]
#[ignore] fn libloading_loader_resolves_device_proc_addr() {
let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
let ptr = unsafe { loader.load(c"vkGetDeviceProcAddr") };
assert!(!ptr.is_null(), "vkGetDeviceProcAddr should be non-null");
}
#[test]
#[ignore] fn libloading_loader_returns_null_for_unknown_symbol() {
let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
let ptr = unsafe { loader.load(c"vkNotARealFunction_XYZ") };
assert!(ptr.is_null(), "unknown symbol should return null");
}
#[test]
#[ignore] fn libloading_loader_resolves_create_instance() {
let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
let ptr = unsafe { loader.load(c"vkCreateInstance") };
assert!(!ptr.is_null(), "vkCreateInstance should be non-null");
}
#[test]
#[ignore] fn libloading_loader_distinct_pointers_for_different_symbols() {
let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
let gipa = unsafe { loader.load(c"vkGetInstanceProcAddr") };
let gdpa = unsafe { loader.load(c"vkGetDeviceProcAddr") };
assert!(!gipa.is_null());
assert!(!gdpa.is_null());
assert_ne!(
gipa, gdpa,
"different symbols should return different pointers"
);
}
#[test]
#[ignore] fn libloading_loader_same_symbol_returns_same_pointer() {
let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
let ptr1 = unsafe { loader.load(c"vkGetInstanceProcAddr") };
let ptr2 = unsafe { loader.load(c"vkGetInstanceProcAddr") };
assert_eq!(ptr1, ptr2, "same symbol should return the same pointer");
}
}