diff options
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi/capabilities.rs')
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/capabilities.rs | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs b/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs new file mode 100644 index 0000000..94fb3a6 --- /dev/null +++ b/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs @@ -0,0 +1,405 @@ +#![allow(unsafe_code)] + +use ash::vk; +use std::ffi::CStr; + +use super::{VulkanInstanceProbe, VulkanSurfaceProbe}; +use crate::policy::{ + compare_reports, plan_vulkan_swapchain, validate_device, VulkanCapabilityError, + VulkanCapabilityReport, VulkanDeviceType, VulkanPhysicalDeviceRecord, VulkanQueueFamily, + VulkanSurfaceFormat, VulkanSwapchainError, VulkanSwapchainPlan, VulkanSwapchainRequest, + VulkanSwapchainSurfaceCapabilities, +}; + +/// Live Vulkan device/surface capability probe. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct VulkanRuntimeCapabilityProbe { + /// Selected device/queue capability report. + pub capability: VulkanCapabilityReport, + /// Swapchain plan built from the selected device and live surface capabilities. + pub swapchain: VulkanSwapchainPlan, +} + +/// Live Vulkan device/surface capability probe error. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum VulkanRuntimeCapabilityError { + /// Physical device enumeration failed. + EnumerateDevicesFailed { + /// Vulkan result. + result: vk::Result, + }, + /// Device extension enumeration failed. + EnumerateDeviceExtensionsFailed { + /// Device name or index context. + device: String, + /// Vulkan result. + result: vk::Result, + }, + /// Queue-family present support query failed. + PresentSupportFailed { + /// Device name. + device: String, + /// Queue-family index. + queue_family: u32, + /// Vulkan result. + result: vk::Result, + }, + /// Surface format query failed. + SurfaceFormatsFailed { + /// Device name. + device: String, + /// Vulkan result. + result: vk::Result, + }, + /// Surface capability query failed. + SurfaceCapabilitiesFailed { + /// Device name. + device: String, + /// Vulkan result. + result: vk::Result, + }, + /// Present mode query failed. + PresentModesFailed { + /// Device name. + device: String, + /// Vulkan result. + result: vk::Result, + }, + /// No device satisfied Stage 0 capability policy. + Capability(VulkanCapabilityError), + /// Live surface capabilities could not produce a swapchain plan. + Swapchain(VulkanSwapchainError), +} + +impl std::fmt::Display for VulkanRuntimeCapabilityError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EnumerateDevicesFailed { result } => { + write!(f, "Vulkan physical device enumeration failed: {result:?}") + } + Self::EnumerateDeviceExtensionsFailed { device, result } => write!( + f, + "Vulkan device {device} extension enumeration failed: {result:?}" + ), + Self::PresentSupportFailed { + device, + queue_family, + result, + } => write!( + f, + "Vulkan device {device} queue family {queue_family} present support query failed: {result:?}" + ), + Self::SurfaceFormatsFailed { device, result } => write!( + f, + "Vulkan device {device} surface format query failed: {result:?}" + ), + Self::SurfaceCapabilitiesFailed { device, result } => write!( + f, + "Vulkan device {device} surface capabilities query failed: {result:?}" + ), + Self::PresentModesFailed { device, result } => write!( + f, + "Vulkan device {device} present mode query failed: {result:?}" + ), + Self::Capability(error) => write!(f, "{error}"), + Self::Swapchain(error) => write!(f, "{error}"), + } + } +} + +impl std::error::Error for VulkanRuntimeCapabilityError {} + +pub(super) struct SelectedLiveDevice { + pub(super) physical_device: vk::PhysicalDevice, + pub(super) runtime: VulkanRuntimeCapabilityProbe, +} + +struct LiveDeviceCandidate { + physical_device: vk::PhysicalDevice, + capability: VulkanCapabilityReport, + surface_formats: Vec<VulkanSurfaceFormat>, + present_modes: Vec<i32>, + surface_capabilities: VulkanSwapchainSurfaceCapabilities, +} + +/// Probes live Vulkan device, queue, surface and swapchain capabilities. +/// +/// # Errors +/// +/// Returns [`VulkanRuntimeCapabilityError`] when device enumeration, surface +/// capability queries, Stage 0 device selection, or swapchain planning fails. +pub fn probe_vulkan_runtime_capabilities( + instance: &VulkanInstanceProbe, + surface: &VulkanSurfaceProbe, + drawable_extent: (u32, u32), +) -> Result<VulkanRuntimeCapabilityProbe, VulkanRuntimeCapabilityError> { + let selected = select_live_device_candidate(instance, surface, drawable_extent)?; + Ok(selected.runtime) +} + +pub(super) fn select_live_device_candidate( + instance: &VulkanInstanceProbe, + surface: &VulkanSurfaceProbe, + drawable_extent: (u32, u32), +) -> Result<SelectedLiveDevice, VulkanRuntimeCapabilityError> { + let devices = { + // SAFETY: The Vulkan instance is live for this query and no handles are retained. + unsafe { instance.instance.enumerate_physical_devices() }.map_err(|error| { + VulkanRuntimeCapabilityError::EnumerateDevicesFailed { result: error } + })? + }; + let mut best: Option<LiveDeviceCandidate> = None; + let mut last_error = None; + for (index, device) in devices.iter().copied().enumerate() { + let candidate = match live_device_candidate(instance, surface, device, index) { + Ok(candidate) => candidate, + Err(err) => { + last_error = Some(err); + continue; + } + }; + match &best { + Some(existing) + if compare_reports(&candidate.capability, &existing.capability) + != std::cmp::Ordering::Greater => {} + _ => best = Some(candidate), + } + } + let best = best.ok_or_else(|| { + last_error.unwrap_or(VulkanRuntimeCapabilityError::Capability( + VulkanCapabilityError::NoPhysicalDevice, + )) + })?; + let swapchain = plan_vulkan_swapchain(&VulkanSwapchainRequest { + drawable_extent, + formats: best.surface_formats, + present_modes: best.present_modes, + capabilities: best.surface_capabilities, + preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(), + }) + .map_err(VulkanRuntimeCapabilityError::Swapchain)?; + Ok(SelectedLiveDevice { + physical_device: best.physical_device, + runtime: VulkanRuntimeCapabilityProbe { + capability: best.capability, + swapchain, + }, + }) +} + +fn live_device_candidate( + instance: &VulkanInstanceProbe, + surface: &VulkanSurfaceProbe, + device: vk::PhysicalDevice, + index: usize, +) -> Result<LiveDeviceCandidate, VulkanRuntimeCapabilityError> { + let properties = { + // SAFETY: `device` was returned by this live instance and the result is copied by value. + unsafe { instance.instance.get_physical_device_properties(device) } + }; + let name = physical_device_name(&properties, index); + let queue_properties = { + // SAFETY: `device` was returned by this live instance and the result is owned by Rust. + unsafe { + instance + .instance + .get_physical_device_queue_family_properties(device) + } + }; + let extensions = live_device_extensions(instance, device, &name)?; + let surface_formats = live_surface_formats(surface, device, &name)?; + let present_modes = live_present_modes(surface, device, &name)?; + let surface_capabilities = live_surface_capabilities(surface, device, &name)?; + let queue_families = queue_properties + .iter() + .enumerate() + .map(|(queue_index, properties)| { + let index = u32::try_from(queue_index).unwrap_or(u32::MAX); + let present = { + // SAFETY: The physical device, surface and queue-family index are live query inputs. + unsafe { + surface.loader.get_physical_device_surface_support( + device, + index, + surface.surface, + ) + } + } + .map_err(|error| VulkanRuntimeCapabilityError::PresentSupportFailed { + device: name.clone(), + queue_family: index, + result: error, + })?; + Ok(VulkanQueueFamily { + index, + graphics: properties.queue_flags.contains(vk::QueueFlags::GRAPHICS), + present, + }) + }) + .collect::<Result<Vec<_>, VulkanRuntimeCapabilityError>>()?; + let record = VulkanPhysicalDeviceRecord { + name, + api_version: properties.api_version, + device_type: match properties.device_type { + vk::PhysicalDeviceType::DISCRETE_GPU => VulkanDeviceType::DiscreteGpu, + vk::PhysicalDeviceType::INTEGRATED_GPU => VulkanDeviceType::IntegratedGpu, + vk::PhysicalDeviceType::CPU => VulkanDeviceType::Cpu, + _ => VulkanDeviceType::Other, + }, + extensions, + queue_families, + surface_formats: surface_formats.clone(), + present_modes: present_modes.clone(), + surface_capabilities, + }; + let capability = validate_device(&record).map_err(VulkanRuntimeCapabilityError::Capability)?; + Ok(LiveDeviceCandidate { + physical_device: device, + capability, + surface_formats, + present_modes, + surface_capabilities, + }) +} + +pub(super) fn unique_queue_families(graphics: u32, present: u32) -> Vec<u32> { + if graphics == present { + vec![graphics] + } else { + vec![graphics, present] + } +} + +fn physical_device_name(properties: &vk::PhysicalDeviceProperties, index: usize) -> String { + // SAFETY: Vulkan device names are fixed-size NUL-terminated C strings per the spec. + let name = unsafe { CStr::from_ptr(properties.device_name.as_ptr()) } + .to_string_lossy() + .trim() + .to_string(); + if name.is_empty() { + format!("physical-device-{index}") + } else { + name + } +} + +fn live_device_extensions( + instance: &VulkanInstanceProbe, + device: vk::PhysicalDevice, + name: &str, +) -> Result<Vec<String>, VulkanRuntimeCapabilityError> { + let properties = { + // SAFETY: `device` was returned by this live instance and no borrowed data escapes. + unsafe { + instance + .instance + .enumerate_device_extension_properties(device) + } + } + .map_err( + |error| VulkanRuntimeCapabilityError::EnumerateDeviceExtensionsFailed { + device: name.to_string(), + result: error, + }, + )?; + let mut extensions = properties + .iter() + .map(|property| { + // SAFETY: Vulkan extension names are fixed-size NUL-terminated C strings per the spec. + unsafe { CStr::from_ptr(property.extension_name.as_ptr()) } + .to_string_lossy() + .into_owned() + }) + .collect::<Vec<_>>(); + extensions.sort(); + extensions.dedup(); + Ok(extensions) +} + +pub(super) fn live_surface_formats( + surface: &VulkanSurfaceProbe, + device: vk::PhysicalDevice, + name: &str, +) -> Result<Vec<VulkanSurfaceFormat>, VulkanRuntimeCapabilityError> { + let formats = { + // SAFETY: The physical device and surface are live query inputs and no handles are retained. + unsafe { + surface + .loader + .get_physical_device_surface_formats(device, surface.surface) + } + } + .map_err(|error| VulkanRuntimeCapabilityError::SurfaceFormatsFailed { + device: name.to_string(), + result: error, + })?; + Ok(formats + .into_iter() + .map(|format| VulkanSurfaceFormat { + format: format.format.as_raw(), + color_space: format.color_space.as_raw(), + }) + .collect()) +} + +pub(super) fn live_present_modes( + surface: &VulkanSurfaceProbe, + device: vk::PhysicalDevice, + name: &str, +) -> Result<Vec<i32>, VulkanRuntimeCapabilityError> { + let modes = { + // SAFETY: The physical device and surface are live query inputs and no handles are retained. + unsafe { + surface + .loader + .get_physical_device_surface_present_modes(device, surface.surface) + } + } + .map_err(|error| VulkanRuntimeCapabilityError::PresentModesFailed { + device: name.to_string(), + result: error, + })?; + Ok(modes.into_iter().map(vk::PresentModeKHR::as_raw).collect()) +} + +pub(super) fn live_surface_capabilities( + surface: &VulkanSurfaceProbe, + device: vk::PhysicalDevice, + name: &str, +) -> Result<VulkanSwapchainSurfaceCapabilities, VulkanRuntimeCapabilityError> { + let capabilities = { + // SAFETY: The physical device and surface are live query inputs and no handles are retained. + unsafe { + surface + .loader + .get_physical_device_surface_capabilities(device, surface.surface) + } + } + .map_err( + |error| VulkanRuntimeCapabilityError::SurfaceCapabilitiesFailed { + device: name.to_string(), + result: error, + }, + )?; + Ok(VulkanSwapchainSurfaceCapabilities { + current_extent: if capabilities.current_extent.width == u32::MAX { + None + } else { + Some(( + capabilities.current_extent.width, + capabilities.current_extent.height, + )) + }, + min_extent: ( + capabilities.min_image_extent.width, + capabilities.min_image_extent.height, + ), + max_extent: ( + capabilities.max_image_extent.width, + capabilities.max_image_extent.height, + ), + min_image_count: capabilities.min_image_count, + max_image_count: capabilities.max_image_count, + supported_usage_flags: capabilities.supported_usage_flags.as_raw(), + }) +} |
