1use std::ffi::{CStr, c_void};
2
3use crate::error::LoadError;
4
5pub unsafe trait Loader: Send + Sync {
32 unsafe fn load(&self, name: &CStr) -> *const c_void;
41}
42
43pub struct LibloadingLoader {
57 lib: libloading::Library,
58}
59
60impl LibloadingLoader {
61 pub fn new() -> Result<Self, LoadError> {
63 let lib = unsafe { load_vulkan_library()? };
65 Ok(Self { lib })
66 }
67}
68
69unsafe impl Loader for LibloadingLoader {
70 unsafe fn load(&self, name: &CStr) -> *const c_void {
71 unsafe {
73 self.lib
74 .get::<*const c_void>(name.to_bytes_with_nul())
75 .map(|sym| *sym)
76 .unwrap_or(std::ptr::null())
77 }
78 }
79}
80
81unsafe fn load_vulkan_library() -> Result<libloading::Library, LoadError> {
87 #[cfg(target_os = "windows")]
88 const LIB_NAMES: &[&str] = &["vulkan-1.dll"];
89
90 #[cfg(target_os = "linux")]
91 const LIB_NAMES: &[&str] = &["libvulkan.so.1", "libvulkan.so"];
92
93 #[cfg(target_os = "android")]
94 const LIB_NAMES: &[&str] = &["libvulkan.so.1", "libvulkan.so"];
95
96 #[cfg(target_os = "macos")]
97 const LIB_NAMES: &[&str] = &["libvulkan.1.dylib", "libMoltenVK.dylib"];
98
99 let mut last_err = None;
100 for name in LIB_NAMES {
101 match unsafe { libloading::Library::new(name) } {
103 Ok(lib) => return Ok(lib),
104 Err(e) => last_err = Some(e),
105 }
106 }
107 Err(LoadError::Library(
108 last_err.expect("LIB_NAMES is non-empty"),
109 ))
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn null_loader_returns_null() {
118 struct TestNullLoader;
119 unsafe impl Loader for TestNullLoader {
120 unsafe fn load(&self, _name: &CStr) -> *const c_void {
121 std::ptr::null()
122 }
123 }
124 let loader = TestNullLoader;
125 let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
126 assert!(ptr.is_null());
127 }
128
129 #[test]
130 fn load_vulkan_library_returns_error_on_missing_platform() {
131 let result = LoadError::MissingEntryPoint;
133 assert_eq!(
134 result.to_string(),
135 "vkGetInstanceProcAddr not found in Vulkan library"
136 );
137 }
138
139 #[test]
140 #[cfg(not(miri))] fn libloading_loader_new_returns_error_message_on_missing_lib() {
142 let err = unsafe { libloading::Library::new("__nonexistent_vulkan_lib__") };
145 assert!(err.is_err());
146 let load_err = LoadError::Library(err.unwrap_err());
147 let msg = load_err.to_string();
148 assert!(msg.contains("failed to load Vulkan library"));
149 }
150
151 #[test]
152 fn custom_loader_returns_non_null() {
153 struct FixedLoader;
154 unsafe impl Loader for FixedLoader {
155 unsafe fn load(&self, _name: &CStr) -> *const c_void {
156 0xDEAD as *const c_void
157 }
158 }
159 let loader = FixedLoader;
160 let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
161 assert!(!ptr.is_null());
162 assert_eq!(ptr as usize, 0xDEAD);
163 }
164
165 fn _assert_loader_is_send_sync<T: Loader>() {}
167 #[test]
168 fn loader_trait_requires_send_sync() {
169 struct TestLoader;
170 unsafe impl Loader for TestLoader {
171 unsafe fn load(&self, _name: &CStr) -> *const c_void {
172 std::ptr::null()
173 }
174 }
175 _assert_loader_is_send_sync::<TestLoader>();
176 }
177
178 #[test]
179 #[cfg(not(miri))] fn libloading_loader_new_error_is_load_error_library() {
181 let lib_err = unsafe { libloading::Library::new("__no_such_lib__") }.unwrap_err();
185 let err = LoadError::Library(lib_err);
186 match &err {
187 LoadError::Library(_) => {}
188 LoadError::MissingEntryPoint => panic!("expected Library variant"),
189 }
190 }
191
192 #[test]
193 #[cfg(not(miri))] fn libloading_loader_new_exercises_load_path() {
195 match LibloadingLoader::new() {
200 Ok(loader) => {
201 let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
203 let _ = ptr;
205
206 let unknown = unsafe { loader.load(c"vkNotARealFunction_XYZ") };
208 assert!(unknown.is_null(), "unknown symbol should return null");
209 }
210 Err(e) => {
211 assert!(
213 matches!(e, LoadError::Library(_)),
214 "expected LoadError::Library, got {e}"
215 );
216 assert!(e.to_string().contains("failed to load Vulkan library"));
217 }
218 }
219 }
220
221 #[test]
222 fn loader_is_object_safe() {
223 struct TestLoader;
226 unsafe impl Loader for TestLoader {
227 unsafe fn load(&self, _name: &CStr) -> *const c_void {
228 0xABCD as *const c_void
229 }
230 }
231 let loader: Box<dyn Loader> = Box::new(TestLoader);
232 let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
233 assert_eq!(ptr as usize, 0xABCD);
234 }
235
236 #[test]
237 fn loader_behind_arc_works() {
238 use std::sync::Arc;
239 struct TestLoader;
240 unsafe impl Loader for TestLoader {
241 unsafe fn load(&self, _name: &CStr) -> *const c_void {
242 0x1234 as *const c_void
243 }
244 }
245 let loader: Arc<dyn Loader> = Arc::new(TestLoader);
246 let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
247 assert_eq!(ptr as usize, 0x1234);
248 assert_eq!(Arc::strong_count(&loader), 1);
249 }
250
251 #[test]
252 fn loader_resolves_different_names_independently() {
253 use std::sync::atomic::{AtomicUsize, Ordering};
254 static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
255
256 struct CountingLoader;
257 unsafe impl Loader for CountingLoader {
258 unsafe fn load(&self, name: &CStr) -> *const c_void {
259 CALL_COUNT.fetch_add(1, Ordering::SeqCst);
260 match name.to_bytes() {
261 b"vkGetInstanceProcAddr" => 0x1000 as *const c_void,
262 b"vkGetDeviceProcAddr" => 0x2000 as *const c_void,
263 _ => std::ptr::null(),
264 }
265 }
266 }
267
268 CALL_COUNT.store(0, Ordering::SeqCst);
269 let loader = CountingLoader;
270 let gipa = unsafe { loader.load(c"vkGetInstanceProcAddr") };
271 let gdpa = unsafe { loader.load(c"vkGetDeviceProcAddr") };
272 let unknown = unsafe { loader.load(c"vkUnknown") };
273
274 assert_eq!(gipa as usize, 0x1000);
275 assert_eq!(gdpa as usize, 0x2000);
276 assert!(unknown.is_null());
277 assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 3);
278 }
279
280 #[test]
281 #[ignore] fn libloading_loader_new_succeeds() {
283 let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
284 let ptr = unsafe { loader.load(c"vkGetInstanceProcAddr") };
285 assert!(!ptr.is_null(), "vkGetInstanceProcAddr should be non-null");
286 }
287
288 #[test]
289 #[ignore] fn libloading_loader_resolves_device_proc_addr() {
291 let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
292 let ptr = unsafe { loader.load(c"vkGetDeviceProcAddr") };
293 assert!(!ptr.is_null(), "vkGetDeviceProcAddr should be non-null");
294 }
295
296 #[test]
297 #[ignore] fn libloading_loader_returns_null_for_unknown_symbol() {
299 let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
300 let ptr = unsafe { loader.load(c"vkNotARealFunction_XYZ") };
301 assert!(ptr.is_null(), "unknown symbol should return null");
302 }
303
304 #[test]
305 #[ignore] fn libloading_loader_resolves_create_instance() {
307 let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
308 let ptr = unsafe { loader.load(c"vkCreateInstance") };
309 assert!(!ptr.is_null(), "vkCreateInstance should be non-null");
310 }
311
312 #[test]
313 #[ignore] fn libloading_loader_distinct_pointers_for_different_symbols() {
315 let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
316 let gipa = unsafe { loader.load(c"vkGetInstanceProcAddr") };
317 let gdpa = unsafe { loader.load(c"vkGetDeviceProcAddr") };
318 assert!(!gipa.is_null());
319 assert!(!gdpa.is_null());
320 assert_ne!(
321 gipa, gdpa,
322 "different symbols should return different pointers"
323 );
324 }
325
326 #[test]
327 #[ignore] fn libloading_loader_same_symbol_returns_same_pointer() {
329 let loader = LibloadingLoader::new().expect("failed to load Vulkan library");
330 let ptr1 = unsafe { loader.load(c"vkGetInstanceProcAddr") };
331 let ptr2 = unsafe { loader.load(c"vkGetInstanceProcAddr") };
332 assert_eq!(ptr1, ptr2, "same symbol should return the same pointer");
333 }
334}