aboutsummaryrefslogtreecommitdiff
path: root/adapters/fparkan-render-vulkan/src/ffi/swapchain.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-06-25 06:07:31 +0300
committerValentin Popov <valentin@popov.link>2026-06-25 10:45:37 +0300
commit3c3221566528774651e709bc86de7b4a58ab5966 (patch)
tree29917bb6c8a996cf482b4a864ea8fe6c6cefb17e /adapters/fparkan-render-vulkan/src/ffi/swapchain.rs
parentf91378b884ea931e97a6f0c825083b6aec06eccb (diff)
downloadfparkan-3c3221566528774651e709bc86de7b4a58ab5966.tar.xz
fparkan-3c3221566528774651e709bc86de7b4a58ab5966.zip
refactor(vulkan-ffi): extract swapchain probe module
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi/swapchain.rs')
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/swapchain.rs220
1 files changed, 220 insertions, 0 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi/swapchain.rs b/adapters/fparkan-render-vulkan/src/ffi/swapchain.rs
new file mode 100644
index 0000000..adfca60
--- /dev/null
+++ b/adapters/fparkan-render-vulkan/src/ffi/swapchain.rs
@@ -0,0 +1,220 @@
+#![allow(unsafe_code)]
+
+use ash::{khr::swapchain, vk};
+
+use super::{
+ runtime::{
+ live_present_modes, live_surface_capabilities, live_surface_formats, unique_queue_families,
+ },
+ VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanRuntimeCapabilityError,
+ VulkanSurfaceProbe,
+};
+use crate::policy::{
+ plan_vulkan_swapchain, select_composite_alpha, VulkanSwapchainError, VulkanSwapchainPlan,
+ VulkanSwapchainRequest,
+};
+
+/// Created Vulkan swapchain probe.
+pub struct VulkanSwapchainProbe {
+ loader: swapchain::Device,
+ swapchain: vk::SwapchainKHR,
+ /// Deterministic swapchain creation report.
+ pub report: VulkanSwapchainReport,
+}
+
+impl Drop for VulkanSwapchainProbe {
+ fn drop(&mut self) {
+ // SAFETY: The swapchain was created by this probe and is destroyed once during drop.
+ unsafe { self.loader.destroy_swapchain(self.swapchain, None) };
+ }
+}
+
+impl VulkanSwapchainProbe {
+ /// Returns the live swapchain handle.
+ #[must_use]
+ pub fn swapchain(&self) -> vk::SwapchainKHR {
+ self.swapchain
+ }
+
+ /// Returns the swapchain extension loader for this live swapchain.
+ #[must_use]
+ pub fn loader(&self) -> &swapchain::Device {
+ &self.loader
+ }
+}
+
+/// Runtime swapchain creation report.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct VulkanSwapchainReport {
+ /// Report schema version.
+ pub schema: u32,
+ /// Deterministic swapchain policy used for creation.
+ pub plan: VulkanSwapchainPlan,
+ /// Number of images returned by `vkGetSwapchainImagesKHR`.
+ pub image_count: u32,
+}
+
+/// Vulkan swapchain creation error.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum VulkanSwapchainProbeError {
+ /// Live runtime capability probing failed before swapchain creation.
+ Runtime(VulkanRuntimeCapabilityError),
+ /// Deterministic swapchain planning failed before create.
+ Plan(VulkanSwapchainError),
+ /// Surface capability query failed.
+ SurfaceCapabilitiesFailed {
+ /// Vulkan result.
+ result: vk::Result,
+ },
+ /// Swapchain creation failed.
+ CreateFailed {
+ /// Vulkan result.
+ result: vk::Result,
+ },
+ /// Swapchain image query failed.
+ ImagesFailed {
+ /// Vulkan result.
+ result: vk::Result,
+ },
+}
+
+impl std::fmt::Display for VulkanSwapchainProbeError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Runtime(error) => write!(f, "{error}"),
+ Self::Plan(error) => write!(f, "{error}"),
+ Self::SurfaceCapabilitiesFailed { result } => {
+ write!(f, "Vulkan surface capabilities query failed: {result:?}")
+ }
+ Self::CreateFailed { result } => {
+ write!(f, "Vulkan swapchain creation failed: {result:?}")
+ }
+ Self::ImagesFailed { result } => {
+ write!(f, "Vulkan swapchain image query failed: {result:?}")
+ }
+ }
+ }
+}
+
+impl std::error::Error for VulkanSwapchainProbeError {}
+
+/// Creates a Vulkan swapchain for the live logical device and surface.
+///
+/// # Errors
+///
+/// Returns [`VulkanSwapchainProbeError`] when live surface capability queries,
+/// swapchain creation, or swapchain image enumeration fails.
+pub fn create_vulkan_swapchain_probe(
+ instance: &VulkanInstanceProbe,
+ surface: &VulkanSurfaceProbe,
+ device: &VulkanLogicalDeviceProbe,
+) -> Result<VulkanSwapchainProbe, VulkanSwapchainProbeError> {
+ create_vulkan_swapchain_probe_for_extent(
+ instance,
+ surface,
+ device,
+ device.runtime.swapchain.extent,
+ vk::SwapchainKHR::null(),
+ )
+}
+
+/// Creates a Vulkan swapchain for the live logical device and surface at a specific extent.
+///
+/// # Errors
+///
+/// Returns [`VulkanSwapchainProbeError`] when live surface capability queries,
+/// swapchain creation, or swapchain image enumeration fails.
+pub fn create_vulkan_swapchain_probe_for_extent(
+ instance: &VulkanInstanceProbe,
+ surface: &VulkanSurfaceProbe,
+ device: &VulkanLogicalDeviceProbe,
+ drawable_extent: (u32, u32),
+ old_swapchain: vk::SwapchainKHR,
+) -> Result<VulkanSwapchainProbe, VulkanSwapchainProbeError> {
+ let raw_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.physical_device(), surface.surface)
+ }
+ }
+ .map_err(|error| VulkanSwapchainProbeError::SurfaceCapabilitiesFailed { result: error })?;
+ let surface_formats = live_surface_formats(
+ surface,
+ device.physical_device(),
+ &device.report.device_name,
+ )
+ .map_err(VulkanSwapchainProbeError::Runtime)?;
+ let present_modes = live_present_modes(
+ surface,
+ device.physical_device(),
+ &device.report.device_name,
+ )
+ .map_err(VulkanSwapchainProbeError::Runtime)?;
+ let capabilities = live_surface_capabilities(
+ surface,
+ device.physical_device(),
+ &device.report.device_name,
+ )
+ .map_err(VulkanSwapchainProbeError::Runtime)?;
+ let plan = plan_vulkan_swapchain(&VulkanSwapchainRequest {
+ drawable_extent,
+ formats: surface_formats,
+ present_modes,
+ capabilities,
+ preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(),
+ })
+ .map_err(VulkanSwapchainProbeError::Plan)?;
+ let queue_family_indices = unique_queue_families(
+ device.runtime.capability.graphics_queue_family,
+ device.runtime.capability.present_queue_family,
+ );
+ let sharing_mode = if queue_family_indices.len() > 1 {
+ vk::SharingMode::CONCURRENT
+ } else {
+ vk::SharingMode::EXCLUSIVE
+ };
+ let create_info = vk::SwapchainCreateInfoKHR::default()
+ .surface(surface.surface)
+ .min_image_count(plan.image_count)
+ .image_format(vk::Format::from_raw(plan.format.format))
+ .image_color_space(vk::ColorSpaceKHR::from_raw(plan.format.color_space))
+ .image_extent(vk::Extent2D {
+ width: plan.extent.0,
+ height: plan.extent.1,
+ })
+ .image_array_layers(1)
+ .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
+ .image_sharing_mode(sharing_mode)
+ .queue_family_indices(&queue_family_indices)
+ .pre_transform(raw_capabilities.current_transform)
+ .composite_alpha(select_composite_alpha(
+ raw_capabilities.supported_composite_alpha,
+ ))
+ .present_mode(vk::PresentModeKHR::from_raw(plan.present_mode))
+ .old_swapchain(old_swapchain)
+ .clipped(true);
+ let loader = swapchain::Device::new(&instance.instance, device.device());
+ // SAFETY: The create info references live instance/device/surface handles for this call.
+ let swapchain = unsafe { loader.create_swapchain(&create_info, None) }
+ .map_err(|error| VulkanSwapchainProbeError::CreateFailed { result: error })?;
+ // SAFETY: The swapchain was created above and the returned image handles are owned by it.
+ let images = match unsafe { loader.get_swapchain_images(swapchain) } {
+ Ok(images) => images,
+ Err(error) => {
+ // SAFETY: The swapchain was created above on this loader/device pair and is destroyed on setup failure.
+ unsafe { loader.destroy_swapchain(swapchain, None) };
+ return Err(VulkanSwapchainProbeError::ImagesFailed { result: error });
+ }
+ };
+ Ok(VulkanSwapchainProbe {
+ loader,
+ swapchain,
+ report: VulkanSwapchainReport {
+ schema: 1,
+ plan,
+ image_count: images.len().try_into().unwrap_or(u32::MAX),
+ },
+ })
+}