Skip to main content

vk_graph/driver/
instance.rs

1//! Vulkan initialization types.
2
3use {
4    super::{DriverError, physical_device::PhysicalDevice},
5    ash::{
6        ext, khr,
7        vk::{self, Handle},
8    },
9    derive_builder::Builder,
10    log::{debug, error, trace, warn},
11    raw_window_handle::{HasDisplayHandle, RawDisplayHandle},
12    std::{
13        collections::HashSet,
14        error::Error,
15        ffi::CStr,
16        fmt::{Debug, Display, Formatter},
17        ops::Deref,
18        sync::Arc,
19        thread::panicking,
20    },
21};
22
23#[cfg(any(not(target_os = "macos"), feature = "loaded"))]
24use {
25    log::{Level, Metadata, info, logger},
26    std::{
27        env::var,
28        ffi::c_void,
29        io::{IsTerminal, stderr},
30        process::{abort, id},
31        thread::{current, park},
32    },
33};
34
35#[cfg(any(not(target_os = "macos"), feature = "loaded"))]
36const SKIP_VALIDATION_PARK_ENV: &str = "VK_GRAPH_SKIP_VALIDATION_PARK";
37
38#[cfg(target_os = "macos")]
39use std::env::set_var;
40
41#[cfg(any(not(target_os = "macos"), feature = "loaded"))]
42unsafe extern "system" fn debug_callback(
43    message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
44    _message_types: vk::DebugUtilsMessageTypeFlagsEXT,
45    callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
46    _user_data: *mut c_void,
47) -> vk::Bool32 {
48    if panicking() {
49        return vk::FALSE;
50    }
51
52    assert!(!callback_data.is_null());
53
54    let callback_data = unsafe { &*callback_data };
55    let message = if callback_data.p_message.is_null() {
56        "<missing Vulkan validation message>"
57    } else {
58        unsafe { CStr::from_ptr(callback_data.p_message) }
59            .to_str()
60            .unwrap_or("<invalid Vulkan validation message>")
61    };
62
63    if !callback_data.p_message_id_name.is_null() {
64        let vuid = unsafe { CStr::from_ptr(callback_data.p_message_id_name) }
65            .to_str()
66            .unwrap_or("<invalid Vulkan validation message ID name>");
67        if vuid != "Loader Message" {
68            debug!("{vuid}");
69        }
70    };
71
72    let is_error = message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR);
73
74    if is_error {
75        error!("{message}");
76    } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING)
77        && !var("VK_GRAPH_DEBUG_IGNORE_WARNING")
78            .map(var_value_is_set)
79            .unwrap_or_default()
80    {
81        warn!("{message}");
82    } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::INFO)
83        && !var("VK_GRAPH_DEBUG_IGNORE_INFO")
84            .map(var_value_is_set)
85            .unwrap_or_default()
86    {
87        info!("{message}");
88    } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE)
89        && !var("VK_GRAPH_DEBUG_IGNORE_VERBOSE")
90            .map(var_value_is_set)
91            .unwrap_or_default()
92    {
93        debug!("{message}");
94    }
95
96    if !is_error {
97        return vk::FALSE;
98    }
99
100    if !logger().enabled(&Metadata::builder().level(Level::Debug).build())
101        || var("RUST_LOG")
102            .map(|rust_log| rust_log.is_empty())
103            .unwrap_or(true)
104    {
105        eprintln!(
106            "note: run with `RUST_LOG=trace` environment variable to display more information"
107        );
108        eprintln!("note: see https://github.com/rust-lang/log#in-executables");
109        abort()
110    }
111
112    if current().name() != Some("main") {
113        warn!("invalid validation callback thread: child thread")
114    }
115
116    if var(SKIP_VALIDATION_PARK_ENV)
117        .map(var_value_is_set)
118        .unwrap_or_default()
119    {
120        warn!("validation callback park skipped; execution will continue");
121
122        return vk::FALSE;
123    }
124
125    if !stderr().is_terminal() {
126        warn!("validation callback park skipped; stderr is not an interactive terminal");
127
128        return vk::FALSE;
129    }
130
131    debug!(
132        "parking validation callback thread `{}` for debugger attach to pid {}",
133        current().name().unwrap_or_default(),
134        id()
135    );
136
137    logger().flush();
138    park();
139
140    vk::FALSE
141}
142
143fn debug_extension_names() -> &'static [&'static CStr] {
144    #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
145    return &[ext::debug_utils::NAME];
146
147    #[cfg(all(target_os = "macos", not(feature = "loaded")))]
148    return &[];
149}
150
151fn debug_layer_names() -> &'static [&'static CStr] {
152    #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
153    return &[c"VK_LAYER_KHRONOS_validation"];
154
155    #[cfg(all(target_os = "macos", not(feature = "loaded")))]
156    return &[];
157}
158
159// Copied from ash_window::enumerate_required_extensions to change the signature
160fn display_extension_names(
161    display_handle: RawDisplayHandle,
162) -> Result<&'static [&'static CStr], DriverError> {
163    let extensions = match display_handle {
164        RawDisplayHandle::Windows(_) => &[khr::surface::NAME, khr::win32_surface::NAME],
165        RawDisplayHandle::Wayland(_) => &[khr::surface::NAME, khr::wayland_surface::NAME],
166        RawDisplayHandle::Xlib(_) => &[khr::surface::NAME, khr::xlib_surface::NAME],
167        RawDisplayHandle::Xcb(_) => &[khr::surface::NAME, khr::xcb_surface::NAME],
168        RawDisplayHandle::Android(_) => &[khr::surface::NAME, khr::android_surface::NAME],
169        RawDisplayHandle::AppKit(_) | RawDisplayHandle::UiKit(_) => {
170            &[khr::surface::NAME, ext::metal_surface::NAME]
171        }
172        _ => {
173            warn!("unsupported display handle type: {display_handle:?}");
174
175            return Err(DriverError::Unsupported);
176        }
177    };
178
179    Ok(extensions)
180}
181
182/*
183Estimates surface extension support.
184
185Imported instances do not expose their enabled extension list, so we infer support by checking that
186the VK_KHR_surface entry points resolve for this instance handle.
187*/
188fn has_vk_khr_surface(entry: &ash::Entry, instance: vk::Instance) -> bool {
189    [
190        c"vkGetPhysicalDeviceSurfaceCapabilitiesKHR",
191        c"vkGetPhysicalDeviceSurfaceFormatsKHR",
192        c"vkGetPhysicalDeviceSurfacePresentModesKHR",
193        c"vkGetPhysicalDeviceSurfaceSupportKHR",
194        c"vkDestroySurfaceKHR",
195    ]
196    .into_iter()
197    .all(|name| unsafe {
198        entry
199            .get_instance_proc_addr(instance, name.as_ptr())
200            .is_some()
201    })
202}
203
204fn var_value_is_set(val: String) -> bool {
205    !matches!(val.as_str(), "" | "0" | "false" | "False" | "FALSE")
206}
207
208/// Vulkan API version.
209///
210/// See [`VkApplicationInfo::apiVersion`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkApplicationInfo.html).
211#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, PartialOrd, Ord)]
212pub enum ApiVersion {
213    /// Version `1.2`.
214    Vulkan12,
215
216    /// Version `1.3`.
217    ///
218    /// This is the default value.
219    #[default]
220    Vulkan13,
221}
222
223impl ApiVersion {
224    /// Returns a version parsed from a native Vulkan value.
225    pub fn try_parse_vk_api_version(version: u32) -> Result<Self, ParseApiVersionError> {
226        Self::try_from(version)
227    }
228
229    /// Vulkan API major version number component. Ex: `vX.0.0-0`.
230    ///
231    /// Always one.
232    pub fn major(self) -> u32 {
233        1
234    }
235
236    /// Vulkan API minor version number component. Ex: `v0.X.0-0`.
237    pub fn minor(self) -> u32 {
238        match self {
239            Self::Vulkan12 => 2,
240            Self::Vulkan13 => 3,
241        }
242    }
243
244    /// Vulkan API patch version number component. Ex: `v0.0.X-0`.
245    ///
246    /// Always zero.
247    pub fn patch(self) -> u32 {
248        0
249    }
250
251    /// Returns a native Vulkan value.
252    pub fn to_vk_api_version(self) -> u32 {
253        self.into()
254    }
255
256    /// Vulkan API variant version number component. Ex: `v0.0.0-X`.
257    ///
258    /// Always zero.
259    pub fn variant(self) -> u32 {
260        0
261    }
262}
263
264impl From<ApiVersion> for u32 {
265    fn from(val: ApiVersion) -> Self {
266        vk::make_api_version(val.variant(), val.major(), val.minor(), val.patch())
267    }
268}
269
270impl TryFrom<u32> for ApiVersion {
271    type Error = ParseApiVersionError;
272
273    fn try_from(val: u32) -> Result<Self, Self::Error> {
274        let major = vk::api_version_major(val);
275        let minor = vk::api_version_minor(val);
276        let patch = vk::api_version_patch(val);
277        let variant = vk::api_version_variant(val);
278
279        if variant != 0 || major != 1 || minor < 2 {
280            return Err(ParseApiVersionError {
281                major,
282                minor,
283                patch,
284                variant,
285            });
286        }
287
288        Ok(match minor {
289            2 => ApiVersion::Vulkan12,
290            _ => ApiVersion::Vulkan13,
291        })
292    }
293}
294
295/// There is no global state in Vulkan and all per-application state is stored in a `VkInstance`
296/// object.
297///
298/// Creating an `Instance` initializes the Vulkan library and allows the application to pass
299/// information about itself to the implementation.
300///
301/// See [`VkInstance`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkInstance.html).
302#[read_only::embed]
303#[allow(private_interfaces)]
304pub struct Instance {
305    /// Information used to create this resource.
306    ///
307    /// _Note:_ This field is read-only.
308    #[readonly]
309    pub info: InstanceInfo,
310
311    #[readonly]
312    pub(self) inner: Arc<InstanceInner>,
313
314    /// True if `VK_KHR_surface` is enabled on this instance.
315    ///
316    /// _Note:_ This field is read-only.
317    #[readonly]
318    pub khr_surface: bool,
319}
320
321impl Clone for Instance {
322    fn clone(&self) -> Self {
323        Self {
324            read_only: ReadOnlyInstance {
325                info: self.info,
326                inner: self.inner.clone(),
327                khr_surface: self.khr_surface,
328            },
329        }
330    }
331}
332
333impl Instance {
334    /// Default Vulkan API version requested when creating an instance.
335    pub const DEFAULT_API_VERSION: ApiVersion = ApiVersion::Vulkan13;
336
337    /// Creates a new Vulkan instance.
338    ///
339    /// This constructor is intended for headless or manually managed setups. It does not infer or
340    /// enable display platform surface extensions. Use [`Self::try_from_display`] when the
341    /// resulting instance must be capable of later surface creation.
342    ///
343    /// See [`vkCreateInstance`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkCreateInstance.html).
344    #[profiling::function]
345    pub fn create(info: impl Into<InstanceInfo>) -> Result<Self, DriverError> {
346        Self::create_with_extension_names(info.into(), &[])
347    }
348
349    fn create_with_extension_names(
350        info: InstanceInfo,
351        extra_extension_names: &[&CStr],
352    ) -> Result<Self, DriverError> {
353        if info.debug && debug_extension_names().is_empty() {
354            error!("debug mode requires VK_EXT_debug_utils support");
355
356            return Err(DriverError::Unsupported);
357        }
358
359        // Required to enable non-uniform descriptor indexing (bindless)
360        #[cfg(target_os = "macos")]
361        unsafe {
362            set_var("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1");
363        }
364
365        // Link the Vulkan loader dynamically (default feature)
366        #[cfg(feature = "loaded")]
367        let entry = unsafe {
368            ash::Entry::load().map_err(|err| {
369                error!("unable to load Vulkan driver: {err}");
370
371                DriverError::Unsupported
372            })?
373        };
374
375        // Link the Vulkan loader statically if explicitly requested
376        #[cfg(not(feature = "loaded"))]
377        let entry = {
378            #[cfg(not(target_os = "macos"))]
379            let entry = ash::Entry::linked();
380
381            // On macOS, by default link molten-vk statically using ash-molten
382            #[cfg(target_os = "macos")]
383            let entry = ash_molten::load();
384        };
385
386        let mut extension_names = info
387            .extension_names
388            .iter()
389            .chain(extra_extension_names)
390            .copied()
391            .collect::<HashSet<_>>();
392
393        if info.debug {
394            extension_names.extend(debug_extension_names());
395        }
396
397        /*
398        If linking dynamically on macOS, we require a few additional extensions. Based on
399        "Encountered VK_ERROR_INCOMPATIBLE_DRIVER" section in:
400        https://vulkan.lunarg.com/doc/view/latest/mac/getting_started.html
401        */
402        #[cfg(all(target_os = "macos", feature = "loaded"))]
403        {
404            extension_names.extend(&[
405                ash::khr::get_physical_device_properties2::NAME,
406                ash::khr::portability_enumeration::NAME,
407            ]);
408        }
409
410        let khr_surface = extension_names.contains(&khr::surface::NAME);
411
412        let extension_name_ptrs = extension_names
413            .iter()
414            .copied()
415            .map(CStr::as_ptr)
416            .collect::<Box<_>>();
417
418        let mut layer_names = Vec::with_capacity(info.debug as _);
419
420        if info.debug {
421            layer_names.extend(debug_layer_names());
422        }
423
424        let layer_name_ptrs = layer_names
425            .iter()
426            .copied()
427            .map(CStr::as_ptr)
428            .collect::<Box<_>>();
429
430        let app_desc =
431            vk::ApplicationInfo::default().api_version(info.api_version.to_vk_api_version());
432        let instance_desc = vk::InstanceCreateInfo::default()
433            .application_info(&app_desc)
434            .enabled_layer_names(&layer_name_ptrs)
435            .enabled_extension_names(&extension_name_ptrs);
436
437        /*
438        MoltenVK doesn't support the full Vulkan feature set, hence the portability flag needs to be
439        set.
440        */
441        #[cfg(all(target_os = "macos", feature = "loaded"))]
442        let instance_desc = instance_desc.flags(vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR);
443
444        #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
445        let mut debug_create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
446            .message_severity(
447                vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE
448                    | vk::DebugUtilsMessageSeverityFlagsEXT::INFO
449                    | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
450                    | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
451            )
452            .message_type(
453                vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
454                    | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
455                    | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
456            )
457            .pfn_user_callback(Some(debug_callback));
458
459        #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
460        let instance_desc = if info.debug {
461            instance_desc.push_next(&mut debug_create_info)
462        } else {
463            instance_desc
464        };
465
466        let instance = unsafe {
467            entry.create_instance(&instance_desc, None).map_err(|_| {
468                if info.debug {
469                    warn!("debug may only be enabled with a valid Vulkan SDK installation");
470                }
471
472                error!(
473                    "Vulkan driver does not support API v{}",
474                    match info.api_version {
475                        ApiVersion::Vulkan12 => "1.2",
476                        ApiVersion::Vulkan13 => "1.3",
477                    }
478                );
479
480                for layer_name in &layer_names {
481                    debug!("Layer: {:?}", layer_name);
482                }
483
484                for extension_name in extension_names {
485                    debug!("Extension: {:?}", extension_name);
486                }
487
488                DriverError::Unsupported
489            })?
490        };
491
492        trace!("created a Vulkan instance");
493
494        #[cfg(all(target_os = "macos", not(feature = "loaded")))]
495        let debug_utils = None;
496
497        #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
498        let debug_utils = if info.debug {
499            let debug_utils = ext::debug_utils::Instance::new(&entry, &instance);
500            let debug_messenger =
501                unsafe { debug_utils.create_debug_utils_messenger(&debug_create_info, None) }
502                    .map_err(|err| {
503                        unsafe {
504                            instance.destroy_instance(None);
505                        }
506
507                        error!("unable to create debug utils messenger: {err}");
508
509                        DriverError::Unsupported
510                    })?;
511
512            Some((debug_utils, debug_messenger))
513        } else {
514            None
515        };
516
517        Ok(Self {
518            read_only: ReadOnlyInstance {
519                info,
520                inner: Arc::new(InstanceInner {
521                    debug_utils,
522                    entry,
523                    instance,
524                    instance_created: true,
525                }),
526                khr_surface,
527            },
528        })
529    }
530
531    /// The ash entry point used to load Vulkan instance functions.
532    pub fn entry(this: &Self) -> &ash::Entry {
533        &this.inner.entry
534    }
535
536    pub(crate) fn supports_debug_utils(this: &Self) -> bool {
537        this.inner.debug_utils.is_some()
538    }
539
540    /// Returns the available physical devices of this instance.
541    ///
542    /// See [`vkEnumeratePhysicalDevices`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkEnumeratePhysicalDevices.html).
543    #[profiling::function]
544    pub fn physical_devices(
545        this: &Self,
546    ) -> Result<impl IntoIterator<Item = PhysicalDevice>, DriverError> {
547        let physical_devices = unsafe { this.enumerate_physical_devices() }.map_err(|err| {
548            error!("unable to enumerate physical devices: {err}");
549
550            match err {
551                vk::Result::ERROR_INITIALIZATION_FAILED => DriverError::Unsupported,
552                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
553                    DriverError::OutOfMemory
554                }
555                vk::Result::ERROR_VALIDATION_FAILED_EXT => DriverError::InvalidData,
556                _ => {
557                    warn!("unexpected enumerate_physical_devices error: {err}");
558
559                    DriverError::Unsupported
560                }
561            }
562        })?;
563
564        Ok(physical_devices
565            .into_iter()
566            .enumerate()
567            .filter_map(|(idx, physical_device)| {
568                let physical_device = unsafe {
569                    PhysicalDevice::try_from_ash(this, physical_device)
570                        .inspect_err(|err| warn!("unsupported physical device #{idx}: {err}"))
571                        .ok()?
572                };
573
574                let api_version = ApiVersion::try_parse_vk_api_version(
575                    physical_device.properties_v1_0.api_version,
576                )
577                .inspect_err(|err| {
578                    warn!(
579                        "unsupported physical device #{idx} {}: {err}",
580                        physical_device.properties_v1_0.device_name
581                    );
582                })
583                .ok()?;
584
585                if api_version < this.info.api_version {
586                    return None;
587                }
588
589                if this.info.debug && !physical_device.vk_ext_private_data {
590                    warn!(
591                        "unsupported physical device #{idx} {}: missing VK_EXT_private_data",
592                        physical_device.properties_v1_0.device_name
593                    );
594
595                    return None;
596                }
597
598                Some(physical_device)
599            }))
600    }
601
602    /// Creates a new Vulkan instance with the platform surface extensions required by the provided
603    /// display handle.
604    ///
605    /// See [`VK_KHR_surface`](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_surface.html).
606    #[profiling::function]
607    pub fn try_from_display(
608        display: impl HasDisplayHandle,
609        info: impl Into<InstanceInfo>,
610    ) -> Result<Self, DriverError> {
611        let display_handle = display.display_handle().map_err(|err| {
612            warn!("unable to get display handle: {err}");
613
614            DriverError::Unsupported
615        })?;
616        let display_extension_names =
617            display_extension_names(display_handle.as_raw()).map_err(|err| {
618                warn!("unable to enumerate display extensions: {err}");
619
620                DriverError::Unsupported
621            })?;
622
623        Self::create_with_extension_names(info.into(), display_extension_names)
624    }
625
626    /// Loads an existing Vulkan instance that may have been created by other means.
627    ///
628    /// This is useful when you want to use a Vulkan instance created by some other library, such
629    /// as OpenXR.
630    ///
631    /// See [`VkInstance`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkInstance.html).
632    #[profiling::function]
633    pub fn try_from_entry(entry: ash::Entry, instance: vk::Instance) -> Result<Self, DriverError> {
634        if instance == vk::Instance::null() {
635            warn!("invalid VkInstance handle: null");
636
637            return Err(DriverError::InvalidData);
638        }
639
640        let api_version = unsafe { entry.try_enumerate_instance_version() }
641            .map_err(|err| match err {
642                vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
643                vk::Result::ERROR_VALIDATION_FAILED_EXT => DriverError::InvalidData,
644                err => {
645                    error!("unable to enumerate instance version: {err}");
646
647                    DriverError::Unsupported
648                }
649            })?
650            .unwrap_or_else(|| {
651                /*
652                The implementation *should* provide a version. If it does not, use the default.
653                */
654                Self::DEFAULT_API_VERSION.to_vk_api_version()
655            })
656            .try_into()
657            .map_err(|err| {
658                warn!("unsupported instance: {err}");
659
660                DriverError::Unsupported
661            })?;
662        let khr_surface = has_vk_khr_surface(&entry, instance);
663
664        let instance = unsafe { ash::Instance::load(entry.static_fn(), instance) };
665
666        Ok(Self {
667            read_only: ReadOnlyInstance {
668                info: InstanceInfo {
669                    api_version,
670                    ..Default::default()
671                },
672                inner: Arc::new(InstanceInner {
673                    debug_utils: None,
674                    entry,
675                    instance,
676                    instance_created: false,
677                }),
678                khr_surface,
679            },
680        })
681    }
682}
683
684impl Debug for Instance {
685    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
686        f.debug_struct(stringify!(Instance))
687            .field("handle", &self.inner.instance.handle())
688            .field("info", &self.info)
689            .field("khr_surface", &self.khr_surface)
690            .field("debug_utils", &self.inner.debug_utils.is_some())
691            .finish_non_exhaustive()
692    }
693}
694
695/// Information used to create an [`Instance`] instance.
696#[derive(Builder, Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
697#[builder(
698    build_fn(private, name = "fallible_build"),
699    derive(Clone, Copy, Debug),
700    pattern = "owned"
701)]
702pub struct InstanceInfo {
703    /// The Vulkan API version to target.
704    ///
705    /// Defaults to [`Instance::DEFAULT_API_VERSION`].
706    #[builder(default = "Instance::DEFAULT_API_VERSION")]
707    pub api_version: ApiVersion,
708
709    /// Enables Vulkan validation layers.
710    ///
711    /// This requires a Vulkan SDK installation and will panic when validation errors happen.
712    /// Additionally, the device must support VK_EXT_private_data.
713    ///
714    /// When `stderr` is attached to an interactive terminal, validation errors will park the
715    /// callback thread for debugger attach.
716    ///
717    /// Set `VK_GRAPH_SKIP_VALIDATION_PARK=1` to keep logging validation errors without parking.
718    ///
719    /// _NOTE:_ Consider turning OFF debug if you discover an unknown issue. Often the validation
720    /// layers will report an error before other layers can provide additional context such as the
721    /// API dump info or other messages. You might find the "actual" issue is detailed in those
722    /// subsequent details.
723    ///
724    /// ## Platform-specific
725    ///
726    /// **macOS:** Has no effect unless the `loaded` feature is enabled.
727    ///
728    #[builder(default)]
729    pub debug: bool,
730
731    /// Required Vulkan instance extension names to load.
732    #[builder(default)]
733    pub extension_names: &'static [&'static CStr],
734}
735
736impl InstanceInfo {
737    /// Creates a default `InstanceInfoBuilder`.
738    pub fn builder() -> InstanceInfoBuilder {
739        Default::default()
740    }
741
742    /// Converts a `InstanceInfo` into a `InstanceInfoBuilder`.
743    pub fn into_builder(self) -> InstanceInfoBuilder {
744        InstanceInfoBuilder {
745            api_version: Some(self.api_version),
746            debug: Some(self.debug),
747            extension_names: Some(self.extension_names),
748        }
749    }
750}
751
752impl InstanceInfoBuilder {
753    /// Builds a new `InstanceInfo`.
754    #[inline(always)]
755    pub fn build(self) -> InstanceInfo {
756        self.fallible_build().expect("invalid instance info")
757    }
758}
759
760impl From<InstanceInfoBuilder> for InstanceInfo {
761    fn from(info: InstanceInfoBuilder) -> Self {
762        info.build()
763    }
764}
765
766struct InstanceInner {
767    debug_utils: Option<(ext::debug_utils::Instance, vk::DebugUtilsMessengerEXT)>,
768    entry: ash::Entry,
769    instance: ash::Instance,
770    instance_created: bool,
771}
772
773impl Drop for InstanceInner {
774    #[profiling::function]
775    fn drop(&mut self) {
776        if panicking() {
777            return;
778        }
779
780        unsafe {
781            if let Some((debug_utils, debug_messenger)) = self.debug_utils.take() {
782                trace!("destroy debug_utils_messenger {}", debug_messenger.as_raw());
783                debug_utils.destroy_debug_utils_messenger(debug_messenger, None);
784                trace!(
785                    "destroy debug_utils_messenger {} DONE",
786                    debug_messenger.as_raw()
787                );
788            }
789
790            if self.instance_created {
791                trace!("destroy instance {}", self.instance.handle().as_raw());
792                self.instance.destroy_instance(None);
793                self.instance_created = false;
794            }
795        }
796    }
797}
798
799/// Data returned when attempting to parse a Vulkan API version number.
800#[derive(Clone, Copy, Debug)]
801pub struct ParseApiVersionError {
802    /// The _major_ version indicates a significant change in the API, which will encompass a
803    /// wholly new version of the specification.
804    pub major: u32,
805
806    /// The _minor_ version indicates the incorporation of new functionality into the core
807    /// specification.
808    pub minor: u32,
809
810    /// The _patch_ version indicates bug fixes, clarifications, and language improvements have
811    /// been incorporated into the specification.
812    pub patch: u32,
813
814    /// The _variant_ indicates the variant of the Vulkan API supported by the implementation. This
815    /// is always 0 for the Vulkan API.
816    pub variant: u32,
817}
818
819impl Display for ParseApiVersionError {
820    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
821        f.write_fmt(format_args!(
822            "v{}.{}.{}-{}",
823            self.major, self.minor, self.patch, self.variant
824        ))
825    }
826}
827
828impl Error for ParseApiVersionError {}
829
830#[doc(hidden)]
831impl Deref for ReadOnlyInstance {
832    type Target = ash::Instance;
833
834    fn deref(&self) -> &Self::Target {
835        &self.inner.instance
836    }
837}
838
839#[cfg(test)]
840mod test {
841    use super::*;
842
843    #[test]
844    pub fn api_versions_match() {
845        assert_eq!(
846            ApiVersion::Vulkan12.to_vk_api_version(),
847            vk::API_VERSION_1_2
848        );
849        assert_eq!(
850            ApiVersion::Vulkan13.to_vk_api_version(),
851            vk::API_VERSION_1_3
852        );
853    }
854
855    #[test]
856    pub fn api_versions_from() {
857        assert_eq!(
858            ApiVersion::try_parse_vk_api_version(vk::API_VERSION_1_2).unwrap(),
859            ApiVersion::Vulkan12
860        );
861        assert_eq!(
862            ApiVersion::try_parse_vk_api_version(vk::API_VERSION_1_3).unwrap(),
863            ApiVersion::Vulkan13
864        );
865    }
866
867    #[test]
868    pub fn default_api_version_matches_instance_info_default() {
869        assert_eq!(
870            Instance::DEFAULT_API_VERSION,
871            InstanceInfo::default().api_version
872        );
873    }
874
875    #[test]
876    pub fn default_api_version_matches_api_version_default() {
877        assert_eq!(Instance::DEFAULT_API_VERSION, ApiVersion::default());
878    }
879
880    #[test]
881    pub fn invalid_api_versions_are_rejected() {
882        assert!(ApiVersion::try_parse_vk_api_version(vk::API_VERSION_1_1).is_err());
883        assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(0, 2, 0, 0)).is_err());
884        assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 1, 9, 0)).is_err());
885        assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 4, 2, 0)).is_err());
886        assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 2, 0, 1)).is_err());
887        assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 3, 0, 1)).is_err());
888    }
889}