🔒 Repository is read-only – file editing is disabled.

PaganDE/smithay-0.7.0/src/backend/vulkan/mod.rs main

747 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
//! Types to initialize and use Vulkan.
//!
//! This module provides some thin abstractions over [`ash`](https://crates.io/crates/ash) for initializing
//! Vulkan.
//!
//! This module does not provide abstractions for logical devices, rendering or memory allocation. These
//! should instead be provided in higher level abstractions.
//!
//! Smithay requires at least Vulkan 1.1[^version].
//!
//! # [`Instance`]
//!
//! To use Vulkan, you would first instantiate an [`Instance`]. An instance is effectively the Vulkan library
//! and provides some information about the environment. This includes the list of supported
//! [instance extensions](Instance::enumerate_extensions) and the list of available physical devices.
//!
//! An instance is constructed using an [`Instance::new`] or [`Instance::with_extensions`].
//!
//! ## Layers
//!
//! The validation layers will be enabled if debug assertions are enabled and the validation layers are
//! available on your system.
//!
//! ## Instance extensions
//!
//! Instances may be created with some enabled extensions. Note that any features gated by an extension are
//! only available if the extension (and it's dependencies) are enabled (you will get a validation error if
//! you ignore that).
//!
//! Some features such as window system integration are only available if their features are enabled.
//! Available instance extensions may be obtained using [`Instance::enumerate_extensions`].
//!
//! # [`PhysicalDevice`]
//!
//! Once you have an instance, you may want to find a suitable device to use. A [`PhysicalDevice`] describes a
//! Vulkan implementation that may correspond to a real or virtual device.
//!
//! To get all the available devices, use [`PhysicalDevice::enumerate`].
//!
//! Physical devices are also describe the logical devices that can be created. A physical device can describe
//! a variety of properties that may be used for device selection, including but not limited to:
//! - [Device name](PhysicalDevice::name)
//! - [Supported Vulkan API version](PhysicalDevice::api_version)
//! - [Type](PhysicalDevice::ty) of the device
//! - [Driver information](PhysicalDevice::driver)
//! - [Extensions](PhysicalDevice::device_extensions)
//! - [Features](PhysicalDevice::features) and [limits](PhysicalDevice::limits)
//!
//! Physical devices implement [`Eq`][^device_eq], meaning two physical devices can be tested for equality.
//!
//! ## Device extensions
//!
//! Depending on the device extension (see the Vulkan specification), a device extension may indicate some
//! physical device data is available or indicate some device feature is supported. Any features that are
//! added using a device extension must be enabled in order to be used.
//!
//! [^version]: Internally Vulkan 1.1 is required because several extensions that were made part of core are
//! used quite extensively in some of the extensions our abstractions use. The vast majority of systems using
//! Vulkan also support at least Vulkan 1.1. If you need Vulkan 1.0 support, please open an issue and we can
//! discuss Vulkan 1.0 support.
//!
//! [^device_eq]: Two physical devices are only equal if both physical devices are created from the same
//! instance and have the same physical device handle.

#![warn(missing_debug_implementations)]
#![forbid(unsafe_op_in_unsafe_fn)]

use std::{
    env::{self, VarError},
    ffi::{CStr, CString},
    sync::{Arc, LazyLock},
};

use ash::{
    ext,
    prelude::VkResult,
    vk::{self, PhysicalDeviceDriverProperties, PhysicalDeviceDrmPropertiesEXT},
    Entry,
};
use libc::c_void;
use scopeguard::ScopeGuard;
use tracing::{error, info, info_span, instrument, trace, warn};

use crate::backend::vulkan::inner::DebugState;

use self::{inner::InstanceInner, version::Version};

#[cfg(feature = "backend_drm")]
use super::drm::DrmNode;

mod inner;
mod phd;

pub mod version;

static LIBRARY: LazyLock<Result<Entry, LoadError>> =
    LazyLock::new(|| unsafe { Entry::load().map_err(|_| LoadError) });

/// Error loading the Vulkan library
#[derive(Debug, thiserror::Error)]
#[error("Failed to load the Vulkan library")]
pub struct LoadError;

/// An error that may occur when creating an [`Instance`].
#[derive(Debug, thiserror::Error)]
pub enum InstanceError {
    /// The instance was created using Vulkan 1.0.
    #[error("Smithay requires at least Vulkan 1.1")]
    UnsupportedVersion,

    /// Failed to load the Vulkan library.
    #[error(transparent)]
    Load(#[from] LoadError),

    /// Vulkan API error.
    #[error(transparent)]
    Vk(#[from] vk::Result),
}

/// Error returned when a physical device property is not supported
#[derive(Debug, thiserror::Error)]
pub enum UnsupportedProperty {
    /// Some required extensions are not available.
    #[error("The following extensions are not available {0:?}")]
    Extensions(&'static [&'static CStr]),
}

/// App info to be passed to the Vulkan implementation.
#[derive(Debug)]
pub struct AppInfo {
    /// Name of the app.
    pub name: String,
    /// Version of the app.
    pub version: Version,
}

/// A Vulkan instance.
///
/// An instance is the object which tracks an application's Vulkan state. An instance allows an application to
/// get a list of physical devices.
///
/// In the Vulkan it is common to have objects which may not outlive the parent instance. A great way to
/// ensure compliance when using child objects is to [`Clone`] the instance and keep a handle with the child
/// object. This will ensure the child object does not outlive the instance.
///
/// An instance is [`Send`] and [`Sync`] which allows meaning multiple threads to access the Vulkan state.
/// Note that this **does not** mean the entire Vulkan API is thread safe, you will need to read the
/// specification to determine what parts of the Vulkan API require external synchronization.
///
/// # Instance extensions
///
/// In order to use features exposed through instance extensions (such as window system integration), you must
/// enable the extensions corresponding to the feature.
///
/// By default, [`Instance`] will automatically try to enable the following instance extensions if available:
/// * `VK_EXT_debug_utils`
///
/// No users should assume the instance extensions that are automatically enabled are available.
#[derive(Debug, Clone)]
pub struct Instance(Arc<InstanceInner>);

impl Instance {
    /// Creates a new [`Instance`].
    pub fn new(max_version: Version, app_info: Option<AppInfo>) -> Result<Instance, InstanceError> {
        unsafe { Self::with_extensions(max_version, app_info, &[]) }
    }

    /// Creates a new [`Instance`] with some additionally specified extensions.
    ///
    /// # Safety
    ///
    /// * All valid usage requirements specified by [`vkCreateInstance`](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/vkCreateInstance.html)
    ///   must be satisfied.
    /// * Any enabled extensions must also have the dependency extensions enabled
    ///   (see `VUID-vkCreateInstance-ppEnabledExtensionNames-01388`).
    pub unsafe fn with_extensions(
        max_version: Version,
        app_info: Option<AppInfo>,
        extensions: &[&'static CStr],
    ) -> Result<Instance, InstanceError> {
        assert!(
            max_version >= Version::VERSION_1_1,
            "Smithay requires at least Vulkan 1.1"
        );
        let requested_max_version = get_env_or_max_version(max_version);

        let span = info_span!("backend_vulkan", version = tracing::field::Empty);
        let _guard = span.enter();

        // Determine the maximum instance version that is possible.
        let max_version = {
            unsafe {
                LIBRARY
                    .as_ref()
                    .or(Err(LoadError))?
                    .try_enumerate_instance_version()
            }
            // Any allocation errors must be the result of the loader or layers
            .or(Err(LoadError))?
            .map(Version::from_raw)
            // Vulkan 1.0 does not have `vkEnumerateInstanceVersion`.
            .unwrap_or(Version::VERSION_1_0)
        };

        if max_version == Version::VERSION_1_0 {
            error!("Vulkan does not support version 1.1");
            return Err(InstanceError::UnsupportedVersion);
        }

        // Pick the lower of the requested max version and max possible version
        let api_version = Version::from_raw(u32::min(max_version.to_raw(), requested_max_version.to_raw()));
        span.record("version", tracing::field::display(api_version));

        let available_layers = Self::enumerate_layers()?.collect::<Vec<_>>();
        let available_extensions = Self::enumerate_extensions()?.collect::<Vec<_>>();

        let mut layers = Vec::new();

        // Enable debug layers if present and debug assertions are enabled.
        if cfg!(debug_assertions) {
            const VALIDATION: &CStr = c"VK_LAYER_KHRONOS_validation";

            if available_layers
                .iter()
                .any(|layer| layer.as_c_str() == VALIDATION)
            {
                layers.push(VALIDATION);
            } else {
                warn!("Validation layers not available. These can be installed through your package manager",);
            }
        }

        let mut enabled_extensions = Vec::<&'static CStr>::new();
        enabled_extensions.extend(extensions);

        // Enable debug utils if available.
        let has_debug_utils = available_extensions
            .iter()
            .any(|name| name.as_c_str() == ext::debug_utils::NAME);

        if has_debug_utils {
            enabled_extensions.push(ext::debug_utils::NAME);
        }

        // Both of these are safe because both vecs contain static CStrs.
        let extension_pointers = enabled_extensions
            .iter()
            .map(|name| name.as_ptr())
            .collect::<Vec<_>>();
        let layer_pointers = layers.iter().map(|name| name.as_ptr()).collect::<Vec<_>>();

        let app_version = app_info.as_ref().map(|info| info.version.to_raw());
        let app_name =
            app_info.map(|info| CString::new(info.name).expect("app name contains null terminator"));
        let mut app_info = vk::ApplicationInfo::default()
            .api_version(api_version.to_raw())
            // SAFETY: null terminated with no interior null bytes.
            .engine_name(c"Smithay")
            .engine_version(Version::SMITHAY.to_raw());

        if let Some(app_version) = app_version {
            app_info = app_info.application_version(app_version);
        }

        if let Some(app_name) = &app_name {
            app_info = app_info.application_name(app_name);
        }

        let library = LIBRARY.as_ref().map_err(|_| LoadError)?;
        let create_info = vk::InstanceCreateInfo::default()
            .application_info(&app_info)
            .enabled_layer_names(&layer_pointers)
            .enabled_extension_names(&extension_pointers);

        // Place the instance in a scopeguard in case creating the debug messenger fails.
        let instance = scopeguard::guard(
            unsafe { library.create_instance(&create_info, None) }?,
            |instance| unsafe {
                instance.destroy_instance(None);
            },
        );

        // Setup the debug utils
        let debug_state = if has_debug_utils {
            let span = info_span!("backend_vulkan_debug");
            let debug_utils = ext::debug_utils::Instance::new(library, &instance);
            // Place the pointer to the span in a scopeguard to prevent a memory leak in case creating the
            // debug messenger fails.
            let span_ptr = scopeguard::guard(Box::into_raw(Box::new(span)), |ptr| unsafe {
                let _ = Box::from_raw(ptr);
            });

            let create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
                .message_severity(
                    vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
                        | vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE
                        | vk::DebugUtilsMessageSeverityFlagsEXT::INFO
                        | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
                )
                .message_type(
                    vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
                        | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE
                        | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION,
                )
                .pfn_user_callback(Some(vulkan_debug_utils_callback))
                .user_data(*span_ptr as *mut _);

            let debug_messenger = unsafe { debug_utils.create_debug_utils_messenger(&create_info, None) }?;

            // Disarm the destructor for the logger pointer since the instance is now responsible for
            // destroying the logger.
            let span_ptr = ScopeGuard::into_inner(span_ptr);

            Some(DebugState {
                debug_utils,
                debug_messenger,
                span_ptr,
            })
        } else {
            None
        };

        // Creating the debug messenger was successful, disarm the scopeguard and let InstanceInner manage
        // destroying the instance.
        let instance = ScopeGuard::into_inner(instance);
        drop(_guard);
        let inner = InstanceInner {
            instance,
            version: api_version,
            debug_state,
            span,
            enabled_extensions,
        };

        info!("Created new instance");
        info!("Enabled instance extensions: {:?}", inner.enabled_extensions);

        #[allow(clippy::arc_with_non_send_sync)]
        Ok(Instance(Arc::new(inner)))
    }

    /// Returns an iterator which contains the available instance extensions on the system.
    pub fn enumerate_extensions() -> Result<impl Iterator<Item = CString>, LoadError> {
        let library = LIBRARY.as_ref().or(Err(LoadError))?;

        let extensions = unsafe { library.enumerate_instance_extension_properties(None) }
            .or(Err(LoadError))?
            .into_iter()
            .map(|properties| {
                // SAFETY: Vulkan guarantees the string is null terminated.
                unsafe { CStr::from_ptr(&properties.extension_name as *const _) }.to_owned()
            })
            .collect::<Vec<_>>()
            .into_iter();

        Ok(extensions)
    }

    /// Returns the enabled instance extensions.
    pub fn enabled_extensions(&self) -> impl Iterator<Item = &CStr> {
        self.0.enabled_extensions.iter().copied()
    }

    /// Returns true if the specified instance extension is enabled.
    ///
    /// This function may be used to ensure safe access to features provided by instance extensions.
    pub fn is_extension_enabled(&self, extension: &CStr) -> bool {
        self.enabled_extensions().any(|name| name == extension)
    }

    /// Returns the version of Vulkan supported by this instance.
    ///
    /// This corresponds to the version specified when building the instance.
    pub fn api_version(&self) -> Version {
        self.0.version
    }

    /// Returns a reference to the underlying [`ash::Instance`].
    ///
    /// Any objects created using the handle must be destroyed before the final instance is dropped per the
    /// valid usage requirements (`VUID-vkDestroyInstance-instance-00629`).
    pub fn handle(&self) -> &ash::Instance {
        &self.0.instance
    }
}

/// A Vulkan physical device.
///
/// A physical device refers to a Vulkan implementation. A physical device has no associated resources and may
/// be used to create a logical device.
#[derive(Debug, Clone)]
pub struct PhysicalDevice {
    phd: vk::PhysicalDevice,
    info: PhdInfo,
    extensions: Vec<CString>,
    instance: Instance,
    span: tracing::Span,
}

impl PhysicalDevice {
    /// Enumerates over all physical devices available on the system, returning an iterator of [`PhysicalDevice`]
    pub fn enumerate(instance: &Instance) -> VkResult<impl Iterator<Item = PhysicalDevice>> {
        let _span = instance.0.span.enter();

        // Must clone instance or else the returned iterator has a lifetime over `&Instance`
        let instance = instance.clone();
        let devices = unsafe { instance.handle().enumerate_physical_devices() }?;
        let devices = devices
            .into_iter()
            // TODO: Warn if any physical devices have an error when getting device properties.
            .flat_map(move |phd| unsafe { PhysicalDevice::from_phd(&instance, phd) })
            .flatten();

        Ok(devices)
    }

    /// Returns the name of the device.
    pub fn name(&self) -> &str {
        &self.info.name
    }

    /// Returns the version of Vulkan supported by this device.
    ///
    /// Unlike the `api_version` property, which is the version reported by the device directly, this function
    /// returns the version the device can actually support, based on the instance’s, `api_version`.
    ///
    /// The Vulkan specification provides more information about the version requirements: <https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#fundamentals-validusage-versions>
    pub fn api_version(&self) -> Version {
        self.info.api_version
    }

    /// Returns the device type.
    ///
    /// This may be used during device selection to choose a higher performance GPU.
    pub fn ty(&self) -> vk::PhysicalDeviceType {
        self.info.properties.device_type
    }

    /// Returns the Vulkan 1.0 physical device features.
    pub fn features(&self) -> vk::PhysicalDeviceFeatures {
        self.info.features
    }

    /// Returns the physical device properties.
    ///
    /// Some properties such as the device name can be obtained using other functions defined on
    /// [`PhysicalDevice`].
    pub fn properties(&self) -> vk::PhysicalDeviceProperties {
        self.info.properties
    }

    /// Returns the device's descriptor set properties.
    ///
    /// This also describes the maximum memory allocation size.
    pub fn properties_maintenance_3(&self) -> vk::PhysicalDeviceMaintenance3Properties<'_> {
        self.info.maintenance_3
    }

    /// Information about universally unique identifiers (UUIDs) that identify this device.
    pub fn id_properties(&self) -> vk::PhysicalDeviceIDProperties<'_> {
        self.info.id
    }

    /// Returns the physical device limits.
    pub fn limits(&self) -> vk::PhysicalDeviceLimits {
        self.info.properties.limits
    }

    /// Information about the Vulkan driver.
    ///
    /// This may return [`None`] for a few reasons:
    /// * The Vulkan implementation is not at least 1.2
    /// * If the Vulkan implementation is not at least Vulkan 1.2, the `VK_KHR_driver_properties` device
    ///   extension is not available.
    pub fn driver(&self) -> Option<&DriverInfo> {
        self.info.driver.as_ref()
    }

    /// Returns the major and minor numbers of the primary node which corresponds to this physical device's DRM
    /// device.
    #[cfg(feature = "backend_drm")]
    #[instrument(level = "debug", parent = &self.span, skip(self))]
    pub fn primary_node(&self) -> Result<Option<DrmNode>, UnsupportedProperty> {
        let properties_drm = self.info.get_drm_properties()?;
        let node = Some(properties_drm)
            .filter(|props| props.has_primary == vk::TRUE)
            .and_then(|props| {
                DrmNode::from_dev_id(libc::makedev(props.primary_major as _, props.primary_minor as _)).ok()
            });

        Ok(node)
    }

    /// Returns the major and minor numbers of the render node which corresponds to this physical device's DRM
    /// device.
    ///
    /// Note that not every device has a render node. If there is no render node (this function returns [`None`])
    /// then try to use the primary node.
    #[cfg(feature = "backend_drm")]
    #[instrument(level = "debug", parent = &self.span, skip(self))]
    pub fn render_node(&self) -> Result<Option<DrmNode>, UnsupportedProperty> {
        let properties_drm = self.info.get_drm_properties()?;
        let node = Some(properties_drm)
            .filter(|props| props.has_render == vk::TRUE)
            .and_then(|props| {
                DrmNode::from_dev_id(libc::makedev(props.render_major as _, props.render_minor as _)).ok()
            });

        Ok(node)
    }

    /// Get physical device properties.
    ///
    /// This function is equivalent to calling [`vkGetPhysicalDeviceProperties2`].
    ///
    /// # Safety
    ///
    /// - All valid usage requirements for [`vkGetPhysicalDeviceProperties2`] apply. Read the specification
    ///   for more information.
    ///
    /// [`vkGetPhysicalDeviceProperties2`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/vkGetPhysicalDeviceProperties2.html
    pub unsafe fn get_properties(&self, props: &mut vk::PhysicalDeviceProperties2<'_>) {
        let instance = self.instance().handle();
        // SAFETY: The caller has garunteed all valid usage requirements for vkGetPhysicalDeviceProperties2
        // are satisfied.
        unsafe { instance.get_physical_device_properties2(self.handle(), props) }
    }

    /// Get physical device format properties.
    ///
    /// This function is equivalent to calling [`vkGetPhysicalDeviceFormatProperties2`].
    ///
    /// # Safety
    ///
    /// - All valid usage requirements for [`vkGetPhysicalDeviceFormatProperties2`] apply. Read the specification
    ///   for more information.
    ///
    /// [`vkGetPhysicalDeviceFormatProperties2`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/vkGetPhysicalDeviceFormatProperties2.html
    pub unsafe fn get_format_properties(&self, format: vk::Format, props: &mut vk::FormatProperties2<'_>) {
        let instance = self.instance().handle();
        // SAFETY: The caller has garunteed all valid usage requirements for vkGetPhysicalDeviceFormatProperties2
        // are satisfied.
        unsafe { instance.get_physical_device_format_properties2(self.handle(), format, props) }
    }

    /// Returns properties for each supported DRM modifier for the specified format.
    ///
    /// Returns [`Err`] if the `VK_EXT_image_drm_format_modifier` extension is not supported.
    #[instrument(level = "debug", parent = &self.span, skip(self))]
    pub fn get_format_modifier_properties(
        &self,
        format: vk::Format,
    ) -> Result<Vec<vk::DrmFormatModifierPropertiesEXT>, UnsupportedProperty> {
        if !self.has_device_extension(ext::image_drm_format_modifier::NAME) {
            const EXTENSIONS: &[&CStr] = &[ext::image_drm_format_modifier::NAME];
            return Err(UnsupportedProperty::Extensions(EXTENSIONS));
        }

        // First get the number of modifiers the driver supports.
        let count = unsafe {
            let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
            let mut format_properties2 = vk::FormatProperties2::default().push_next(&mut list);
            self.get_format_properties(format, &mut format_properties2);
            list.drm_format_modifier_count as usize
        };

        // Allocate the vector to receive the modifiers in.
        let mut data = Vec::with_capacity(count);

        unsafe {
            let mut list = vk::DrmFormatModifierPropertiesListEXT {
                // We cannot use the builder here because the Vec is currently empty, so we need to tell Vulkan
                // where to write out the modifier properties and tell it how large the Vec is.
                p_drm_format_modifier_properties: data.as_mut_ptr(),
                drm_format_modifier_count: count as u32,
                ..Default::default()
            };

            let mut format_properties2 = vk::FormatProperties2::default().push_next(&mut list);
            self.get_format_properties(format, &mut format_properties2);
            // SAFETY: Vulkan just initialized the elements of the vector.
            data.set_len(list.drm_format_modifier_count as usize);
        }

        Ok(data)
    }

    /// Returns the device extensions supported by the physical device.
    pub fn device_extensions(&self) -> impl Iterator<Item = &CStr> {
        self.extensions.iter().map(CString::as_c_str)
    }

    /// Returns `true` if this device supports the specified device extension.
    pub fn has_device_extension(&self, extension: &CStr) -> bool {
        self.device_extensions().any(|name| name == extension)
    }

    /// Returns a handle to the underlying [`vk::PhysicalDevice`].
    ///
    /// The handle refers to a specific physical device advertised by the instance. This handle is only valid
    /// for the lifetime of the instance.
    pub fn handle(&self) -> vk::PhysicalDevice {
        self.phd
    }

    /// The instance which provided this physical device.
    pub fn instance(&self) -> &Instance {
        &self.instance
    }
}

impl PartialEq for PhysicalDevice {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        // Both the physical device handle and instance handle must be the same
        self.phd == other.phd && self.instance().handle().handle() == other.instance().handle().handle()
    }
}

// SAFETY: The internal pointers in the PhysicalDevice*Properties are always null and only copies of the
// PhysicalDevice*Properties types are returned.
unsafe impl Send for PhysicalDevice {}
unsafe impl Sync for PhysicalDevice {}

/// Information about the driver providing a [`PhysicalDevice`].
#[derive(Debug, Clone)]
pub struct DriverInfo {
    /// ID which identifies the driver.
    pub id: vk::DriverId,

    /// The name of the driver.
    pub name: String,

    /// Information describing the driver.
    ///
    /// This may include information such as the driver version.
    pub info: String,

    /// The Vulkan conformance test this driver is conformant against.
    pub conformance: vk::ConformanceVersion,
}

#[derive(Debug, Clone, Default)]
struct PhdInfo {
    api_version: Version,
    name: String,
    properties: vk::PhysicalDeviceProperties,
    features: vk::PhysicalDeviceFeatures,
    maintenance_3: vk::PhysicalDeviceMaintenance3Properties<'static>,
    id: vk::PhysicalDeviceIDProperties<'static>,
    properties_driver: Option<PhysicalDeviceDriverProperties<'static>>,
    /// Information about the DRM device which corresponds to this physical device.
    #[cfg_attr(not(feature = "backend_drm"), allow(dead_code))]
    properties_drm: Option<PhysicalDeviceDrmPropertiesEXT<'static>>,
    driver: Option<DriverInfo>,
}

fn get_env_or_max_version(max_version: Version) -> Version {
    // Consider max version overrides from env
    match env::var("SMITHAY_VK_VERSION") {
        Ok(version) => {
            let overriden_version = match &version[..] {
                "1.0" => {
                    warn!("Smithay does not support Vulkan 1.0, ignoring SMITHAY_VK_VERSION");
                    return max_version;
                }
                "1.1" => Some(Version::VERSION_1_1),
                "1.2" => Some(Version::VERSION_1_2),
                "1.3" => Some(Version::VERSION_1_3),
                _ => None,
            };

            // The env var can only lower the maximum version, not raise it.
            if let Some(overridden_version) = overriden_version {
                if overridden_version > max_version {
                    warn!(
                        "Ignoring SMITHAY_VK_VERSION since the requested max version is higher than the maximum of {}.{}",
                        max_version.major,
                        max_version.minor
                    );
                    max_version
                } else {
                    overridden_version
                }
            } else {
                warn!("SMITHAY_VK_VERSION was set to an unknown Vulkan version");
                max_version
            }
        }

        Err(VarError::NotUnicode(_)) => {
            warn!("Value of SMITHAY_VK_VERSION is not valid Unicode, ignoring.");

            max_version
        }

        Err(VarError::NotPresent) => max_version,
    }
}

unsafe extern "system" fn vulkan_debug_utils_callback(
    message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
    message_type: vk::DebugUtilsMessageTypeFlagsEXT,
    p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
    span: *mut c_void,
) -> vk::Bool32 {
    let _ = std::panic::catch_unwind(|| {
        // Get the span from the user data pointer we gave to Vulkan.
        //
        // The span is allocated on the heap using a box, but we do not want to drop the span,
        // so read from the pointer.
        let _guard = unsafe { (span as *mut tracing::Span).as_ref() }.unwrap().enter();

        // VUID-VkDebugUtilsMessengerCallbackDataEXT-pMessage-parameter: Message must be valid UTF-8 with a null
        // terminator.
        let message = unsafe { CStr::from_ptr((*p_callback_data).p_message) }.to_string_lossy();
        // Message type is in full uppercase since we print the bitflag debug representation.
        let ty = format!("{:?}", message_type).to_lowercase();

        match message_severity {
            vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => {
                trace!(ty, "{}", message)
            }
            vk::DebugUtilsMessageSeverityFlagsEXT::INFO => info!(ty, "{}", message),
            vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => warn!(ty, "{}", message),
            vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => error!(ty, "{}", message),
            _ => (),
        }
    });

    // Must always return false.
    vk::FALSE
}

#[cfg(test)]
mod tests {
    use super::{Instance, PhysicalDevice};

    fn is_send_sync<T: Send + Sync>() {}

    /// Test that both [`Instance`] and [`PhysicalDevice`] are Send and Sync.
    #[test]
    fn send_sync() {
        is_send_sync::<Instance>();
        is_send_sync::<PhysicalDevice>();
    }
}