aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-06-25 05:12:20 +0300
committerValentin Popov <valentin@popov.link>2026-06-25 10:45:35 +0300
commitb6b47ae6f612d365e77bbaf421b7c0927df12835 (patch)
tree7cd4e5e49da95492c77bb6d05dc704276da567d3
parent1eead8d597e7edf363988f2b2c88c331578ac9f5 (diff)
downloadfparkan-b6b47ae6f612d365e77bbaf421b7c0927df12835.tar.xz
fparkan-b6b47ae6f612d365e77bbaf421b7c0927df12835.zip
refactor(vulkan-ffi): extract surface bootstrap module
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi.rs163
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/surface.rs159
-rw-r--r--xtask/src/main.rs1
3 files changed, 168 insertions, 155 deletions
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<String>,
-}
-
-/// 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<NativeWindowHandles>,
-) -> Result<VulkanSurfacePlan, VulkanSurfaceError> {
- 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<NativeWindowHandles>,
-) -> Result<VulkanSurfaceProbe, VulkanSurfaceError> {
- 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<String, VulkanSurfaceError> {
- // 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<String>,
+}
+
+/// 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<NativeWindowHandles>,
+) -> Result<VulkanSurfacePlan, VulkanSurfaceError> {
+ 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<NativeWindowHandles>,
+) -> Result<VulkanSurfaceProbe, VulkanSurfaceError> {
+ 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<String, VulkanSurfaceError> {
+ // 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 {