From fad6f20c6cc4d64e7c38cdd1ef32febe9b56f6f9 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Wed, 11 May 2022 20:36:10 -0500 Subject: [PATCH] Initial Vulkan backend code --- Cargo.toml | 10 +- examples/vulkan.rs | 27 ++ src/backend/mod.rs | 3 + src/backend/vulkan/inner.rs | 74 ++++ src/backend/vulkan/mod.rs | 740 ++++++++++++++++++++++++++++++++++ src/backend/vulkan/phd.rs | 234 +++++++++++ src/backend/vulkan/version.rs | 104 +++++ 7 files changed, 1191 insertions(+), 1 deletion(-) create mode 100644 examples/vulkan.rs create mode 100644 src/backend/vulkan/inner.rs create mode 100644 src/backend/vulkan/mod.rs create mode 100644 src/backend/vulkan/phd.rs create mode 100644 src/backend/vulkan/version.rs diff --git a/Cargo.toml b/Cargo.toml index d36e92b0ea50..796df11b9787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,8 @@ members = [ [dependencies] appendlist = "1.4" +# Intentionally pick one commit back from the "release 0.37.1" branch since the release commit is likely to be rebased +ash = { git = "https://github.com/ash-rs/ash", rev = "28a4868dc302013e00569dc5393f8a0b43ed4a70", optional = true } bitflags = "1" calloop = "0.10.0" cgmath = "0.18.0" @@ -42,6 +44,7 @@ libloading = { version="0.7.0", optional = true } nix = "0.22" once_cell = "1.8.0" rand = "0.8.4" +scopeguard = { version = "1.1.0", optional = true } slog = "2" slog-stdlog = { version = "4", optional = true } tempfile = { version = "3.0", optional = true } @@ -69,7 +72,7 @@ gl_generator = { version = "0.14", optional = true } pkg-config = { version = "0.3.17", optional = true } [features] -default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_x11", "backend_winit", "desktop", "renderer_gl", "renderer_multi", "xwayland", "wayland_frontend", "slog-stdlog"] +default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_x11", "backend_winit", "desktop", "renderer_gl", "renderer_multi", "xwayland", "wayland_frontend", "slog-stdlog", "backend_vulkan"] backend_winit = ["winit", "backend_egl", "wayland-egl", "renderer_gl"] backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"] backend_drm = ["drm", "drm-ffi"] @@ -78,6 +81,7 @@ backend_egl = ["gl_generator", "libloading"] backend_libinput = ["input"] backend_session = [] backend_udev = ["udev", "input/udev"] +backend_vulkan = ["ash", "scopeguard"] backend_session_logind = ["dbus", "backend_session", "pkg-config"] backend_session_elogind = ["backend_session_logind"] backend_session_libseat = ["backend_session", "libseat"] @@ -105,3 +109,7 @@ required-features = ["wayland_frontend"] [[example]] name = "compositor" required-features = ["wayland_frontend"] + +[[example]] +name = "vulkan" +required-features = ["backend_vulkan"] diff --git a/examples/vulkan.rs b/examples/vulkan.rs new file mode 100644 index 000000000000..d1f7b43a386a --- /dev/null +++ b/examples/vulkan.rs @@ -0,0 +1,27 @@ +use std::sync::Mutex; + +use slog::{o, Drain}; +use smithay::backend::vulkan::{Instance, PhysicalDevice}; + +fn main() { + let logger = slog::Logger::root(Mutex::new(slog_term::term_full().fuse()).fuse(), o!()); + + println!("Version: {}", Instance::max_instance_version().unwrap()); + println!( + "Available instance extensions: {:?}", + Instance::enumerate_extensions().unwrap().collect::>() + ); + println!(); + + let instance = Instance::new(Version::VERSION_1_3, None, logger).unwrap(); + + for (idx, phy) in PhysicalDevice::enumerate(&instance).unwrap().enumerate() { + println!( + "Device #{}: {} v{}, {:?}", + idx, + phy.name(), + phy.api_version(), + phy.driver() + ); + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 2f3d57f0277d..32bef4cb8d91 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -91,6 +91,9 @@ pub mod session; #[cfg(feature = "backend_udev")] pub mod udev; +#[cfg(feature = "backend_vulkan")] +pub mod vulkan; + #[cfg(feature = "backend_winit")] pub mod winit; diff --git a/src/backend/vulkan/inner.rs b/src/backend/vulkan/inner.rs new file mode 100644 index 000000000000..814c5e6275e9 --- /dev/null +++ b/src/backend/vulkan/inner.rs @@ -0,0 +1,74 @@ +use std::{ + ffi::{CStr, CString}, + fmt, +}; + +use ash::{extensions::ext::DebugUtils, vk}; +use slog::Logger; + +use super::{version::Version, LoadError, LIBRARY}; + +pub struct InstanceInner { + pub instance: ash::Instance, + pub version: Version, + pub debug_state: Option, + + /// Enabled instance extensions. + pub enabled_extensions: Vec<&'static CStr>, +} + +pub struct DebugState { + pub debug_utils: DebugUtils, + pub debug_messenger: vk::DebugUtilsMessengerEXT, + pub logger_ptr: *mut Logger, +} + +impl fmt::Debug for InstanceInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("InstanceInner") + .field("instance", &self.instance.handle()) + .finish_non_exhaustive() + } +} + +impl Drop for InstanceInner { + fn drop(&mut self) { + let logger = if let Some(debug) = &self.debug_state { + unsafe { + debug + .debug_utils + .destroy_debug_utils_messenger(debug.debug_messenger, None); + } + + Some(unsafe { Box::from_raw(debug.logger_ptr as *mut slog::Logger) }) + } else { + None + }; + + // Users of `Instance` are responsible for compliance with `VUID-vkDestroyInstance-instance-00629`. + + // SAFETY (Host Synchronization): InstanceInner is always stored in an Arc, therefore destruction is + // synchronized (since the inner value of an Arc is always dropped on a single thread). + unsafe { self.instance.destroy_instance(None) }; + + // Now that the instance has been destroyed, we can destroy the logger. + drop(logger); + } +} + +impl super::Instance { + pub(super) fn enumerate_layers() -> Result, LoadError> { + let library = LIBRARY.as_ref().or(Err(LoadError))?; + + let layers = library + .enumerate_instance_layer_properties() + .or(Err(LoadError))? + .into_iter() + .map(|properties| { + // SAFETY: Vulkan guarantees the string is null terminated. + unsafe { CStr::from_ptr(&properties.layer_name as *const _) }.to_owned() + }); + + Ok(layers) + } +} diff --git a/src/backend/vulkan/mod.rs b/src/backend/vulkan/mod.rs new file mode 100644 index 000000000000..01c037a93002 --- /dev/null +++ b/src/backend/vulkan/mod.rs @@ -0,0 +1,740 @@ +//! 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)] +#![deny(unsafe_op_in_unsafe_fn)] + +use std::{ + env::{self, VarError}, + ffi::{CStr, CString}, + sync::Arc, +}; + +use ash::{ + extensions::ext::DebugUtils, + prelude::VkResult, + vk::{self, PhysicalDeviceDrmPropertiesEXT}, + Entry, +}; +use libc::c_void; +use nix::sys::stat; +use once_cell::sync::Lazy; +use scopeguard::ScopeGuard; + +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: Lazy> = + Lazy::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), +} + +/// 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); + +impl Instance { + /// Creates a new [`Instance`]. + pub fn new( + max_version: Version, + app_info: Option, + logger: L, + ) -> Result + where + L: Into>, + { + unsafe { Self::with_extensions(max_version, app_info, logger, &[]) } + } + + /// 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, + logger: L, + extensions: &[&'static CStr], + ) -> Result + where + L: Into>, + { + let logger = + crate::slog_or_fallback(logger.into()).new(slog::o!("smithay_module" => "backend_vulkan")); + + 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, &logger); + + // Determine the maximum instance version that is possible. + let max_version = { + 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 { + slog::error!(logger, "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())); + + let available_layers = Self::enumerate_layers()?.collect::>(); + let available_extensions = Self::enumerate_extensions()?.collect::>(); + + let mut layers = Vec::new(); + + // Enable debug layers if present and debug assertions are enabled. + if cfg!(debug_assertions) { + const VALIDATION: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(b"VK_LAYER_KHRONOS_validation\0") }; + + if available_layers + .iter() + .any(|layer| layer.as_c_str() == VALIDATION) + { + layers.push(VALIDATION); + } else { + slog::warn!( + logger, + "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() == vk::ExtDebugUtilsFn::name()); + + if has_debug_utils { + enabled_extensions.push(vk::ExtDebugUtilsFn::name()); + } + + // Both of these are safe because both vecs contain static CStrs. + let extension_pointers = enabled_extensions + .iter() + .map(|name| name.as_ptr()) + .collect::>(); + let layer_pointers = layers.iter().map(|name| name.as_ptr()).collect::>(); + + 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::builder() + .api_version(api_version.to_raw()) + // SAFETY: null terminated with no interior null bytes. + .engine_name(unsafe { CStr::from_bytes_with_nul_unchecked(b"Smithay\0") }) + .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::builder() + .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 messenger_logger = logger.new(slog::o!("vulkan" => "debug_messenger")); + let debug_utils = DebugUtils::new(library, &instance); + + // Place the pointer to the logger in a scopeguard to prevent a memory leak in case creating the + // debug messenger fails. + let logger_ptr = scopeguard::guard(Box::into_raw(Box::new(messenger_logger)), |ptr| unsafe { + let _ = Box::from_raw(ptr); + }); + + let create_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() + .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(*logger_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 logger_ptr = ScopeGuard::into_inner(logger_ptr); + + Some(DebugState { + debug_utils, + debug_messenger, + logger_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); + let inner = InstanceInner { + instance, + version: api_version, + debug_state, + enabled_extensions, + }; + + slog::info!(logger, "Created new instance" ; slog::o!("version" => format!("{}", api_version))); + slog::info!( + logger, + "Enabled instance extensions: {:?}", + inner.enabled_extensions + ); + + Ok(Instance(Arc::new(inner))) + } + + /// Returns an iterator which contains the available instance extensions on the system. + pub fn enumerate_extensions() -> Result, LoadError> { + let library = LIBRARY.as_ref().or(Err(LoadError))?; + + let extensions = 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::>() + .into_iter(); + + Ok(extensions) + } + + /// Returns the enabled instance extensions. + pub fn enabled_extensions(&self) -> impl Iterator { + 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 + } +} + +// SAFETY: Destruction is externally synchronized (using an internal Arc). +unsafe impl Send for Instance {} +unsafe impl Sync for 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, + instance: Instance, +} + +impl PhysicalDevice { + /// Enumerates over all physical devices available on the system, returning an iterator of [`PhysicalDevice`] + pub fn enumerate(instance: &Instance) -> VkResult> { + // 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: + 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 Vulkan 1.1 physical device features. + /// + /// Confusingly, this is only available if the device supports at least Version 1.2 (since this type was + /// added in Vulkan 1.2). + pub fn features_1_1(&self) -> Option { + self.info.features_1_1 + } + + /// Returns the Vulkan 1.2 physical device features. + /// + /// This will return [`None`] if the physical device does not support at least Vulkan 1.2. + pub fn features_1_2(&self) -> Option { + self.info.features_1_2 + } + + /// Returns the Vulkan 1.3 physical device features. + /// + /// This will return [`None`] if the physical device does not support at least Vulkan 1.3. + pub fn features_1_3(&self) -> Option { + self.info.features_1_3 + } + + /// 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 Vulkan 1.1 physical device properties. + /// + /// Confusingly, this is only available if the device supports at least Version 1.2 (since this type was + /// added in Vulkan 1.2). + pub fn properties_1_1(&self) -> Option { + self.info.properties_1_1 + } + + /// Returns the Vulkan 1.2 physical device properties. + /// + /// This will return [`None`] if the physical device does not support at least Vulkan 1.2. + pub fn properties_1_2(&self) -> Option { + self.info.properties_1_2 + } + + /// Returns the Vulkan 1.3 physical device properties. + /// + /// This will return [`None`] if the physical device does not support at least Vulkan 1.3. + pub fn properties_1_3(&self) -> Option { + self.info.properties_1_3 + } + + /// 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 + } + + /// 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")] + pub fn primary_node(&self) -> Option { + self.info + .properties_drm + .filter(|props| props.has_primary == vk::TRUE) + .and_then(|props| { + DrmNode::from_dev_id(stat::makedev(props.primary_major as _, props.primary_minor as _)).ok() + }) + } + + /// 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")] + pub fn render_node(&self) -> Option { + self.info + .properties_drm + .filter(|props| props.has_render == vk::TRUE) + .and_then(|props| { + DrmNode::from_dev_id(stat::makedev(props.render_major as _, props.render_minor as _)).ok() + }) + } + + /// Gets some physical device properties. + /// + /// This function is used to get some physical device data associated with an extension and 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) } + } + + /// Returns the device extensions supported by the physical device. + pub fn device_extensions(&self) -> impl Iterator { + 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 { + 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)] +struct PhdInfo { + api_version: Version, + name: String, + properties: vk::PhysicalDeviceProperties, + features: vk::PhysicalDeviceFeatures, + maintenance_3: vk::PhysicalDeviceMaintenance3Properties, + properties_1_1: Option, + features_1_1: Option, + properties_1_2: Option, + features_1_2: Option, + properties_1_3: Option, + features_1_3: Option, + /// Information about the DRM device which corresponds to this physical device. + properties_drm: Option, + driver: Option, +} + +fn get_env_or_max_version(max_version: Version, logger: &slog::Logger) -> Version { + // Consider max version overrides from env + match env::var("SMITHAY_VK_VERSION") { + Ok(version) => { + let overriden_version = match &version[..] { + "1.0" => { + slog::warn!( + logger, + "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 { + slog::warn!( + logger, + "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 { + slog::warn!(logger, "SMITHAY_VK_VERSION was set to an unknown Vulkan version"); + max_version + } + } + + Err(VarError::NotUnicode(_)) => { + slog::warn!( + logger, + "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, + logger: *mut c_void, +) -> vk::Bool32 { + let _ = std::panic::catch_unwind(|| { + // Get the logger from the user data pointer we gave to Vulkan. + // + // The logger is allocated on the heap using a box, but we do not want to drop the logger, so read from + // the pointer. + let logger: &slog::Logger = unsafe { (logger as *mut slog::Logger).as_ref() }.unwrap(); + + // 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 => { + slog::trace!(logger, "{}", message ; "ty" => ty) + } + vk::DebugUtilsMessageSeverityFlagsEXT::INFO => slog::info!(logger, "{}", message ; "ty" => ty), + vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => slog::warn!(logger, "{}", message ; "ty" => ty), + vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => slog::error!(logger, "{}", message ; "ty" => ty), + _ => (), + } + }); + + // Must always return false. + vk::FALSE +} + +#[cfg(test)] +mod tests { + use super::{Instance, PhysicalDevice}; + + fn is_send_sync() {} + + /// Test that both [`Instance`] and [`PhysicalDevice`] are Send and Sync. + #[test] + fn send_sync() { + is_send_sync::(); + is_send_sync::(); + } +} diff --git a/src/backend/vulkan/phd.rs b/src/backend/vulkan/phd.rs new file mode 100644 index 000000000000..e7568349e3db --- /dev/null +++ b/src/backend/vulkan/phd.rs @@ -0,0 +1,234 @@ +//! Functions to get physical device information + +use std::ffi::{CStr, CString}; + +use ash::{prelude::VkResult, vk}; + +use super::{version::Version, DriverInfo, PhdInfo}; + +impl super::PhysicalDevice { + /// # Safety: + /// + /// The physical device must belong to the specified instance. + pub(super) unsafe fn from_phd( + instance: &super::Instance, + phd: vk::PhysicalDevice, + ) -> VkResult> { + let instance = instance.clone(); + + let extensions = unsafe { instance.handle().enumerate_device_extension_properties(phd) }?; + let extensions = extensions + .iter() + .map(|extension| { + // SAFETY: Vulkan guarantees the device name is valid UTF-8 with a null terminator. + unsafe { CStr::from_ptr(&extension.extension_name as *const _) }.to_owned() + }) + .collect::>(); + + if let Some(info) = + unsafe { PhdInfo::from_phd(instance.handle(), instance.api_version(), phd, &extensions) } + { + Ok(Some(Self { + phd, + info, + extensions, + instance, + })) + } else { + Ok(None) + } + } +} + +impl super::PhdInfo { + /// Returns [`None`] if the physical device does not support Vulkan 1.1 + /// + /// # Panics + /// + /// - If the instance version is not at least Vulkan 1.1 + /// + /// # Safety + /// + /// - The instance version must be the same version the instance was created with. + /// - The physical device must belong to the specified instance. + unsafe fn from_phd( + instance: &ash::Instance, + instance_version: Version, + phd: vk::PhysicalDevice, + supported_extensions: &[CString], + ) -> Option { + assert!(instance_version >= Version::VERSION_1_1); + + let properties = unsafe { instance.get_physical_device_properties(phd) }; + let features = unsafe { instance.get_physical_device_features(phd) }; + + // Pick the lower of the instance version and device version to get the actual version of Vulkan that + // can be used with the device. + let api_version = Version::from_raw(u32::min(properties.api_version, instance_version.to_raw())); + + if api_version < Version::VERSION_1_1 { + // Device does not support Vulkan 1.1, so ignore it. + return None; + } + + // SAFETY: Vulkan guarantees the device name is valid UTF-8 with a null terminator. + let name = unsafe { CStr::from_ptr(&properties.device_name as *const _) } + .to_str() + .unwrap() + .to_string(); + + // Maintenance3 + // + // SAFETY: Maintenance3 extension is supported since Vulkan 1.1 + let maintenance_3 = unsafe { + let mut maintenance_3 = vk::PhysicalDeviceMaintenance3Properties::default(); + let mut props = vk::PhysicalDeviceProperties2::builder().push_next(&mut maintenance_3); + instance.get_physical_device_properties2(phd, &mut props); + maintenance_3 + }; + + // Vulkan 1.1 features + // + // Confusingly these types were not added until Vulkan 1.2 + let (properties_1_1, features_1_1) = { + if api_version >= Version::VERSION_1_2 { + unsafe { + let mut properties_1_1 = vk::PhysicalDeviceVulkan11Properties::default(); + let mut props = vk::PhysicalDeviceProperties2::builder().push_next(&mut properties_1_1); + instance.get_physical_device_properties2(phd, &mut props); + + let mut features_1_1 = vk::PhysicalDeviceVulkan11Features::default(); + let mut features = vk::PhysicalDeviceFeatures2::builder().push_next(&mut features_1_1); + instance.get_physical_device_features2(phd, &mut features); + + (Some(properties_1_1), Some(features_1_1)) + } + } else { + (None, None) + } + }; + + // Vulkan 1.2 + let (properties_1_2, features_1_2) = { + if api_version >= Version::VERSION_1_2 { + // SAFETY: The physical device supports Vulkan 1.2 + unsafe { + let mut properties_1_2 = vk::PhysicalDeviceVulkan12Properties::default(); + let mut props = vk::PhysicalDeviceProperties2::builder().push_next(&mut properties_1_2); + instance.get_physical_device_properties2(phd, &mut props); + + let mut features_1_2 = vk::PhysicalDeviceVulkan12Features::default(); + let mut features = vk::PhysicalDeviceFeatures2::builder().push_next(&mut features_1_2); + instance.get_physical_device_features2(phd, &mut features); + + (Some(properties_1_2), Some(features_1_2)) + } + } else { + (None, None) + } + }; + + // Vulkan 1.3 + let (properties_1_3, features_1_3) = { + if api_version >= Version::VERSION_1_3 { + // SAFETY: The physical device supports Vulkan 1.3 + unsafe { + let mut properties_1_2 = vk::PhysicalDeviceVulkan13Properties::default(); + let mut props = vk::PhysicalDeviceProperties2::builder().push_next(&mut properties_1_2); + instance.get_physical_device_properties2(phd, &mut props); + + let mut features_1_2 = vk::PhysicalDeviceVulkan13Features::default(); + let mut features = vk::PhysicalDeviceFeatures2::builder().push_next(&mut features_1_2); + instance.get_physical_device_features2(phd, &mut features); + + (Some(properties_1_2), Some(features_1_2)) + } + } else { + (None, None) + } + }; + + // VK_EXT_physical_device_drm + let properties_drm = if supported_extensions + .iter() + .any(|name| name.as_c_str() == vk::ExtPhysicalDeviceDrmFn::name()) + { + // SAFETY: The caller has garunteed the physical device supports VK_EXT_physical_device_drm + unsafe { + let mut properties_drm = vk::PhysicalDeviceDrmPropertiesEXT::default(); + let mut props = vk::PhysicalDeviceProperties2::builder().push_next(&mut properties_drm); + instance.get_physical_device_properties2(phd, &mut props); + Some(properties_drm) + } + } else { + None + }; + + // VK_KHR_driver_properties or Vulkan 1.2 + let driver = if api_version >= Version::VERSION_1_2 { + // Copy the data from the Vulkan 1.2 properties into the extension struct for data creation. + let properties = vk::PhysicalDeviceDriverProperties { + driver_id: properties_1_2.unwrap().driver_id, + driver_name: properties_1_2.unwrap().driver_name, + driver_info: properties_1_2.unwrap().driver_info, + conformance_version: properties_1_2.unwrap().conformance_version, + ..Default::default() + }; + + Some(unsafe { DriverInfo::from_driver_properties(properties) }) + } else if supported_extensions + .iter() + .any(|name| name.as_c_str() == vk::KhrDriverPropertiesFn::name()) + { + // SAFETY: VK_KHR_driver_properties is supported + unsafe { + let mut driver_props = vk::PhysicalDeviceDriverProperties::default(); + let mut props = vk::PhysicalDeviceProperties2::builder().push_next(&mut driver_props); + instance.get_physical_device_properties2(phd, &mut props); + + Some(DriverInfo::from_driver_properties(driver_props)) + } + } else { + None + }; + + Some(Self { + api_version, + name, + properties, + features, + maintenance_3, + properties_1_1, + features_1_1, + properties_1_2, + features_1_2, + properties_1_3, + features_1_3, + properties_drm, + driver, + }) + } +} + +impl super::DriverInfo { + unsafe fn from_driver_properties(properties: vk::PhysicalDeviceDriverProperties) -> DriverInfo { + // SAFETY: Vulkan guarantees the driver name is valid UTF-8 with a null terminator. + let name = unsafe { CStr::from_ptr(&properties.driver_name as *const _) } + .to_str() + .unwrap() + .to_string(); + + // SAFETY: Vulkan guarantees the driver info is valid UTF-8 with a null terminator. + let info = unsafe { CStr::from_ptr(&properties.driver_info as *const _) } + .to_str() + .unwrap() + .to_string(); + + DriverInfo { + id: properties.driver_id, + name, + info, + conformance: properties.conformance_version, + } + } +} diff --git a/src/backend/vulkan/version.rs b/src/backend/vulkan/version.rs new file mode 100644 index 000000000000..d98b3c9c4d62 --- /dev/null +++ b/src/backend/vulkan/version.rs @@ -0,0 +1,104 @@ +//! The [`Version`] type. + +use std::{ + cmp::Ordering, + fmt::{self, Formatter}, +}; + +use ash::vk; + +/// A Vulkan API version. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] +pub struct Version { + /// The variant of the Vulkan API. + /// + /// Generally this value will be `0` because the Vulkan specification uses variant `0`. + pub variant: u32, + + /// The major version of the Vulkan API. + pub major: u32, + + /// The minor version of the Vulkan API. + pub minor: u32, + + /// The patch version of the Vulkan API. + /// + /// Most Vulkan API calls which take a version typically ignore the patch value. Consumers of the Vulkan API may + /// typically ignore the patch value. + pub patch: u32, +} + +impl Version { + /// Version 1.0 of the Vulkan API. + pub const VERSION_1_0: Version = Version::from_raw(vk::API_VERSION_1_0); + + /// Version 1.1 of the Vulkan API. + pub const VERSION_1_1: Version = Version::from_raw(vk::API_VERSION_1_1); + + /// Version 1.2 of the Vulkan API. + pub const VERSION_1_2: Version = Version::from_raw(vk::API_VERSION_1_2); + + /// Version 1.3 of the Vulkan API. + pub const VERSION_1_3: Version = Version::from_raw(vk::API_VERSION_1_3); + + /// The version of Smithay. + pub const SMITHAY: Version = Version { + // TODO: May be useful to place the version information in a single spot that isn't just Vulkan + variant: 0, + major: 0, + minor: 3, + patch: 0, + }; + + /// Converts a packed version into a version struct. + pub const fn from_raw(raw: u32) -> Version { + Version { + variant: vk::api_version_variant(raw), + major: vk::api_version_major(raw), + minor: vk::api_version_minor(raw), + patch: vk::api_version_patch(raw), + } + } + + /// Converts a version struct into a packed version. + pub const fn to_raw(self) -> u32 { + vk::make_api_version(self.variant, self.major, self.minor, self.patch) + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{}.{}.{} variant {}", + self.major, self.minor, self.patch, self.variant + ) + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Version { + fn cmp(&self, other: &Self) -> Ordering { + match self.variant.cmp(&other.variant) { + Ordering::Equal => {} + ord => return ord, + } + + match self.major.cmp(&other.major) { + Ordering::Equal => {} + ord => return ord, + } + + match self.minor.cmp(&other.minor) { + Ordering::Equal => {} + ord => return ord, + } + + self.patch.cmp(&other.patch) + } +}