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::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::*;
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::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::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::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::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::*;
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::PhysicalDevice,
180        create_info: &vk::DeviceCreateInfo,
181        allocator: Option<&vk::AllocationCallbacks>,
182    ) -> VkResult<Device> {
183        let fp = self
184            .commands
185            .create_device
186            .expect("vkCreateDevice not loaded");
187        let mut raw = vk::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::Handle;
209
210    fn fake_handle() -> vk::Instance {
211        vk::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::Instance,
217        _name: *const c_char,
218    ) -> vk::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::Instance,
250        name: *const c_char,
251    ) -> vk::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(vk::Device, *const c_char) -> vk::PFN_vkVoidFunction,
257                    unsafe extern "system" fn(),
258                >(mock_device_proc_addr)
259            }),
260            b"vkCreateDevice" => Some(unsafe {
261                std::mem::transmute::<
262                    unsafe extern "system" fn(
263                        vk::PhysicalDevice,
264                        *const vk::DeviceCreateInfo,
265                        *const vk::AllocationCallbacks,
266                        *mut vk::Device,
267                    ) -> vk::Result,
268                    unsafe extern "system" fn(),
269                >(mock_create_device)
270            }),
271            _ => None,
272        }
273    }
274
275    unsafe extern "system" fn mock_device_proc_addr(
276        _device: vk::Device,
277        _name: *const c_char,
278    ) -> vk::PFN_vkVoidFunction {
279        None
280    }
281
282    unsafe extern "system" fn mock_create_device(
283        _physical_device: vk::PhysicalDevice,
284        _p_create_info: *const vk::DeviceCreateInfo,
285        _p_allocator: *const vk::AllocationCallbacks,
286        p_device: *mut vk::Device,
287    ) -> vk::Result {
288        unsafe {
289            *p_device = std::mem::transmute::<usize, vk::Device>(0xBEEF_usize);
290        }
291        vk::Result::SUCCESS
292    }
293
294    fn mock_instance() -> Instance {
295        unsafe {
296            Instance::load(
297                fake_handle(),
298                Some(rich_instance_proc_addr),
299                Some(mock_device_proc_addr),
300                None,
301            )
302        }
303    }
304
305    #[test]
306    fn create_device_succeeds_with_mock() {
307        let instance = mock_instance();
308        let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
309        let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
310        let device = unsafe { instance.create_device(physical_device, &create_info, None) }
311            .expect("create_device should succeed");
312        assert_eq!(device.handle().as_raw(), 0xBEEF);
313    }
314
315    #[test]
316    fn create_device_with_allocator() {
317        let instance = mock_instance();
318        let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
319        let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
320        let allocator: vk::AllocationCallbacks = unsafe { std::mem::zeroed() };
321        let device =
322            unsafe { instance.create_device(physical_device, &create_info, Some(&allocator)) }
323                .expect("create_device should succeed");
324        assert_eq!(device.handle().as_raw(), 0xBEEF);
325    }
326
327    #[test]
328    fn from_raw_parts_resolves_device_proc_addr() {
329        // rich_instance_proc_addr returns a non-null vkGetDeviceProcAddr,
330        // so from_raw_parts should store it.
331        let instance =
332            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr)) };
333        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
334        // Verify create_device works, proving get_device_proc_addr was resolved.
335        let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
336        let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
337        let device = unsafe { instance.create_device(physical_device, &create_info, None) }
338            .expect("create_device should succeed via from_raw_parts path");
339        assert_eq!(device.handle().as_raw(), 0xBEEF);
340    }
341
342    #[test]
343    fn load_with_loader_reference() {
344        use std::ffi::{CStr, c_void};
345        struct DummyLoader;
346        unsafe impl Loader for DummyLoader {
347            unsafe fn load(&self, _name: &CStr) -> *const c_void {
348                std::ptr::null()
349            }
350        }
351        let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
352        let instance = unsafe {
353            Instance::load(
354                fake_handle(),
355                Some(mock_get_instance_proc_addr),
356                None,
357                Some(loader.clone()),
358            )
359        };
360        // The loader Arc should have 2 strong refs: our local + instance.
361        assert_eq!(Arc::strong_count(&loader), 2);
362        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
363    }
364
365    // -- Error-path mock for create_device ------------------------------------
366
367    unsafe extern "system" fn failing_instance_proc_addr(
368        _instance: vk::Instance,
369        name: *const c_char,
370    ) -> vk::PFN_vkVoidFunction {
371        let name = unsafe { std::ffi::CStr::from_ptr(name) };
372        match name.to_bytes() {
373            b"vkGetDeviceProcAddr" => Some(unsafe {
374                std::mem::transmute::<
375                    unsafe extern "system" fn(vk::Device, *const c_char) -> vk::PFN_vkVoidFunction,
376                    unsafe extern "system" fn(),
377                >(mock_device_proc_addr)
378            }),
379            b"vkCreateDevice" => Some(unsafe {
380                std::mem::transmute::<
381                    unsafe extern "system" fn(
382                        vk::PhysicalDevice,
383                        *const vk::DeviceCreateInfo,
384                        *const vk::AllocationCallbacks,
385                        *mut vk::Device,
386                    ) -> vk::Result,
387                    unsafe extern "system" fn(),
388                >(failing_create_device)
389            }),
390            _ => None,
391        }
392    }
393
394    unsafe extern "system" fn failing_create_device(
395        _physical_device: vk::PhysicalDevice,
396        _p_create_info: *const vk::DeviceCreateInfo,
397        _p_allocator: *const vk::AllocationCallbacks,
398        _p_device: *mut vk::Device,
399    ) -> vk::Result {
400        vk::Result::ERROR_INITIALIZATION_FAILED
401    }
402
403    #[test]
404    fn from_raw_parts_stores_no_loader() {
405        let instance =
406            unsafe { Instance::from_raw_parts(fake_handle(), Some(mock_get_instance_proc_addr)) };
407        // from_raw_parts passes None for loader; verify handle is stored correctly.
408        assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
409    }
410
411    #[test]
412    fn load_with_loader_keeps_arc_alive() {
413        use std::ffi::{CStr, c_void};
414        struct DummyLoader;
415        unsafe impl Loader for DummyLoader {
416            unsafe fn load(&self, _name: &CStr) -> *const c_void {
417                std::ptr::null()
418            }
419        }
420        let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
421        let weak = Arc::downgrade(&loader);
422        let instance = unsafe {
423            Instance::load(
424                fake_handle(),
425                Some(mock_get_instance_proc_addr),
426                None,
427                Some(loader),
428            )
429        };
430        assert!(weak.upgrade().is_some(), "loader should still be alive");
431        drop(instance);
432        assert!(weak.upgrade().is_none(), "loader should be dropped");
433    }
434
435    #[test]
436    fn commands_all_none_from_null_mock() {
437        let instance =
438            unsafe { Instance::load(fake_handle(), Some(mock_get_instance_proc_addr), None, None) };
439        // All commands should be None since mock returns null for everything.
440        assert!(instance.commands().enumerate_physical_devices.is_none());
441        assert!(instance.commands().destroy_instance.is_none());
442        assert!(instance.commands().create_device.is_none());
443    }
444
445    // -- Rich mock that resolves additional instance commands ------------------
446
447    /// Mock enumerate_physical_devices: returns 2 fake physical devices.
448    unsafe extern "system" fn mock_enumerate_physical_devices(
449        _instance: vk::Instance,
450        p_count: *mut u32,
451        p_devices: *mut vk::PhysicalDevice,
452    ) -> vk::Result {
453        unsafe { *p_count = 2 };
454        if !p_devices.is_null() {
455            unsafe {
456                *p_devices = vk::PhysicalDevice::from_raw(0xAA);
457                *p_devices.add(1) = vk::PhysicalDevice::from_raw(0xBB);
458            }
459        }
460        vk::Result::SUCCESS
461    }
462
463    unsafe extern "system" fn mock_destroy_instance(
464        _instance: vk::Instance,
465        _p_allocator: *const vk::AllocationCallbacks,
466    ) {
467    }
468
469    /// Richer `vkGetInstanceProcAddr` that resolves core instance commands.
470    unsafe extern "system" fn rich_instance_proc_addr_v2(
471        _instance: vk::Instance,
472        name: *const c_char,
473    ) -> vk::PFN_vkVoidFunction {
474        let name = unsafe { std::ffi::CStr::from_ptr(name) };
475        match name.to_bytes() {
476            b"vkGetDeviceProcAddr" => Some(unsafe {
477                std::mem::transmute::<
478                    unsafe extern "system" fn(vk::Device, *const c_char) -> vk::PFN_vkVoidFunction,
479                    unsafe extern "system" fn(),
480                >(mock_device_proc_addr)
481            }),
482            b"vkCreateDevice" => Some(unsafe {
483                std::mem::transmute::<
484                    unsafe extern "system" fn(
485                        vk::PhysicalDevice,
486                        *const vk::DeviceCreateInfo,
487                        *const vk::AllocationCallbacks,
488                        *mut vk::Device,
489                    ) -> vk::Result,
490                    unsafe extern "system" fn(),
491                >(mock_create_device)
492            }),
493            b"vkEnumeratePhysicalDevices" => Some(unsafe {
494                std::mem::transmute::<
495                    unsafe extern "system" fn(
496                        vk::Instance,
497                        *mut u32,
498                        *mut vk::PhysicalDevice,
499                    ) -> vk::Result,
500                    unsafe extern "system" fn(),
501                >(mock_enumerate_physical_devices)
502            }),
503            b"vkDestroyInstance" => Some(unsafe {
504                std::mem::transmute::<
505                    unsafe extern "system" fn(vk::Instance, *const vk::AllocationCallbacks),
506                    unsafe extern "system" fn(),
507                >(mock_destroy_instance)
508            }),
509            _ => None,
510        }
511    }
512
513    #[test]
514    fn from_raw_parts_populates_commands_from_rich_mock() {
515        let instance =
516            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
517        assert!(instance.commands().enumerate_physical_devices.is_some());
518        assert!(instance.commands().destroy_instance.is_some());
519        assert!(instance.commands().create_device.is_some());
520        // Commands not returned by the mock should be None.
521        assert!(instance.commands().get_physical_device_properties.is_none());
522    }
523
524    #[test]
525    fn enumerate_physical_devices_with_mock() {
526        let instance =
527            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
528        let devices =
529            unsafe { instance.enumerate_physical_devices() }.expect("enumerate should succeed");
530        assert_eq!(devices.len(), 2);
531        assert_eq!(devices[0].as_raw(), 0xAA);
532        assert_eq!(devices[1].as_raw(), 0xBB);
533    }
534
535    #[test]
536    fn destroy_instance_with_mock() {
537        let instance =
538            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
539        // Should not panic; mock destroy is a no-op.
540        unsafe { instance.destroy_instance(None) };
541    }
542
543    #[test]
544    fn create_device_from_raw_parts_instance() {
545        // Verify the full flow: from_raw_parts resolves get_device_proc_addr,
546        // then create_device uses it to load device commands.
547        let instance =
548            unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
549        let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
550        let device = unsafe {
551            instance.create_device(vk::PhysicalDevice::from_raw(0xAA), &create_info, None)
552        }
553        .expect("create_device should succeed");
554        assert_eq!(device.handle().as_raw(), 0xBEEF);
555    }
556
557    #[test]
558    fn create_device_propagates_error() {
559        let instance = unsafe {
560            Instance::load(
561                fake_handle(),
562                Some(failing_instance_proc_addr),
563                Some(mock_device_proc_addr),
564                None,
565            )
566        };
567        let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
568        let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
569        let result = unsafe { instance.create_device(physical_device, &create_info, None) };
570        match result {
571            Err(e) => assert_eq!(e, vk::Result::ERROR_INITIALIZATION_FAILED),
572            Ok(_) => panic!("expected error, got Ok"),
573        }
574    }
575
576    #[test]
577    #[ignore] // requires Vulkan runtime
578    fn enumerate_physical_devices_returns_at_least_one() {
579        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
580        let instance = create_real_instance();
581        let devices = unsafe { instance.enumerate_physical_devices() }
582            .expect("enumerate_physical_devices failed");
583        assert!(!devices.is_empty(), "expected at least one physical device");
584    }
585
586    #[test]
587    #[ignore] // requires Vulkan runtime
588    fn get_physical_device_properties_succeeds() {
589        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
590        let instance = create_real_instance();
591        let devices = unsafe { instance.enumerate_physical_devices() }
592            .expect("enumerate_physical_devices failed");
593        let props = unsafe { instance.get_physical_device_properties(devices[0]) };
594        let name_bytes: Vec<u8> = props
595            .device_name
596            .iter()
597            .take_while(|&&c| c != 0)
598            .map(|&c| c as u8)
599            .collect();
600        let name = String::from_utf8_lossy(&name_bytes);
601        println!("GPU: {name}");
602        assert!(!name.is_empty());
603    }
604
605    #[test]
606    #[ignore] // requires Vulkan runtime
607    fn get_physical_device_queue_family_properties_returns_at_least_one() {
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 families = unsafe { instance.get_physical_device_queue_family_properties(devices[0]) };
613        assert!(!families.is_empty(), "expected at least one queue family");
614    }
615
616    #[test]
617    #[ignore] // requires Vulkan runtime
618    fn get_physical_device_memory_properties_succeeds() {
619        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
620        let instance = create_real_instance();
621        let devices = unsafe { instance.enumerate_physical_devices() }
622            .expect("enumerate_physical_devices failed");
623        let mem_props = unsafe { instance.get_physical_device_memory_properties(devices[0]) };
624        assert!(
625            mem_props.memory_type_count > 0,
626            "expected at least one memory type"
627        );
628        assert!(
629            mem_props.memory_heap_count > 0,
630            "expected at least one memory heap"
631        );
632    }
633
634    #[test]
635    #[ignore] // requires Vulkan runtime
636    fn get_physical_device_features_succeeds() {
637        let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
638        let instance = create_real_instance();
639        let devices = unsafe { instance.enumerate_physical_devices() }
640            .expect("enumerate_physical_devices failed");
641        // Just verify the call completes without crashing; feature
642        // availability is driver-dependent.
643        let _features = unsafe { instance.get_physical_device_features(devices[0]) };
644    }
645
646    fn create_real_instance() -> Instance {
647        use crate::entry::Entry;
648        use crate::loader::LibloadingLoader;
649
650        let loader = LibloadingLoader::new().expect("failed to load Vulkan");
651        let entry = unsafe { Entry::new(loader) }.expect("failed to create Entry");
652
653        let api_version_1_0 = crate::Version::new(1, 0, 0).to_raw();
654
655        let app_info = vk::ApplicationInfo {
656            s_type: vk::StructureType::APPLICATION_INFO,
657            p_next: std::ptr::null(),
658            p_application_name: std::ptr::null(),
659            application_version: 0,
660            p_engine_name: std::ptr::null(),
661            engine_version: 0,
662            api_version: api_version_1_0,
663        };
664        let create_info = vk::InstanceCreateInfo {
665            s_type: vk::StructureType::INSTANCE_CREATE_INFO,
666            p_next: std::ptr::null(),
667            flags: vk::InstanceCreateFlagBits::empty(),
668            p_application_info: &app_info,
669            enabled_layer_count: 0,
670            pp_enabled_layer_names: std::ptr::null(),
671            enabled_extension_count: 0,
672            pp_enabled_extension_names: std::ptr::null(),
673        };
674
675        unsafe { entry.create_instance(&create_info, None) }.expect("failed to create instance")
676    }
677}