From b6b47ae6f612d365e77bbaf421b7c0927df12835 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Thu, 25 Jun 2026 06:12:20 +0400 Subject: refactor(vulkan-ffi): extract surface bootstrap module --- adapters/fparkan-render-vulkan/src/ffi.rs | 163 ++-------------------- adapters/fparkan-render-vulkan/src/ffi/surface.rs | 159 +++++++++++++++++++++ xtask/src/main.rs | 1 + 3 files changed, 168 insertions(+), 155 deletions(-) create mode 100644 adapters/fparkan-render-vulkan/src/ffi/surface.rs diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs index 070c2ad..9be61eb 100644 --- a/adapters/fparkan-render-vulkan/src/ffi.rs +++ b/adapters/fparkan-render-vulkan/src/ffi.rs @@ -28,6 +28,7 @@ //! This crate is the declared low-level Vulkan boundary. mod instance; +mod surface; pub use self::instance::{ create_vulkan_instance_probe, plan_vulkan_instance, probe_vulkan_loader, @@ -37,19 +38,20 @@ pub use self::instance::{ }; #[cfg(test)] use self::instance::{cstring_vec, ensure_instance_extensions_available}; +#[cfg(test)] +use self::surface::extension_name; +pub use self::surface::{ + create_vulkan_surface_probe, plan_vulkan_surface, render_surface_plan_json, VulkanSurfaceError, + VulkanSurfacePlan, VulkanSurfaceProbe, +}; use crate::policy::*; use crate::shader_manifest::{ triangle_shader_manifest, validate_shader_manifest, VulkanShaderManifestError, }; -use ash::{ - khr::{surface, swapchain}, - vk, -}; +use ash::{khr::swapchain, vk}; use fparkan_platform::NativeWindowHandles; -use serde::Serialize; use std::collections::BTreeSet; use std::ffi::{CStr, CString}; -use std::os::raw::c_char; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Mutex; /// Minimum Vulkan API version accepted by the Stage 0 backend. @@ -442,74 +444,6 @@ pub(crate) const TRIANGLE_FRAGMENT_SHADER_WORDS: &[u32] = &[ 0x0001_0038, ]; -/// Deterministic Vulkan surface creation plan. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct VulkanSurfacePlan { - /// Report schema version. - pub schema: u32, - /// Instance extensions required by the native display backend. - pub required_instance_extensions: Vec, -} - -/// Vulkan surface bootstrap error. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum VulkanSurfaceError { - /// No native raw window/display handles were available. - MissingNativeHandles, - /// Required platform surface extensions could not be enumerated. - RequiredExtensionsFailed { - /// Vulkan result. - result: vk::Result, - }, - /// A required extension pointer was not valid UTF-8. - InvalidExtensionName, - /// Surface creation failed. - CreateFailed { - /// Vulkan result. - result: vk::Result, - }, -} - -impl std::fmt::Display for VulkanSurfaceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::MissingNativeHandles => { - write!( - f, - "native window/display handles are required for Vulkan surface creation" - ) - } - Self::RequiredExtensionsFailed { result } => write!( - f, - "failed to enumerate required Vulkan surface extensions: {result:?}" - ), - Self::InvalidExtensionName => { - write!(f, "Vulkan surface extension name is not valid UTF-8") - } - Self::CreateFailed { result } => { - write!(f, "Vulkan surface creation failed: {result:?}") - } - } - } -} - -impl std::error::Error for VulkanSurfaceError {} - -/// Created Vulkan surface probe. -pub struct VulkanSurfaceProbe { - loader: surface::Instance, - surface: vk::SurfaceKHR, - /// Deterministic surface creation report. - pub report: VulkanSurfacePlan, -} - -impl Drop for VulkanSurfaceProbe { - fn drop(&mut self) { - // SAFETY: The `SurfaceKHR` was created by this probe and is destroyed once during drop. - unsafe { self.loader.destroy_surface(self.surface, None) }; - } -} - /// Live Vulkan device/surface capability probe. #[derive(Clone, Debug, Eq, PartialEq)] pub struct VulkanRuntimeCapabilityProbe { @@ -2308,61 +2242,6 @@ impl std::fmt::Display for VulkanSwapchainProbeError { impl std::error::Error for VulkanSwapchainProbeError {} -/// Builds a deterministic Vulkan surface plan from native window handles. -/// -/// # Errors -/// -/// Returns [`VulkanSurfaceError`] when no native handles exist or the platform -/// display backend has no Vulkan surface extension mapping. -pub fn plan_vulkan_surface( - handles: Option, -) -> Result { - let handles = handles.ok_or(VulkanSurfaceError::MissingNativeHandles)?; - let required = ash_window::enumerate_required_extensions(handles.display) - .map_err(|error| VulkanSurfaceError::RequiredExtensionsFailed { result: error })?; - let mut required_instance_extensions = Vec::with_capacity(required.len()); - for extension in required { - let name = extension_name(*extension)?; - required_instance_extensions.push(name); - } - required_instance_extensions.sort(); - required_instance_extensions.dedup(); - Ok(VulkanSurfacePlan { - schema: 1, - required_instance_extensions, - }) -} - -/// Creates a Vulkan surface probe from native window handles. -/// -/// # Errors -/// -/// Returns [`VulkanSurfaceError`] when handles are missing, required extensions -/// cannot be planned, or `vkCreate*SurfaceKHR` fails. -pub fn create_vulkan_surface_probe( - instance: &VulkanInstanceProbe, - handles: Option, -) -> Result { - let handles = handles.ok_or(VulkanSurfaceError::MissingNativeHandles)?; - let report = plan_vulkan_surface(Some(handles))?; - // SAFETY: The platform handles are only used to create a child surface owned by this probe. - let surface = unsafe { - ash_window::create_surface( - &instance.entry, - &instance.instance, - handles.display, - handles.window, - None, - ) - } - .map_err(|error| VulkanSurfaceError::CreateFailed { result: error })?; - Ok(VulkanSurfaceProbe { - loader: surface::Instance::new(&instance.entry, &instance.instance), - surface, - report, - }) -} - /// Probes live Vulkan device, queue, surface and swapchain capabilities. /// /// # Errors @@ -2841,32 +2720,6 @@ fn live_surface_capabilities( }) } -/// Renders a deterministic JSON Vulkan surface plan. -#[must_use] -pub fn render_surface_plan_json(plan: &VulkanSurfacePlan) -> String { - #[derive(Serialize)] - struct SurfacePlanJson<'a> { - schema: u32, - required_instance_extensions: &'a [String], - } - - serialize_json_or_fallback( - &SurfacePlanJson { - schema: plan.schema, - required_instance_extensions: &plan.required_instance_extensions, - }, - "{\"schema\":0,\"required_instance_extensions\":[]}", - ) -} - -fn extension_name(extension: *const c_char) -> Result { - // SAFETY: `ash-window` returns extension pointers to static NUL-terminated Vulkan names. - let name = unsafe { CStr::from_ptr(extension) }; - name.to_str() - .map(str::to_string) - .map_err(|_| VulkanSurfaceError::InvalidExtensionName) -} - #[cfg(test)] mod tests { use super::*; diff --git a/adapters/fparkan-render-vulkan/src/ffi/surface.rs b/adapters/fparkan-render-vulkan/src/ffi/surface.rs new file mode 100644 index 0000000..8fc9c5a --- /dev/null +++ b/adapters/fparkan-render-vulkan/src/ffi/surface.rs @@ -0,0 +1,159 @@ +#![allow(unsafe_code)] + +use ash::{khr::surface, vk}; +use fparkan_platform::NativeWindowHandles; +use serde::Serialize; +use std::ffi::CStr; +use std::os::raw::c_char; + +use super::VulkanInstanceProbe; +use crate::policy::serialize_json_or_fallback; + +/// Deterministic Vulkan surface creation plan. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct VulkanSurfacePlan { + /// Report schema version. + pub schema: u32, + /// Instance extensions required by the native display backend. + pub required_instance_extensions: Vec, +} + +/// Vulkan surface bootstrap error. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum VulkanSurfaceError { + /// No native raw window/display handles were available. + MissingNativeHandles, + /// Required platform surface extensions could not be enumerated. + RequiredExtensionsFailed { + /// Vulkan result. + result: vk::Result, + }, + /// A required extension pointer was not valid UTF-8. + InvalidExtensionName, + /// Surface creation failed. + CreateFailed { + /// Vulkan result. + result: vk::Result, + }, +} + +impl std::fmt::Display for VulkanSurfaceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MissingNativeHandles => { + write!( + f, + "native window/display handles are required for Vulkan surface creation" + ) + } + Self::RequiredExtensionsFailed { result } => write!( + f, + "failed to enumerate required Vulkan surface extensions: {result:?}" + ), + Self::InvalidExtensionName => { + write!(f, "Vulkan surface extension name is not valid UTF-8") + } + Self::CreateFailed { result } => { + write!(f, "Vulkan surface creation failed: {result:?}") + } + } + } +} + +impl std::error::Error for VulkanSurfaceError {} + +/// Created Vulkan surface probe. +pub struct VulkanSurfaceProbe { + pub(super) loader: surface::Instance, + pub(super) surface: vk::SurfaceKHR, + /// Deterministic surface creation report. + pub report: VulkanSurfacePlan, +} + +impl Drop for VulkanSurfaceProbe { + fn drop(&mut self) { + // SAFETY: The `SurfaceKHR` was created by this probe and is destroyed once during drop. + unsafe { self.loader.destroy_surface(self.surface, None) }; + } +} + +/// Builds a deterministic Vulkan surface plan from native window handles. +/// +/// # Errors +/// +/// Returns [`VulkanSurfaceError`] when no native handles exist or the platform +/// display backend has no Vulkan surface extension mapping. +pub fn plan_vulkan_surface( + handles: Option, +) -> Result { + let handles = handles.ok_or(VulkanSurfaceError::MissingNativeHandles)?; + let required = ash_window::enumerate_required_extensions(handles.display) + .map_err(|error| VulkanSurfaceError::RequiredExtensionsFailed { result: error })?; + let mut required_instance_extensions = Vec::with_capacity(required.len()); + for extension in required { + let name = extension_name(*extension)?; + required_instance_extensions.push(name); + } + required_instance_extensions.sort(); + required_instance_extensions.dedup(); + Ok(VulkanSurfacePlan { + schema: 1, + required_instance_extensions, + }) +} + +/// Creates a Vulkan surface probe from native window handles. +/// +/// # Errors +/// +/// Returns [`VulkanSurfaceError`] when handles are missing, required extensions +/// cannot be planned, or `vkCreate*SurfaceKHR` fails. +pub fn create_vulkan_surface_probe( + instance: &VulkanInstanceProbe, + handles: Option, +) -> Result { + let handles = handles.ok_or(VulkanSurfaceError::MissingNativeHandles)?; + let report = plan_vulkan_surface(Some(handles))?; + // SAFETY: The platform handles are only used to create a child surface owned by this probe. + let surface = unsafe { + ash_window::create_surface( + &instance.entry, + &instance.instance, + handles.display, + handles.window, + None, + ) + } + .map_err(|error| VulkanSurfaceError::CreateFailed { result: error })?; + Ok(VulkanSurfaceProbe { + loader: surface::Instance::new(&instance.entry, &instance.instance), + surface, + report, + }) +} + +/// Renders a deterministic JSON Vulkan surface plan. +#[must_use] +pub fn render_surface_plan_json(plan: &VulkanSurfacePlan) -> String { + #[derive(Serialize)] + struct SurfacePlanJson<'a> { + schema: u32, + required_instance_extensions: &'a [String], + } + + serialize_json_or_fallback( + &SurfacePlanJson { + schema: plan.schema, + required_instance_extensions: &plan.required_instance_extensions, + }, + "{\"schema\":0,\"required_instance_extensions\":[]}", + ) +} + +pub(super) fn extension_name(extension: *const c_char) -> Result { + // SAFETY: `ash-window` returns extension pointers to static NUL-terminated Vulkan names. + let name = unsafe { CStr::from_ptr(extension) }; + name.to_str() + .map(str::to_string) + .map_err(|_| VulkanSurfaceError::InvalidExtensionName) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 95866b4..bf79ec7 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1242,6 +1242,7 @@ fn has_safety_comment(line: &str) -> bool { const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &[ "adapters/fparkan-render-vulkan/src/ffi.rs", "adapters/fparkan-render-vulkan/src/ffi/instance.rs", + "adapters/fparkan-render-vulkan/src/ffi/surface.rs", ]; fn is_audited_unsafe_source(path: &Path) -> bool { -- cgit v1.2.3