Skip to main content

vulkan_rust/
instance.rs

1use std::sync::Arc;
2
3use crate::device::Device;
4use crate::error::{VkResult, check};
5use crate::loader::Loader;
6use crate::vk;
7use vk::handles::Handle;
8
9/// Wrapper around a `VkInstance` handle and its loaded command table.
10///
11/// Owns a `Box<InstanceCommands>` containing all instance-level function
12/// pointers, loaded at construction via `vkGetInstanceProcAddr`. Also
13/// stores `vkGetDeviceProcAddr` for later use when creating a `Device`.
14///
15/// Holds an optional reference to the Vulkan shared library so that
16/// function pointers remain valid even if the originating `Entry` is
17/// dropped. When created via `from_raw_parts`, the caller manages the
18/// library lifetime and this field is `None`.
19///
20/// Does **not** implement `Drop`, the caller must explicitly call
21/// `destroy_instance` when done. This avoids double-destroy bugs when
22/// wrapping externally managed handles via `from_raw_parts`.
23///
24/// **Guide:** [The Vulkan Object Model](https://hiddentale.github.io/vulkan_rust/concepts/object-model.html)
25/// covers handles, lifetimes, and parent-child relationships.
26///
27/// # Examples
28///
29/// ```no_run
30/// use vulkan_rust::vk::structs::*;
31///
32/// # let (entry, instance) = vulkan_rust::test_helpers::create_test_instance().unwrap();
33/// // Enumerate GPUs and query properties.
34/// let devices = unsafe { instance.enumerate_physical_devices() }
35///     .expect("no devices");
36/// let props = unsafe { instance.get_physical_device_properties(devices[0]) };
37///
38/// // Clean up.
39/// unsafe { instance.destroy_instance(None) };
40/// ```
41pub struct Instance {
42    handle: vk::handles::Instance,
43    commands: Box<vk::commands::InstanceCommands>,
44    get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
45    _loader: Option<Arc<dyn Loader>>,
46}
47
48impl Instance {
49    /// Internal construction path. Called by `Entry::create_instance`.
50    ///
51    /// Loads all instance-level function pointers using the real instance
52    /// handle, which gives instance-specific trampolines that skip a layer
53    /// of dispatch compared to the global `vkGetInstanceProcAddr`.
54    ///
55    /// # Safety
56    ///
57    /// - `handle` must be a valid `VkInstance`.
58    /// - `get_instance_proc_addr` must resolve instance-level commands for
59    ///   this handle.
60    /// - `get_device_proc_addr` must be the function used to load
61    ///   device-level commands later.
62    pub(crate) unsafe fn load(
63        handle: vk::handles::Instance,
64        get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr,
65        get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
66        loader: Option<Arc<dyn Loader>>,
67    ) -> Self {
68        let get_instance_proc_addr_fn =
69            get_instance_proc_addr.expect("vkGetInstanceProcAddr not loaded");
70        // SAFETY: handle is valid per caller contract; transmute converts raw fn ptrs.
71        let commands = Box::new(unsafe {
72            vk::commands::InstanceCommands::load(|name| {
73                std::mem::transmute(get_instance_proc_addr_fn(handle, name.as_ptr()))
74            })
75        });
76        Self {
77            handle,
78            commands,
79            get_device_proc_addr,
80            _loader: loader,
81        }
82    }
83
84    /// Wrap a raw handle created externally (OpenXR, middleware, testing).
85    ///
86    /// Resolves `vkGetDeviceProcAddr` from the instance automatically, so
87    /// the caller only needs to provide `vkGetInstanceProcAddr`.
88    ///
89    /// # Safety
90    ///
91    /// - `handle` must be a valid `VkInstance` that was created externally.
92    /// - `get_instance_proc_addr` must be the function used to load
93    ///   instance-level commands for this handle.
94    /// - The caller is responsible for the instance's lifetime, it must
95    ///   outlive this wrapper and not be destroyed while in use.
96    ///
97    /// # Examples
98    ///
99    /// ```no_run
100    /// use vulkan_rust::Instance;
101    /// # let entry = vulkan_rust::test_helpers::create_test_entry().unwrap();
102    ///
103    /// // Given a raw instance handle from OpenXR or another source:
104    /// let raw_instance = unsafe { entry.create_instance_raw(
105    ///     &Default::default(), None,
106    /// ) }.unwrap();
107    ///
108    /// let instance = unsafe {
109    ///     Instance::from_raw_parts(raw_instance, entry.get_instance_proc_addr())
110    /// };
111    /// // Use instance...
112    /// unsafe { instance.destroy_instance(None) };
113    /// ```
114    pub unsafe fn from_raw_parts(
115        handle: vk::handles::Instance,
116        get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr,
117    ) -> Self {
118        let get_instance_proc_addr_fn =
119            get_instance_proc_addr.expect("vkGetInstanceProcAddr not loaded");
120
121        // SAFETY: resolving vkGetDeviceProcAddr from a valid instance handle.
122        let get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr = unsafe {
123            std::mem::transmute(get_instance_proc_addr_fn(
124                handle,
125                c"vkGetDeviceProcAddr".as_ptr(),
126            ))
127        };
128
129        // SAFETY: forwards caller's safety guarantees to `load`.
130        unsafe { Self::load(handle, get_instance_proc_addr, get_device_proc_addr, None) }
131    }
132
133    /// Returns the raw `VkInstance` handle.
134    pub fn handle(&self) -> vk::handles::Instance {
135        self.handle
136    }
137
138    /// Returns the loaded instance-level command table.
139    ///
140    /// Use this to call any of the ~90 instance-level commands directly,
141    /// including those without hand-written ergonomic wrappers.
142    pub fn commands(&self) -> &vk::commands::InstanceCommands {
143        &self.commands
144    }
145
146    /// Create a logical device for the given physical device.
147    ///
148    /// # Safety
149    ///
150    /// - `physical_device` must be a valid handle obtained from this instance.
151    /// - `create_info` must be a valid, fully populated `DeviceCreateInfo`.
152    /// - The caller is responsible for calling `device.destroy_device` when done.
153    ///
154    /// # Examples
155    ///
156    /// ```no_run
157    /// # use vulkan_rust::vk::structs::*;
158    /// # let (entry, instance) = vulkan_rust::test_helpers::create_test_instance().expect("test setup failed");
159    /// let physical_devices = unsafe { instance.enumerate_physical_devices() }
160    ///     .expect("no devices");
161    /// let physical_device = physical_devices[0];
162    ///
163    /// let priorities = [1.0f32];
164    /// let queue_info = DeviceQueueCreateInfo::builder()
165    ///     .queue_family_index(0)
166    ///     .queue_priorities(&priorities);
167    /// let queue_infos = [*queue_info];
168    /// let device_info = DeviceCreateInfo::builder()
169    ///     .queue_create_infos(&queue_infos);
170    /// let device = unsafe {
171    ///     instance.create_device(physical_device, &device_info, None)
172    /// }.expect("device creation failed");
173    /// // Use device...
174    /// unsafe { device.destroy_device(None) };
175    /// # unsafe { instance.destroy_instance(None) };
176    /// ```
177    pub unsafe fn create_device(
178        &self,
179        physical_device: vk::handles::PhysicalDevice,
180        create_info: &vk::structs::DeviceCreateInfo,
181        allocator: Option<&vk::structs::AllocationCallbacks>,
182    ) -> VkResult<Device> {
183        let fp = self
184            .commands
185            .create_device
186            .expect("vkCreateDevice not loaded");
187        let mut raw = vk::handles::Device::null();
188        // SAFETY: caller guarantees physical_device and create_info are valid.
189        let result = unsafe {
190            fp(
191                physical_device,
192                create_info,
193                allocator.map_or(std::ptr::null(), |a| a),
194                &mut raw,
195            )
196        };
197        check(result)?;
198        // SAFETY: raw is a freshly created valid device handle.
199        let device = unsafe { Device::load(raw, self.get_device_proc_addr, self._loader.clone()) };
200        Ok(device)
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use std::ffi::c_char;
208    use vk::handles::Handle;
209
210    fn fake_handle() -> vk::handles::Instance {
211        vk::handles::Instance::from_raw(0xDEAD)
212    }
213
214    /// Stub `vkGetInstanceProcAddr` that returns null for all lookups.
215    unsafe extern "system" fn mock_get_instance_proc_addr(
216        _instance: vk::handles::Instance,
217        _name: *const c_char,
218    ) -> vk::structs::PFN_vkVoidFunction {
219        None
220    }
221
222    #[test]
223    fn from_raw_parts_stores_handle() {
224        let instance =
225            unsafe { Instance::from_raw_parts(fake_handle(), Some(mock_get_instance_proc_addr)) };
226        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
227    }
228
229    #[test]
230    fn handle_returns_value_from_construction() {
231        let instance =
232            unsafe { Instance::load(fake_handle(), Some(mock_get_instance_proc_addr), None, None) };
233        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
234    }
235
236    #[test]
237    fn commands_returns_reference() {
238        let instance =
239            unsafe { Instance::load(fake_handle(), Some(mock_get_instance_proc_addr), None, None) };
240        // Commands were loaded with a null-returning proc addr, so all
241        // function pointers are None,but the reference is valid.
242        let _ = instance.commands();
243    }
244
245    // -- Rich mock for testing create_device without Vulkan ------------------
246
247    /// A `vkGetInstanceProcAddr` that returns fake fps for key commands.
248    unsafe extern "system" fn rich_instance_proc_addr(
249        _instance: vk::handles::Instance,
250        name: *const c_char,
251    ) -> vk::structs::PFN_vkVoidFunction {
252        let name = unsafe { std::ffi::CStr::from_ptr(name) };
253        match name.to_bytes() {
254            b"vkGetDeviceProcAddr" => Some(unsafe {
255                std::mem::transmute::<
256                    unsafe extern "system" fn(
257                        vk::handles::Device,
258                        *const c_char,
259                    )
260                        -> vk::structs::PFN_vkVoidFunction,
261                    unsafe extern "system" fn(),
262                >(mock_device_proc_addr)
263            }),
264            b"vkCreateDevice" => Some(unsafe {
265                std::mem::transmute::<
266                    unsafe extern "system" fn(
267                        vk::handles::PhysicalDevice,
268                        *const vk::structs::DeviceCreateInfo,
269                        *const vk::structs::AllocationCallbacks,
270                        *mut vk::handles::Device,
271                    ) -> vk::enums::Result,
272                    unsafe extern "system" fn(),
273                >(mock_create_device)
274            }),
275            _ => None,
276        }
277    }
278
279    unsafe extern "system" fn mock_device_proc_addr(
280        _device: vk::handles::Device,
281        _name: *const c_char,
282    ) -> vk::structs::PFN_vkVoidFunction {
283        None
284    }
285
286    unsafe extern "system" fn mock_create_device(
287        _physical_device: vk::handles::PhysicalDevice,
288        _p_create_info: *const vk::structs::DeviceCreateInfo,
289        _p_allocator: *const vk::structs::AllocationCallbacks,
290        p_device: *mut vk::handles::Device,
291    ) -> vk::enums::Result {
292        unsafe {
293            *p_device = std::mem::transmute::<usize, vk::handles::Device>(0xBEEF_usize);
294        }
295        vk::enums::Result::SUCCESS
296    }
297
298    fn mock_instance() -> Instance {
299        unsafe {
300            Instance::load(
301                fake_handle(),
302                Some(rich_instance_proc_addr),
303                Some(mock_device_proc_addr),
304                None,
305            )
306        }
307    }
308
309    #[test]
310    fn create_device_succeeds_with_mock() {
311        let instance = mock_instance();
312        let physical_device = vk::handles::PhysicalDevice::from_raw(0xCAFE);
313        let create_info: vk::structs::DeviceCreateInfo = unsafe { std::mem::zeroed() };
314        let device = unsafe { instance.create_device(physical_device, &create_info, None) }
315            .expect("create_device should succeed");
316        assert_eq!(device.handle().as_raw(), 0xBEEF);
317    }
318
319    #[test]
320    fn create_device_with_allocator() {
321        let instance = mock_instance();
322        let physical_device = vk::handles::PhysicalDevice::from_raw(0xCAFE);
323        let create_info: vk::structs::DeviceCreateInfo = unsafe { std::mem::zeroed() };
324        let allocator: vk::structs::AllocationCallbacks = unsafe { std::mem::zeroed() };
325        let device =
326            unsafe { instance.create_device(physical_device, &create_info, Some(&allocator)) }
327                .expect("create_device should succeed");
328        assert_eq!(device.handle().as_raw(), 0xBEEF);
329    }
330
331    #[test]
332    fn from_raw_parts_resolves_device_proc_addr() {
333        // rich_instance_proc_addr returns a non-null vkGetDeviceProcAddr,
334        // so from_raw_parts should store it.
335        let instance =
336            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr)) };
337        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
338        // Verify create_device works, proving get_device_proc_addr was resolved.
339        let physical_device = vk::handles::PhysicalDevice::from_raw(0xCAFE);
340        let create_info: vk::structs::DeviceCreateInfo = unsafe { std::mem::zeroed() };
341        let device = unsafe { instance.create_device(physical_device, &create_info, None) }
342            .expect("create_device should succeed via from_raw_parts path");
343        assert_eq!(device.handle().as_raw(), 0xBEEF);
344    }
345
346    #[test]
347    fn load_with_loader_reference() {
348        use std::ffi::{CStr, c_void};
349        struct DummyLoader;
350        unsafe impl Loader for DummyLoader {
351            unsafe fn load(&self, _name: &CStr) -> *const c_void {
352                std::ptr::null()
353            }
354        }
355        let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
356        let instance = unsafe {
357            Instance::load(
358                fake_handle(),
359                Some(mock_get_instance_proc_addr),
360                None,
361                Some(loader.clone()),
362            )
363        };
364        // The loader Arc should have 2 strong refs: our local + instance.
365        assert_eq!(Arc::strong_count(&loader), 2);
366        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
367    }
368
369    // -- Error-path mock for create_device ------------------------------------
370
371    unsafe extern "system" fn failing_instance_proc_addr(
372        _instance: vk::handles::Instance,
373        name: *const c_char,
374    ) -> vk::structs::PFN_vkVoidFunction {
375        let name = unsafe { std::ffi::CStr::from_ptr(name) };
376        match name.to_bytes() {
377            b"vkGetDeviceProcAddr" => Some(unsafe {
378                std::mem::transmute::<
379                    unsafe extern "system" fn(
380                        vk::handles::Device,
381                        *const c_char,
382                    )
383                        -> vk::structs::PFN_vkVoidFunction,
384                    unsafe extern "system" fn(),
385                >(mock_device_proc_addr)
386            }),
387            b"vkCreateDevice" => Some(unsafe {
388                std::mem::transmute::<
389                    unsafe extern "system" fn(
390                        vk::handles::PhysicalDevice,
391                        *const vk::structs::DeviceCreateInfo,
392                        *const vk::structs::AllocationCallbacks,
393                        *mut vk::handles::Device,
394                    ) -> vk::enums::Result,
395                    unsafe extern "system" fn(),
396                >(failing_create_device)
397            }),
398            _ => None,
399        }
400    }
401
402    unsafe extern "system" fn failing_create_device(
403        _physical_device: vk::handles::PhysicalDevice,
404        _p_create_info: *const vk::structs::DeviceCreateInfo,
405        _p_allocator: *const vk::structs::AllocationCallbacks,
406        _p_device: *mut vk::handles::Device,
407    ) -> vk::enums::Result {
408        vk::enums::Result::ERROR_INITIALIZATION_FAILED
409    }
410
411    #[test]
412    fn from_raw_parts_stores_no_loader() {
413        let instance =
414            unsafe { Instance::from_raw_parts(fake_handle(), Some(mock_get_instance_proc_addr)) };
415        // from_raw_parts passes None for loader; verify handle is stored correctly.
416        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
417    }
418
419    #[test]
420    fn load_with_loader_keeps_arc_alive() {
421        use std::ffi::{CStr, c_void};
422        struct DummyLoader;
423        unsafe impl Loader for DummyLoader {
424            unsafe fn load(&self, _name: &CStr) -> *const c_void {
425                std::ptr::null()
426            }
427        }
428        let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
429        let weak = Arc::downgrade(&loader);
430        let instance = unsafe {
431            Instance::load(
432                fake_handle(),
433                Some(mock_get_instance_proc_addr),
434                None,
435                Some(loader),
436            )
437        };
438        assert!(weak.upgrade().is_some(), "loader should still be alive");
439        drop(instance);
440        assert!(weak.upgrade().is_none(), "loader should be dropped");
441    }
442
443    #[test]
444    fn commands_all_none_from_null_mock() {
445        let instance =
446            unsafe { Instance::load(fake_handle(), Some(mock_get_instance_proc_addr), None, None) };
447        // All commands should be None since mock returns null for everything.
448        assert!(instance.commands().enumerate_physical_devices.is_none());
449        assert!(instance.commands().destroy_instance.is_none());
450        assert!(instance.commands().create_device.is_none());
451    }
452
453    // -- Rich mock that resolves additional instance commands ------------------
454
455    /// Mock enumerate_physical_devices: returns 2 fake physical devices.
456    unsafe extern "system" fn mock_enumerate_physical_devices(
457        _instance: vk::handles::Instance,
458        p_count: *mut u32,
459        p_devices: *mut vk::handles::PhysicalDevice,
460    ) -> vk::enums::Result {
461        unsafe { *p_count = 2 };
462        if !p_devices.is_null() {
463            unsafe {
464                *p_devices = vk::handles::PhysicalDevice::from_raw(0xAA);
465                *p_devices.add(1) = vk::handles::PhysicalDevice::from_raw(0xBB);
466            }
467        }
468        vk::enums::Result::SUCCESS
469    }
470
471    unsafe extern "system" fn mock_destroy_instance(
472        _instance: vk::handles::Instance,
473        _p_allocator: *const vk::structs::AllocationCallbacks,
474    ) {
475    }
476
477    /// Richer `vkGetInstanceProcAddr` that resolves core instance commands.
478    unsafe extern "system" fn rich_instance_proc_addr_v2(
479        _instance: vk::handles::Instance,
480        name: *const c_char,
481    ) -> vk::structs::PFN_vkVoidFunction {
482        let name = unsafe { std::ffi::CStr::from_ptr(name) };
483        match name.to_bytes() {
484            b"vkGetDeviceProcAddr" => Some(unsafe {
485                std::mem::transmute::<
486                    unsafe extern "system" fn(
487                        vk::handles::Device,
488                        *const c_char,
489                    )
490                        -> vk::structs::PFN_vkVoidFunction,
491                    unsafe extern "system" fn(),
492                >(mock_device_proc_addr)
493            }),
494            b"vkCreateDevice" => Some(unsafe {
495                std::mem::transmute::<
496                    unsafe extern "system" fn(
497                        vk::handles::PhysicalDevice,
498                        *const vk::structs::DeviceCreateInfo,
499                        *const vk::structs::AllocationCallbacks,
500                        *mut vk::handles::Device,
501                    ) -> vk::enums::Result,
502                    unsafe extern "system" fn(),
503                >(mock_create_device)
504            }),
505            b"vkEnumeratePhysicalDevices" => Some(unsafe {
506                std::mem::transmute::<
507                    unsafe extern "system" fn(
508                        vk::handles::Instance,
509                        *mut u32,
510                        *mut vk::handles::PhysicalDevice,
511                    ) -> vk::enums::Result,
512                    unsafe extern "system" fn(),
513                >(mock_enumerate_physical_devices)
514            }),
515            b"vkDestroyInstance" => Some(unsafe {
516                std::mem::transmute::<
517                    unsafe extern "system" fn(
518                        vk::handles::Instance,
519                        *const vk::structs::AllocationCallbacks,
520                    ),
521                    unsafe extern "system" fn(),
522                >(mock_destroy_instance)
523            }),
524            _ => None,
525        }
526    }
527
528    #[test]
529    fn from_raw_parts_populates_commands_from_rich_mock() {
530        let instance =
531            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
532        assert!(instance.commands().enumerate_physical_devices.is_some());
533        assert!(instance.commands().destroy_instance.is_some());
534        assert!(instance.commands().create_device.is_some());
535        // Commands not returned by the mock should be None.
536        assert!(instance.commands().get_physical_device_properties.is_none());
537    }
538
539    #[test]
540    fn enumerate_physical_devices_with_mock() {
541        let instance =
542            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
543        let devices =
544            unsafe { instance.enumerate_physical_devices() }.expect("enumerate should succeed");
545        assert_eq!(devices.len(), 2);
546        assert_eq!(devices[0].as_raw(), 0xAA);
547        assert_eq!(devices[1].as_raw(), 0xBB);
548    }
549
550    #[test]
551    fn destroy_instance_with_mock() {
552        let instance =
553            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
554        // Should not panic; mock destroy is a no-op.
555        unsafe { instance.destroy_instance(None) };
556    }
557
558    #[test]
559    fn create_device_from_raw_parts_instance() {
560        // Verify the full flow: from_raw_parts resolves get_device_proc_addr,
561        // then create_device uses it to load device commands.
562        let instance =
563            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
564        let create_info: vk::structs::DeviceCreateInfo = unsafe { std::mem::zeroed() };
565        let device = unsafe {
566            instance.create_device(
567                vk::handles::PhysicalDevice::from_raw(0xAA),
568                &create_info,
569                None,
570            )
571        }
572        .expect("create_device should succeed");
573        assert_eq!(device.handle().as_raw(), 0xBEEF);
574    }
575
576    #[test]
577    fn create_device_propagates_error() {
578        let instance = unsafe {
579            Instance::load(
580                fake_handle(),
581                Some(failing_instance_proc_addr),
582                Some(mock_device_proc_addr),
583                None,
584            )
585        };
586        let physical_device = vk::handles::PhysicalDevice::from_raw(0xCAFE);
587        let create_info: vk::structs::DeviceCreateInfo = unsafe { std::mem::zeroed() };
588        let result = unsafe { instance.create_device(physical_device, &create_info, None) };
589        match result {
590            Err(e) => assert_eq!(e, vk::enums::Result::ERROR_INITIALIZATION_FAILED),
591            Ok(_) => panic!("expected error, got Ok"),
592        }
593    }
594
595    #[test]
596    #[ignore] // requires Vulkan runtime
597    fn enumerate_physical_devices_returns_at_least_one() {
598        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
599        let instance = create_real_instance();
600        let devices = unsafe { instance.enumerate_physical_devices() }
601            .expect("enumerate_physical_devices failed");
602        assert!(!devices.is_empty(), "expected at least one physical device");
603    }
604
605    #[test]
606    #[ignore] // requires Vulkan runtime
607    fn get_physical_device_properties_succeeds() {
608        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
609        let instance = create_real_instance();
610        let devices = unsafe { instance.enumerate_physical_devices() }
611            .expect("enumerate_physical_devices failed");
612        let props = unsafe { instance.get_physical_device_properties(devices[0]) };
613        let name_bytes: Vec<u8> = props
614            .device_name
615            .iter()
616            .take_while(|&&c| c != 0)
617            .map(|&c| c as u8)
618            .collect();
619        let name = String::from_utf8_lossy(&name_bytes);
620        println!("GPU: {name}");
621        assert!(!name.is_empty());
622    }
623
624    #[test]
625    #[ignore] // requires Vulkan runtime
626    fn get_physical_device_queue_family_properties_returns_at_least_one() {
627        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
628        let instance = create_real_instance();
629        let devices = unsafe { instance.enumerate_physical_devices() }
630            .expect("enumerate_physical_devices failed");
631        let families = unsafe { instance.get_physical_device_queue_family_properties(devices[0]) };
632        assert!(!families.is_empty(), "expected at least one queue family");
633    }
634
635    #[test]
636    #[ignore] // requires Vulkan runtime
637    fn get_physical_device_memory_properties_succeeds() {
638        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
639        let instance = create_real_instance();
640        let devices = unsafe { instance.enumerate_physical_devices() }
641            .expect("enumerate_physical_devices failed");
642        let mem_props = unsafe { instance.get_physical_device_memory_properties(devices[0]) };
643        assert!(
644            mem_props.memory_type_count > 0,
645            "expected at least one memory type"
646        );
647        assert!(
648            mem_props.memory_heap_count > 0,
649            "expected at least one memory heap"
650        );
651    }
652
653    #[test]
654    #[ignore] // requires Vulkan runtime
655    fn get_physical_device_features_succeeds() {
656        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
657        let instance = create_real_instance();
658        let devices = unsafe { instance.enumerate_physical_devices() }
659            .expect("enumerate_physical_devices failed");
660        // Just verify the call completes without crashing; feature
661        // availability is driver-dependent.
662        let _features = unsafe { instance.get_physical_device_features(devices[0]) };
663    }
664
665    fn create_real_instance() -> Instance {
666        use crate::entry::Entry;
667        use crate::loader::LibloadingLoader;
668
669        let loader = LibloadingLoader::new().expect("failed to load Vulkan");
670        let entry = unsafe { Entry::new(loader) }.expect("failed to create Entry");
671
672        // VK_MAKE_API_VERSION(0, 1, 0, 0)
673        let api_version_1_0 = 1u32 << 22;
674
675        let app_info = vk::structs::ApplicationInfo {
676            s_type: vk::enums::StructureType::APPLICATION_INFO,
677            p_next: std::ptr::null(),
678            p_application_name: std::ptr::null(),
679            application_version: 0,
680            p_engine_name: std::ptr::null(),
681            engine_version: 0,
682            api_version: api_version_1_0,
683        };
684        let create_info = vk::structs::InstanceCreateInfo {
685            s_type: vk::enums::StructureType::INSTANCE_CREATE_INFO,
686            p_next: std::ptr::null(),
687            flags: vk::bitmasks::InstanceCreateFlagBits::empty(),
688            p_application_info: &app_info,
689            enabled_layer_count: 0,
690            pp_enabled_layer_names: std::ptr::null(),
691            enabled_extension_count: 0,
692            pp_enabled_extension_names: std::ptr::null(),
693        };
694
695        unsafe { entry.create_instance(&create_info, None) }.expect("failed to create instance")
696    }
697}