aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-06-23 21:43:54 +0300
committerValentin Popov <valentin@popov.link>2026-06-23 21:43:54 +0300
commit8ea1fd5c188998a005d9bf48c090af4324608eff (patch)
tree418eadb7de0b2acdb8cf4c2a0da1cadfa40b1734
parent69c032accab677b53f3dff61f3afb870c2e8e0a8 (diff)
downloadfparkan-8ea1fd5c188998a005d9bf48c090af4324608eff.tar.xz
fparkan-8ea1fd5c188998a005d9bf48c090af4324608eff.zip
feat: probe Vulkan loader boundary
-rw-r--r--adapters/fparkan-render-vulkan/Cargo.toml20
-rw-r--r--adapters/fparkan-render-vulkan/src/lib.rs111
-rw-r--r--fixtures/acceptance/coverage.tsv2
-rw-r--r--fixtures/acceptance/stage_0_2_roadmap.md2
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`