aboutsummaryrefslogtreecommitdiff
path: root/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs
diff options
context:
space:
mode:
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi/capabilities.rs')
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/capabilities.rs405
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(),
+ })
+}