Skip to main content

vulkan_rust/
entry.rs

1use std::sync::Arc;
2
3use std::ffi::CStr;
4
5use crate::error::{LoadError, VkResult, check, enumerate_two_call};
6use crate::instance::Instance;
7use crate::loader::Loader;
8use crate::version::Version;
9use crate::vk;
10use vk::handles::Handle;
11
12/// Entry point into the Vulkan API.
13///
14/// Loads the Vulkan shared library, resolves the bootstrap function pointers,
15/// and provides access to entry-level commands (instance creation, version
16/// query, layer/extension enumeration).
17///
18/// The `Entry` keeps the shared library alive via `Arc<dyn Loader>` for the
19/// lifetime of all derived objects.
20///
21/// **Guide:** [Hello Triangle, Part 1](https://hiddentale.github.io/vulkan_rust/getting-started/hello-triangle-1.html)
22/// covers creating an `Entry` and bootstrapping the API.
23///
24/// # Examples
25///
26/// ```no_run
27/// use vulkan_rust::{Entry, LibloadingLoader};
28///
29/// let loader = unsafe { LibloadingLoader::new() }.expect("Vulkan not found");
30/// let entry = unsafe { Entry::new(loader) }.expect("entry creation failed");
31///
32/// let version = entry.version().expect("version query failed");
33/// println!("Vulkan {version}");
34/// ```
35pub struct Entry {
36    _loader: Arc<dyn Loader>,
37    get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr,
38    get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
39    commands: vk::commands::EntryCommands,
40}
41
42impl Entry {
43    /// Create a new `Entry` from the given loader.
44    ///
45    /// Resolves `vkGetInstanceProcAddr` from the library, then uses it to
46    /// bootstrap `vkGetDeviceProcAddr` and all entry-level commands.
47    ///
48    /// # Safety
49    ///
50    /// The loader must return valid Vulkan function pointers. The loaded
51    /// shared library must remain valid for the lifetime of this `Entry`
52    /// and any objects created from it.
53    ///
54    /// # Examples
55    ///
56    /// ```no_run
57    /// use vulkan_rust::{Entry, LibloadingLoader};
58    ///
59    /// let loader = unsafe { LibloadingLoader::new() }.expect("Vulkan not found");
60    /// let entry = unsafe { Entry::new(loader) }.expect("entry creation failed");
61    /// ```
62    pub unsafe fn new(loader: impl Loader + 'static) -> Result<Self, LoadError> {
63        let loader: Arc<dyn Loader> = Arc::new(loader);
64
65        // SAFETY: loader returns a valid fn ptr or null; null is checked before transmute.
66        let get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr = unsafe {
67            let ptr = loader.load(c"vkGetInstanceProcAddr");
68            if ptr.is_null() {
69                return Err(LoadError::MissingEntryPoint);
70            }
71            std::mem::transmute(ptr)
72        };
73
74        let get_instance_proc_addr_fn =
75            get_instance_proc_addr.expect("vkGetInstanceProcAddr not loaded");
76        let null_instance = vk::handles::Instance::null();
77
78        // SAFETY: loader returns a valid fn ptr or null; null becomes None.
79        let get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr =
80            unsafe { std::mem::transmute(loader.load(c"vkGetDeviceProcAddr")) };
81
82        // SAFETY: get_instance_proc_addr_fn is valid; null instance queries entry-level commands.
83        let commands = unsafe {
84            vk::commands::EntryCommands::load(|name| {
85                std::mem::transmute(get_instance_proc_addr_fn(null_instance, name.as_ptr()))
86            })
87        };
88
89        Ok(Self {
90            _loader: loader,
91            get_instance_proc_addr,
92            get_device_proc_addr,
93            commands,
94        })
95    }
96
97    /// Returns the raw `vkGetInstanceProcAddr` function pointer.
98    ///
99    /// Needed by OpenXR's `XR_KHR_vulkan_enable2` which requires the
100    /// application to provide this function pointer.
101    pub fn get_instance_proc_addr(&self) -> vk::commands::PFN_vkGetInstanceProcAddr {
102        self.get_instance_proc_addr
103    }
104
105    /// Returns the raw `vkGetDeviceProcAddr` function pointer.
106    pub fn get_device_proc_addr(&self) -> vk::commands::PFN_vkGetDeviceProcAddr {
107        self.get_device_proc_addr
108    }
109
110    /// Returns a reference to the loaded entry-level commands.
111    pub(crate) fn commands(&self) -> &vk::commands::EntryCommands {
112        &self.commands
113    }
114
115    /// Query the Vulkan instance version supported by the loader.
116    ///
117    /// Requires Vulkan 1.1+. On a 1.0-only system where
118    /// `vkEnumerateInstanceVersion` is unavailable, returns `1.0.0`.
119    ///
120    /// # Examples
121    ///
122    /// ```no_run
123    /// # let entry = vulkan_rust::test_helpers::create_test_entry().unwrap();
124    /// let version = entry.version().expect("version query failed");
125    /// assert!(version.major >= 1);
126    /// println!("Vulkan {version}");
127    /// ```
128    pub fn version(&self) -> VkResult<Version> {
129        let fp = match self.commands.enumerate_instance_version {
130            Some(fp) => fp,
131            None => {
132                return Ok(Version {
133                    major: 1,
134                    minor: 0,
135                    patch: 0,
136                });
137            }
138        };
139        let mut raw = 0u32;
140        // SAFETY: fp is vkEnumerateInstanceVersion; raw is a valid output pointer.
141        check(unsafe { fp(&mut raw) })?;
142        Ok(Version::from_raw(raw))
143    }
144
145    /// Create a Vulkan instance.
146    ///
147    /// # Safety
148    ///
149    /// `create_info` must be a valid, fully populated `InstanceCreateInfo`.
150    /// The caller is responsible for calling `instance.destroy_instance`
151    /// when done.
152    ///
153    /// # Examples
154    ///
155    /// ```no_run
156    /// use vulkan_rust::{Entry, LibloadingLoader, Version};
157    /// use vulkan_rust::vk::structs::*;
158    ///
159    /// let loader = unsafe { LibloadingLoader::new() }.expect("Vulkan not found");
160    /// let entry = unsafe { Entry::new(loader) }.expect("entry creation failed");
161    ///
162    /// let app_info = ApplicationInfo::builder()
163    ///     .api_version(Version::new(1, 0, 0).to_raw());
164    /// let create_info = InstanceCreateInfo::builder()
165    ///     .p_application_info(&*app_info);
166    /// let instance = unsafe { entry.create_instance(&create_info, None) }
167    ///     .expect("instance creation failed");
168    /// // Use instance...
169    /// unsafe { instance.destroy_instance(None) };
170    /// ```
171    pub unsafe fn create_instance(
172        &self,
173        create_info: &vk::structs::InstanceCreateInfo,
174        allocator: Option<&vk::structs::AllocationCallbacks>,
175    ) -> VkResult<Instance> {
176        // SAFETY: caller guarantees create_info is valid (this fn is unsafe).
177        let raw = unsafe { self.create_instance_raw(create_info, allocator) }?;
178        // SAFETY: raw is a freshly created valid instance handle.
179        let instance = unsafe {
180            Instance::load(
181                raw,
182                self.get_instance_proc_addr,
183                self.get_device_proc_addr,
184                Some(self._loader.clone()),
185            )
186        };
187        Ok(instance)
188    }
189
190    /// Create a Vulkan instance and return the raw handle.
191    ///
192    /// Use this when you need the `VkInstance` handle without the wrapper,
193    /// for example when passing it to OpenXR which manages the instance
194    /// lifetime externally.
195    ///
196    /// # Safety
197    ///
198    /// `create_info` must be a valid, fully populated `InstanceCreateInfo`.
199    /// The caller is responsible for destroying the instance with
200    /// `vkDestroyInstance` when done.
201    pub unsafe fn create_instance_raw(
202        &self,
203        create_info: &vk::structs::InstanceCreateInfo,
204        allocator: Option<&vk::structs::AllocationCallbacks>,
205    ) -> VkResult<vk::handles::Instance> {
206        let fp = self
207            .commands
208            .create_instance
209            .expect("vkCreateInstance not loaded");
210        let mut instance = vk::handles::Instance::null();
211        // SAFETY: caller guarantees create_info is valid (this fn is unsafe).
212        let result = unsafe {
213            fp(
214                create_info,
215                allocator.map_or(std::ptr::null(), |a| a),
216                &mut instance,
217            )
218        };
219        check(result)?;
220        Ok(instance)
221    }
222
223    /// Enumerate available instance layer properties.
224    ///
225    /// # Safety
226    ///
227    /// The Vulkan loader must be in a valid state.
228    pub unsafe fn enumerate_instance_layer_properties(
229        &self,
230    ) -> VkResult<Vec<vk::structs::LayerProperties>> {
231        let fp = self
232            .commands
233            .enumerate_instance_layer_properties
234            .expect("vkEnumerateInstanceLayerProperties not loaded");
235        // SAFETY: fp is vkEnumerateInstanceLayerProperties; two-call pattern handles allocation.
236        enumerate_two_call(|count, data| unsafe { fp(count, data) })
237    }
238
239    /// Enumerate available instance extension properties.
240    ///
241    /// Pass `None` for `layer_name` to enumerate extensions provided by the
242    /// loader and implicit layers. Pass a layer name to enumerate extensions
243    /// provided by that layer.
244    ///
245    /// # Safety
246    ///
247    /// If `layer_name` is `Some`, it must name a layer that is present.
248    pub unsafe fn enumerate_instance_extension_properties(
249        &self,
250        layer_name: Option<&CStr>,
251    ) -> VkResult<Vec<vk::structs::ExtensionProperties>> {
252        let fp = self
253            .commands
254            .enumerate_instance_extension_properties
255            .expect("vkEnumerateInstanceExtensionProperties not loaded");
256        let layer_ptr = layer_name.map_or(std::ptr::null(), |n| n.as_ptr());
257        // SAFETY: fp is vkEnumerateInstanceExtensionProperties; layer_ptr is null or valid CStr.
258        enumerate_two_call(|count, data| unsafe { fp(layer_ptr, count, data) })
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use std::ffi::{CStr, c_char, c_void};
266
267    /// Mock loader that returns null for everything,simulates missing library.
268    struct NullLoader;
269
270    unsafe impl Loader for NullLoader {
271        unsafe fn load(&self, _name: &CStr) -> *const c_void {
272            std::ptr::null()
273        }
274    }
275
276    /// Mock loader that returns a fake `vkGetInstanceProcAddr` which itself
277    /// returns null for everything. This lets us construct an Entry without
278    /// a real Vulkan runtime.
279    struct FakeEntryLoader;
280
281    unsafe extern "system" fn mock_get_instance_proc_addr(
282        _instance: vk::handles::Instance,
283        _name: *const c_char,
284    ) -> vk::structs::PFN_vkVoidFunction {
285        None
286    }
287
288    unsafe impl Loader for FakeEntryLoader {
289        unsafe fn load(&self, name: &CStr) -> *const c_void {
290            if name == c"vkGetInstanceProcAddr" {
291                mock_get_instance_proc_addr as *const c_void
292            } else {
293                std::ptr::null()
294            }
295        }
296    }
297
298    #[test]
299    fn new_returns_missing_entry_point_when_loader_returns_null() {
300        let result = unsafe { Entry::new(NullLoader) };
301        match result {
302            Err(LoadError::MissingEntryPoint) => {}
303            Err(other) => panic!("expected MissingEntryPoint, got {other}"),
304            Ok(_) => panic!("expected error, got Ok"),
305        }
306    }
307
308    #[test]
309    fn new_succeeds_with_fake_loader() {
310        let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
311        assert!(entry.get_instance_proc_addr().is_some());
312    }
313
314    #[test]
315    fn version_returns_1_0_when_enumerate_instance_version_is_none() {
316        // FakeEntryLoader returns null for all commands, so
317        // enumerate_instance_version will be None → 1.0 fallback.
318        let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
319        let version = entry.version().expect("version should succeed");
320        assert_eq!(version.major, 1);
321        assert_eq!(version.minor, 0);
322        assert_eq!(version.patch, 0);
323    }
324
325    #[test]
326    fn commands_returns_reference() {
327        let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
328        let _ = entry.commands();
329    }
330
331    #[test]
332    fn get_instance_proc_addr_returns_some() {
333        let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
334        assert!(entry.get_instance_proc_addr().is_some());
335    }
336
337    #[test]
338    fn get_device_proc_addr_returns_none_from_fake_loader() {
339        // FakeEntryLoader returns null for vkGetDeviceProcAddr
340        let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
341        assert!(entry.get_device_proc_addr().is_none());
342    }
343
344    // -- Rich mock loader that returns fake entry-level function pointers ----
345
346    /// Mock loader where `vkGetInstanceProcAddr` dispatches to fake
347    /// implementations of entry-level commands.
348    struct RichEntryLoader;
349
350    unsafe extern "system" fn rich_get_instance_proc_addr(
351        _instance: vk::handles::Instance,
352        name: *const c_char,
353    ) -> vk::structs::PFN_vkVoidFunction {
354        let name = unsafe { CStr::from_ptr(name) };
355        match name.to_bytes() {
356            b"vkEnumerateInstanceVersion" => Some(unsafe {
357                std::mem::transmute::<
358                    unsafe extern "system" fn(*mut u32) -> vk::enums::Result,
359                    unsafe extern "system" fn(),
360                >(mock_enumerate_instance_version)
361            }),
362            b"vkEnumerateInstanceLayerProperties" => Some(unsafe {
363                std::mem::transmute::<
364                    unsafe extern "system" fn(
365                        *mut u32,
366                        *mut vk::structs::LayerProperties,
367                    ) -> vk::enums::Result,
368                    unsafe extern "system" fn(),
369                >(mock_enumerate_instance_layer_properties)
370            }),
371            b"vkEnumerateInstanceExtensionProperties" => Some(unsafe {
372                std::mem::transmute::<
373                    unsafe extern "system" fn(
374                        *const c_char,
375                        *mut u32,
376                        *mut vk::structs::ExtensionProperties,
377                    ) -> vk::enums::Result,
378                    unsafe extern "system" fn(),
379                >(mock_enumerate_instance_extension_properties)
380            }),
381            b"vkCreateInstance" => Some(unsafe {
382                std::mem::transmute::<
383                    unsafe extern "system" fn(
384                        *const vk::structs::InstanceCreateInfo,
385                        *const vk::structs::AllocationCallbacks,
386                        *mut vk::handles::Instance,
387                    ) -> vk::enums::Result,
388                    unsafe extern "system" fn(),
389                >(mock_create_instance)
390            }),
391            _ => None,
392        }
393    }
394
395    unsafe extern "system" fn mock_enumerate_instance_version(
396        p_api_version: *mut u32,
397    ) -> vk::enums::Result {
398        // Return Vulkan 1.3.290
399        unsafe { *p_api_version = (1 << 22) | (3 << 12) | 290 };
400        vk::enums::Result::SUCCESS
401    }
402
403    unsafe extern "system" fn mock_enumerate_instance_layer_properties(
404        p_count: *mut u32,
405        _p_properties: *mut vk::structs::LayerProperties,
406    ) -> vk::enums::Result {
407        unsafe { *p_count = 0 };
408        vk::enums::Result::SUCCESS
409    }
410
411    unsafe extern "system" fn mock_enumerate_instance_extension_properties(
412        _p_layer_name: *const c_char,
413        p_count: *mut u32,
414        _p_properties: *mut vk::structs::ExtensionProperties,
415    ) -> vk::enums::Result {
416        unsafe { *p_count = 0 };
417        vk::enums::Result::SUCCESS
418    }
419
420    unsafe extern "system" fn mock_create_instance(
421        _p_create_info: *const vk::structs::InstanceCreateInfo,
422        _p_allocator: *const vk::structs::AllocationCallbacks,
423        p_instance: *mut vk::handles::Instance,
424    ) -> vk::enums::Result {
425        // Write a non-null sentinel handle.
426        unsafe { *p_instance = std::mem::transmute::<usize, vk::handles::Instance>(0x1234_usize) };
427        vk::enums::Result::SUCCESS
428    }
429
430    unsafe impl Loader for RichEntryLoader {
431        unsafe fn load(&self, name: &CStr) -> *const c_void {
432            match name.to_bytes() {
433                b"vkGetInstanceProcAddr" => rich_get_instance_proc_addr as *const c_void,
434                b"vkGetDeviceProcAddr" => std::ptr::null(),
435                _ => std::ptr::null(),
436            }
437        }
438    }
439
440    #[test]
441    fn version_returns_parsed_version_when_fp_available() {
442        let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
443        let version = entry.version().expect("version should succeed");
444        assert_eq!(version.major, 1);
445        assert_eq!(version.minor, 3);
446        assert_eq!(version.patch, 290);
447    }
448
449    #[test]
450    fn enumerate_layer_properties_with_mock() {
451        let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
452        let layers =
453            unsafe { entry.enumerate_instance_layer_properties() }.expect("should succeed");
454        assert!(layers.is_empty());
455    }
456
457    #[test]
458    fn enumerate_extension_properties_with_mock() {
459        let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
460        let extensions =
461            unsafe { entry.enumerate_instance_extension_properties(None) }.expect("should succeed");
462        assert!(extensions.is_empty());
463    }
464
465    #[test]
466    fn enumerate_extension_properties_with_layer_name() {
467        let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
468        let extensions =
469            unsafe { entry.enumerate_instance_extension_properties(Some(c"VK_LAYER_test")) }
470                .expect("should succeed");
471        assert!(extensions.is_empty());
472    }
473
474    #[test]
475    fn create_instance_raw_with_mock() {
476        let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
477        let create_info: vk::structs::InstanceCreateInfo = unsafe { std::mem::zeroed() };
478        let raw = unsafe { entry.create_instance_raw(&create_info, None) }.expect("should succeed");
479        assert!(!raw.is_null());
480    }
481
482    // -- Error-path mock loader -----------------------------------------------
483
484    /// Mock loader where entry-level commands return Vulkan errors.
485    struct FailingEntryLoader;
486
487    unsafe extern "system" fn failing_get_instance_proc_addr(
488        _instance: vk::handles::Instance,
489        name: *const c_char,
490    ) -> vk::structs::PFN_vkVoidFunction {
491        let name = unsafe { CStr::from_ptr(name) };
492        match name.to_bytes() {
493            b"vkEnumerateInstanceVersion" => Some(unsafe {
494                std::mem::transmute::<
495                    unsafe extern "system" fn(*mut u32) -> vk::enums::Result,
496                    unsafe extern "system" fn(),
497                >(failing_enumerate_instance_version)
498            }),
499            b"vkEnumerateInstanceLayerProperties" => Some(unsafe {
500                std::mem::transmute::<
501                    unsafe extern "system" fn(
502                        *mut u32,
503                        *mut vk::structs::LayerProperties,
504                    ) -> vk::enums::Result,
505                    unsafe extern "system" fn(),
506                >(failing_enumerate_instance_layer_properties)
507            }),
508            b"vkEnumerateInstanceExtensionProperties" => Some(unsafe {
509                std::mem::transmute::<
510                    unsafe extern "system" fn(
511                        *const c_char,
512                        *mut u32,
513                        *mut vk::structs::ExtensionProperties,
514                    ) -> vk::enums::Result,
515                    unsafe extern "system" fn(),
516                >(failing_enumerate_instance_extension_properties)
517            }),
518            b"vkCreateInstance" => Some(unsafe {
519                std::mem::transmute::<
520                    unsafe extern "system" fn(
521                        *const vk::structs::InstanceCreateInfo,
522                        *const vk::structs::AllocationCallbacks,
523                        *mut vk::handles::Instance,
524                    ) -> vk::enums::Result,
525                    unsafe extern "system" fn(),
526                >(failing_create_instance)
527            }),
528            _ => None,
529        }
530    }
531
532    unsafe extern "system" fn failing_enumerate_instance_version(
533        _p_api_version: *mut u32,
534    ) -> vk::enums::Result {
535        vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY
536    }
537
538    unsafe extern "system" fn failing_enumerate_instance_layer_properties(
539        _p_count: *mut u32,
540        _p_properties: *mut vk::structs::LayerProperties,
541    ) -> vk::enums::Result {
542        vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY
543    }
544
545    unsafe extern "system" fn failing_enumerate_instance_extension_properties(
546        _p_layer_name: *const c_char,
547        _p_count: *mut u32,
548        _p_properties: *mut vk::structs::ExtensionProperties,
549    ) -> vk::enums::Result {
550        vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY
551    }
552
553    unsafe extern "system" fn failing_create_instance(
554        _p_create_info: *const vk::structs::InstanceCreateInfo,
555        _p_allocator: *const vk::structs::AllocationCallbacks,
556        _p_instance: *mut vk::handles::Instance,
557    ) -> vk::enums::Result {
558        vk::enums::Result::ERROR_INITIALIZATION_FAILED
559    }
560
561    unsafe impl Loader for FailingEntryLoader {
562        unsafe fn load(&self, name: &CStr) -> *const c_void {
563            match name.to_bytes() {
564                b"vkGetInstanceProcAddr" => failing_get_instance_proc_addr as *const c_void,
565                _ => std::ptr::null(),
566            }
567        }
568    }
569
570    #[test]
571    fn version_propagates_error() {
572        let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
573        let result = entry.version();
574        assert_eq!(
575            result.unwrap_err(),
576            vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY
577        );
578    }
579
580    #[test]
581    fn create_instance_raw_propagates_error() {
582        let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
583        let create_info: vk::structs::InstanceCreateInfo = unsafe { std::mem::zeroed() };
584        let result = unsafe { entry.create_instance_raw(&create_info, None) };
585        assert_eq!(
586            result.unwrap_err(),
587            vk::enums::Result::ERROR_INITIALIZATION_FAILED
588        );
589    }
590
591    #[test]
592    fn enumerate_layer_properties_propagates_error() {
593        let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
594        let result = unsafe { entry.enumerate_instance_layer_properties() };
595        assert_eq!(
596            result.unwrap_err(),
597            vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY
598        );
599    }
600
601    #[test]
602    fn enumerate_extension_properties_propagates_error() {
603        let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
604        let result = unsafe { entry.enumerate_instance_extension_properties(None) };
605        assert_eq!(
606            result.unwrap_err(),
607            vk::enums::Result::ERROR_OUT_OF_HOST_MEMORY
608        );
609    }
610
611    #[test]
612    #[ignore] // requires Vulkan runtime
613    fn new_succeeds_with_real_loader() {
614        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
615        let loader = crate::loader::LibloadingLoader::new().expect("failed to load Vulkan library");
616        let entry = unsafe { Entry::new(loader) }.expect("failed to create Entry");
617        assert!(entry.get_instance_proc_addr().is_some());
618        assert!(entry.get_device_proc_addr().is_some());
619    }
620
621    #[test]
622    #[ignore] // requires Vulkan runtime
623    fn version_returns_at_least_1_0() {
624        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
625        let entry = create_entry();
626        let version = entry.version().expect("failed to query version");
627        assert!(version.major >= 1);
628        println!("Vulkan {version}");
629    }
630
631    #[test]
632    #[ignore] // requires Vulkan runtime
633    fn enumerate_layer_properties_succeeds() {
634        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
635        let entry = create_entry();
636        let layers = unsafe { entry.enumerate_instance_layer_properties() }
637            .expect("failed to enumerate layers");
638        println!("found {} layers", layers.len());
639    }
640
641    #[test]
642    #[ignore] // requires Vulkan runtime
643    fn enumerate_extension_properties_succeeds() {
644        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
645        let entry = create_entry();
646        let extensions = unsafe { entry.enumerate_instance_extension_properties(None) }
647            .expect("failed to enumerate extensions");
648        assert!(!extensions.is_empty(), "expected at least one extension");
649        println!("found {} extensions", extensions.len());
650    }
651
652    /// Helper to create an Entry for integration tests.
653    fn create_entry() -> Entry {
654        let loader = crate::loader::LibloadingLoader::new().expect("failed to load Vulkan library");
655        unsafe { Entry::new(loader) }.expect("failed to create Entry")
656    }
657}