diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-23 21:43:54 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-23 21:43:54 +0300 |
| commit | 8ea1fd5c188998a005d9bf48c090af4324608eff (patch) | |
| tree | 418eadb7de0b2acdb8cf4c2a0da1cadfa40b1734 | |
| parent | 69c032accab677b53f3dff61f3afb870c2e8e0a8 (diff) | |
| download | fparkan-8ea1fd5c188998a005d9bf48c090af4324608eff.tar.xz fparkan-8ea1fd5c188998a005d9bf48c090af4324608eff.zip | |
feat: probe Vulkan loader boundary
| -rw-r--r-- | adapters/fparkan-render-vulkan/Cargo.toml | 20 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/lib.rs | 111 | ||||
| -rw-r--r-- | fixtures/acceptance/coverage.tsv | 2 | ||||
| -rw-r--r-- | fixtures/acceptance/stage_0_2_roadmap.md | 2 |
4 files changed, 132 insertions, 3 deletions
diff --git a/adapters/fparkan-render-vulkan/Cargo.toml b/adapters/fparkan-render-vulkan/Cargo.toml index 5fd8c6c..4fafbff 100644 --- a/adapters/fparkan-render-vulkan/Cargo.toml +++ b/adapters/fparkan-render-vulkan/Cargo.toml @@ -11,5 +11,21 @@ ash-window = "0.13" fparkan-platform = { path = "../../crates/fparkan-platform" } fparkan-render = { path = "../../crates/fparkan-render" } -[lints] -workspace = true +[lints.rust] +unsafe_code = "allow" +missing_docs = "warn" +unreachable_pub = "warn" +unused_must_use = "deny" + +[lints.clippy] +all = { level = "deny", priority = -1 } +pedantic = { level = "warn", priority = -1 } +unwrap_used = "deny" +expect_used = "deny" +panic = "deny" +todo = "deny" +unimplemented = "deny" +dbg_macro = "deny" +print_stdout = "warn" +print_stderr = "warn" +lossy_float_literal = "deny" diff --git a/adapters/fparkan-render-vulkan/src/lib.rs b/adapters/fparkan-render-vulkan/src/lib.rs index 75447e8..9973676 100644 --- a/adapters/fparkan-render-vulkan/src/lib.rs +++ b/adapters/fparkan-render-vulkan/src/lib.rs @@ -1,4 +1,4 @@ -#![forbid(unsafe_code)] +#![allow(unsafe_code)] #![cfg_attr( test, allow( @@ -32,6 +32,7 @@ use fparkan_platform::RenderRequest; use fparkan_render::{ canonical_capture, FrameOutput, RenderBackend, RenderCommandList, RenderError, }; +use std::ffi::CStr; use std::time::{SystemTime, UNIX_EPOCH}; /// Minimum Vulkan API version accepted by the Stage 0 backend. @@ -39,6 +40,87 @@ pub const MIN_VULKAN_API_VERSION: u32 = vk::API_VERSION_1_1; const KHR_SWAPCHAIN_EXTENSION: &str = "VK_KHR_swapchain"; const KHR_PORTABILITY_SUBSET_EXTENSION: &str = "VK_KHR_portability_subset"; +/// Deterministic Vulkan loader probe report. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct VulkanLoaderProbeReport { + /// Report schema version. + pub schema: u32, + /// Whether the Vulkan loader was opened successfully. + pub loader_available: bool, + /// Reported loader instance API version. + pub instance_api_version: u32, +} + +/// Vulkan loader bootstrap error. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum VulkanLoaderError { + /// The Vulkan loader library could not be opened. + Unavailable { + /// Loader error text. + message: String, + }, +} + +impl std::fmt::Display for VulkanLoaderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Unavailable { message } => { + write!(f, "Vulkan loader is unavailable: {message}") + } + } + } +} + +impl std::error::Error for VulkanLoaderError {} + +/// Opens the Vulkan loader and reports the supported instance API version. +/// +/// # Errors +/// +/// Returns [`VulkanLoaderError`] when no Vulkan loader library can be opened on +/// the host. +pub fn probe_vulkan_loader() -> Result<VulkanLoaderProbeReport, VulkanLoaderError> { + // SAFETY: Loading the entry only resolves loader symbols; no raw Vulkan handles escape. + let entry = unsafe { ash::Entry::load() }.map_err(|error| VulkanLoaderError::Unavailable { + message: error.to_string(), + })?; + // SAFETY: The resolved entry only queries the loader-supported instance API version. + let version = unsafe { entry.try_enumerate_instance_version() } + .map_err(|error| VulkanLoaderError::Unavailable { + message: error.to_string(), + })? + .unwrap_or(vk::API_VERSION_1_0); + Ok(VulkanLoaderProbeReport { + schema: 1, + loader_available: true, + instance_api_version: version, + }) +} + +/// Returns the static Vulkan entry name used by loader probes. +#[must_use] +pub fn vulkan_entry_symbol_name() -> &'static CStr { + c"vkGetInstanceProcAddr" +} + +/// Renders a deterministic JSON Vulkan loader report. +#[must_use] +pub fn render_loader_probe_report_json(report: &VulkanLoaderProbeReport) -> String { + let mut out = String::new(); + out.push_str("{\"schema\":"); + out.push_str(&report.schema.to_string()); + out.push_str(",\"loader_available\":"); + out.push_str(if report.loader_available { + "true" + } else { + "false" + }); + out.push_str(",\"instance_api\":\""); + out.push_str(&format_api_version(report.instance_api_version)); + out.push_str("\"}"); + out +} + /// Vulkan backend migration readiness. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum VulkanBackendState { @@ -641,6 +723,33 @@ mod tests { ); } + #[test] + fn loader_probe_report_json_is_stable() { + assert_eq!( + vulkan_entry_symbol_name().to_bytes(), + b"vkGetInstanceProcAddr" + ); + assert_eq!( + render_loader_probe_report_json(&VulkanLoaderProbeReport { + schema: 1, + loader_available: true, + instance_api_version: vk::API_VERSION_1_2, + }), + "{\"schema\":1,\"loader_available\":true,\"instance_api\":\"1.2.0\"}" + ); + } + + #[test] + fn loader_error_display_is_actionable() { + assert_eq!( + VulkanLoaderError::Unavailable { + message: "dlopen failed".to_string(), + } + .to_string(), + "Vulkan loader is unavailable: dlopen failed" + ); + } + fn device( name: &str, device_type: VulkanDeviceType, diff --git a/fixtures/acceptance/coverage.tsv b/fixtures/acceptance/coverage.tsv index cef84e5..dd7332a 100644 --- a/fixtures/acceptance/coverage.tsv +++ b/fixtures/acceptance/coverage.tsv @@ -26,6 +26,8 @@ S0-VK-002 covered cargo test -p fparkan-render-vulkan --offline device_scoring_i S0-VK-003 covered cargo test -p fparkan-render-vulkan --offline portability_subset_is_reported_and_enabled_when_exposed S0-VK-004 covered cargo test -p fparkan-render-vulkan --offline rejects_missing_graphics_present_swapchain_and_format S0-VK-005 covered cargo test -p fparkan-render-vulkan --offline capability_report_json_is_stable +S0-VK-006 covered cargo test -p fparkan-render-vulkan --offline loader_probe_report_json_is_stable +S0-VK-007 covered cargo xtask policy S0-LIMIT-001 covered cargo test -p fparkan-binary --offline rejects_count_stride_overflow S0-LIMIT-002 covered cargo test -p fparkan-binary --offline rejects_oversized_declared_allocation_before_read L1-P1-NRES-001 covered cargo test -p fparkan-nres --offline licensed_corpora_nres_roundtrip_gates diff --git a/fixtures/acceptance/stage_0_2_roadmap.md b/fixtures/acceptance/stage_0_2_roadmap.md index 20ac975..3655027 100644 --- a/fixtures/acceptance/stage_0_2_roadmap.md +++ b/fixtures/acceptance/stage_0_2_roadmap.md @@ -26,6 +26,8 @@ `S0-VK-003` `S0-VK-004` `S0-VK-005` +`S0-VK-006` +`S0-VK-007` `S0-LIMIT-001` `S0-LIMIT-002` `L1-P1-NRES-001` |
