feat: probe Vulkan loader boundary
This commit is contained in:
@@ -11,5 +11,21 @@ ash-window = "0.13"
|
|||||||
fparkan-platform = { path = "../../crates/fparkan-platform" }
|
fparkan-platform = { path = "../../crates/fparkan-platform" }
|
||||||
fparkan-render = { path = "../../crates/fparkan-render" }
|
fparkan-render = { path = "../../crates/fparkan-render" }
|
||||||
|
|
||||||
[lints]
|
[lints.rust]
|
||||||
workspace = true
|
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"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
test,
|
test,
|
||||||
allow(
|
allow(
|
||||||
@@ -32,6 +32,7 @@ use fparkan_platform::RenderRequest;
|
|||||||
use fparkan_render::{
|
use fparkan_render::{
|
||||||
canonical_capture, FrameOutput, RenderBackend, RenderCommandList, RenderError,
|
canonical_capture, FrameOutput, RenderBackend, RenderCommandList, RenderError,
|
||||||
};
|
};
|
||||||
|
use std::ffi::CStr;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
/// Minimum Vulkan API version accepted by the Stage 0 backend.
|
/// 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_SWAPCHAIN_EXTENSION: &str = "VK_KHR_swapchain";
|
||||||
const KHR_PORTABILITY_SUBSET_EXTENSION: &str = "VK_KHR_portability_subset";
|
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.
|
/// Vulkan backend migration readiness.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum VulkanBackendState {
|
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(
|
fn device(
|
||||||
name: &str,
|
name: &str,
|
||||||
device_type: VulkanDeviceType,
|
device_type: VulkanDeviceType,
|
||||||
|
|||||||
@@ -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-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-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-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-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
|
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
|
L1-P1-NRES-001 covered cargo test -p fparkan-nres --offline licensed_corpora_nres_roundtrip_gates
|
||||||
|
|||||||
|
@@ -26,6 +26,8 @@
|
|||||||
`S0-VK-003`
|
`S0-VK-003`
|
||||||
`S0-VK-004`
|
`S0-VK-004`
|
||||||
`S0-VK-005`
|
`S0-VK-005`
|
||||||
|
`S0-VK-006`
|
||||||
|
`S0-VK-007`
|
||||||
`S0-LIMIT-001`
|
`S0-LIMIT-001`
|
||||||
`S0-LIMIT-002`
|
`S0-LIMIT-002`
|
||||||
`L1-P1-NRES-001`
|
`L1-P1-NRES-001`
|
||||||
|
|||||||
Reference in New Issue
Block a user