aboutsummaryrefslogtreecommitdiff
path: root/adapters
diff options
context:
space:
mode:
Diffstat (limited to 'adapters')
-rw-r--r--adapters/fparkan-platform-winit/Cargo.toml5
-rw-r--r--adapters/fparkan-platform-winit/src/lib.rs19
-rw-r--r--adapters/fparkan-render-vulkan/Cargo.toml34
-rw-r--r--adapters/fparkan-render-vulkan/build.rs92
-rw-r--r--adapters/fparkan-render-vulkan/shaders/manifest.json1
-rw-r--r--adapters/fparkan-render-vulkan/src/asset_mesh.rs1
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi.rs28
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/instance.rs44
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/resources.rs3
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/smoke.rs12
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs12
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/surface.rs20
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs6
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/tests.rs280
-rw-r--r--adapters/fparkan-render-vulkan/src/lib.rs6
-rw-r--r--adapters/fparkan-render-vulkan/src/planning_backend.rs168
-rw-r--r--adapters/fparkan-render-vulkan/src/policy.rs164
-rw-r--r--adapters/fparkan-render-vulkan/src/shader.rs82
-rw-r--r--adapters/fparkan-render-vulkan/src/shader_manifest.rs399
19 files changed, 126 insertions, 1250 deletions
diff --git a/adapters/fparkan-platform-winit/Cargo.toml b/adapters/fparkan-platform-winit/Cargo.toml
index 10f89cb..3a6e591 100644
--- a/adapters/fparkan-platform-winit/Cargo.toml
+++ b/adapters/fparkan-platform-winit/Cargo.toml
@@ -6,9 +6,6 @@ license.workspace = true
repository.workspace = true
[dependencies]
-fparkan-platform = { path = "../../crates/fparkan-platform", version = "0.1.0" }
+fparkan-platform = { path = "../../crates/fparkan-platform" }
raw-window-handle = "0.6"
winit = { version = "0.30", default-features = false, features = ["rwh_06"] }
-
-[lints]
-workspace = true
diff --git a/adapters/fparkan-platform-winit/src/lib.rs b/adapters/fparkan-platform-winit/src/lib.rs
index 19de207..bb06eca 100644
--- a/adapters/fparkan-platform-winit/src/lib.rs
+++ b/adapters/fparkan-platform-winit/src/lib.rs
@@ -1,23 +1,4 @@
#![forbid(unsafe_code)]
-#![cfg_attr(
- test,
- allow(
- clippy::cast_possible_truncation,
- clippy::cast_possible_wrap,
- clippy::cast_precision_loss,
- clippy::expect_used,
- clippy::float_cmp,
- clippy::identity_op,
- clippy::too_many_lines,
- clippy::uninlined_format_args,
- clippy::map_unwrap_or,
- clippy::needless_raw_string_hashes,
- clippy::semicolon_if_nothing_returned,
- clippy::type_complexity,
- clippy::panic,
- clippy::unwrap_used
- )
-)]
//! Minimal `winit`-backed platform adapter shim.
use fparkan_platform::{
diff --git a/adapters/fparkan-render-vulkan/Cargo.toml b/adapters/fparkan-render-vulkan/Cargo.toml
index abd0928..eeaa46b 100644
--- a/adapters/fparkan-render-vulkan/Cargo.toml
+++ b/adapters/fparkan-render-vulkan/Cargo.toml
@@ -4,35 +4,13 @@ version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
-build = "build.rs"
[dependencies]
ash = "0.38"
ash-window = "0.13"
-fparkan-animation = { path = "../../crates/fparkan-animation", version = "0.1.0" }
-fparkan-binary = { path = "../../crates/fparkan-binary", version = "0.1.0" }
-fparkan-msh = { path = "../../crates/fparkan-msh", version = "0.1.0" }
-fparkan-platform = { path = "../../crates/fparkan-platform", version = "0.1.0" }
-fparkan-render = { path = "../../crates/fparkan-render", version = "0.1.0" }
-fparkan-terrain-format = { path = "../../crates/fparkan-terrain-format", version = "0.1.0" }
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-
-[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"
+fparkan-animation = { path = "../../crates/fparkan-animation" }
+fparkan-binary = { path = "../../crates/fparkan-binary" }
+fparkan-msh = { path = "../../crates/fparkan-msh" }
+fparkan-platform = { path = "../../crates/fparkan-platform" }
+fparkan-render = { path = "../../crates/fparkan-render" }
+fparkan-terrain-format = { path = "../../crates/fparkan-terrain-format" }
diff --git a/adapters/fparkan-render-vulkan/build.rs b/adapters/fparkan-render-vulkan/build.rs
deleted file mode 100644
index d66c022..0000000
--- a/adapters/fparkan-render-vulkan/build.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-//! Build-time shader tool metadata for Vulkan reports.
-
-use std::env;
-use std::path::Path;
-use std::process::Command;
-
-const SHADER_COMPILER_NAME: &str = "glslangValidator";
-const SPIRV_VALIDATOR_NAME: &str = "spirv-val";
-
-fn main() {
- println!("cargo:rerun-if-env-changed=PATH");
- println!("cargo:rerun-if-env-changed=FPARKAN_GLSLANG_VALIDATOR");
- println!("cargo:rerun-if-env-changed=FPARKAN_SPIRV_VAL");
-
- emit_tool_metadata(
- "FPARKAN_BUILD_SHADER_COMPILER",
- &tool_path("FPARKAN_GLSLANG_VALIDATOR", SHADER_COMPILER_NAME),
- );
- emit_tool_metadata(
- "FPARKAN_BUILD_SPIRV_VALIDATOR",
- &tool_path("FPARKAN_SPIRV_VAL", SPIRV_VALIDATOR_NAME),
- );
-}
-
-fn tool_path(env_var: &str, fallback: &str) -> String {
- env::var(env_var).unwrap_or_else(|_| fallback.to_string())
-}
-
-fn emit_tool_metadata(prefix: &str, tool: &str) {
- let Some(path) = resolve_tool(tool) else {
- return;
- };
- println!("cargo:rerun-if-changed={}", path.display());
- let Some(version) = tool_version(&path) else {
- return;
- };
- let Some(binary_sha256) = tool_sha256(&path) else {
- return;
- };
- let name = path
- .file_name()
- .and_then(|value| value.to_str())
- .unwrap_or(tool)
- .to_string();
- println!("cargo:rustc-env={prefix}_NAME={name}");
- println!("cargo:rustc-env={prefix}_VERSION={version}");
- println!("cargo:rustc-env={prefix}_SHA256={binary_sha256}");
-}
-
-fn resolve_tool(tool: &str) -> Option<std::path::PathBuf> {
- let candidate = Path::new(tool);
- if candidate.components().count() > 1 {
- return candidate.is_file().then(|| candidate.to_path_buf());
- }
- let output = Command::new("which").arg(tool).output().ok()?;
- if !output.status.success() {
- return None;
- }
- let path = String::from_utf8(output.stdout).ok()?;
- let path = path.trim();
- (!path.is_empty()).then(|| path.into())
-}
-
-fn tool_version(path: &Path) -> Option<String> {
- let output = Command::new(path).arg("--version").output().ok()?;
- if !output.status.success() {
- return None;
- }
- let stdout = String::from_utf8(output.stdout).ok()?;
- stdout
- .lines()
- .find(|line| !line.trim().is_empty())
- .map(str::trim)
- .map(|line| {
- line.strip_prefix("Glslang Version: ")
- .unwrap_or(line)
- .to_string()
- })
-}
-
-fn tool_sha256(path: &Path) -> Option<String> {
- let output = Command::new("shasum")
- .args(["-a", "256"])
- .arg(path)
- .output()
- .ok()?;
- if !output.status.success() {
- return None;
- }
- let stdout = String::from_utf8(output.stdout).ok()?;
- stdout.split_whitespace().next().map(ToString::to_string)
-}
diff --git a/adapters/fparkan-render-vulkan/shaders/manifest.json b/adapters/fparkan-render-vulkan/shaders/manifest.json
deleted file mode 100644
index 89df3ce..0000000
--- a/adapters/fparkan-render-vulkan/shaders/manifest.json
+++ /dev/null
@@ -1 +0,0 @@
-{"schema":2,"target_env":"vulkan1.1","compiler":{"name":"glslangValidator","version":"11:16.3.0","binary_sha256":"9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f"},"validator":{"name":"spirv-val","version":"SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00","binary_sha256":"f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f"},"modules":[{"name":"triangle.vert","stage":"vertex","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert","source_sha256":"fe3721202477220d2d9677b6572d7f3baecb374e94f04eea9d7286ed5e98b6c9","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","word_count":358,"sha256":"4e2051ac43b933a57e5557e596bcc95952b4efbfcb45315180f89581e094398d","descriptor_sets":0,"push_constant_bytes":64,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","interface_hash":"f76cc92b9aaa2261eda33bd320503e1be100a2dd7e1a9fcfe13351678dda2a06"},{"name":"triangle.frag","stage":"fragment","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag","source_sha256":"fb3ed435af48e4fb4a817f160b467f7a561c7c547c87b6f0c1e89f5f58af7329","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","word_count":296,"sha256":"8ab7bf835e166892b04b10fa1100258daf355d2f693ad311d014dbee22de5c7b","descriptor_sets":1,"push_constant_bytes":68,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","interface_hash":"91552fce09a408a2baf8a608bc642b6632372d3d2316217051384159ba14cee2"}],"manifest_hash":"cf471972978ddb5c8919711ad64c93d0d64e18b1f590a89ce014f1e2a743cfb5"}
diff --git a/adapters/fparkan-render-vulkan/src/asset_mesh.rs b/adapters/fparkan-render-vulkan/src/asset_mesh.rs
index 79a42a4..236361b 100644
--- a/adapters/fparkan-render-vulkan/src/asset_mesh.rs
+++ b/adapters/fparkan-render-vulkan/src/asset_mesh.rs
@@ -439,7 +439,6 @@ fn project_msh_to_static_mesh_in_world_space_with_node_poses(
})
}
-#[allow(clippy::too_many_arguments)]
fn append_node_fallback_batch(
model: &ModelAsset,
batch: &fparkan_msh::Batch,
diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs
index 79ca919..640551a 100644
--- a/adapters/fparkan-render-vulkan/src/ffi.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi.rs
@@ -1,22 +1,3 @@
-#![cfg_attr(
- test,
- allow(
- clippy::cast_possible_truncation,
- clippy::cast_possible_wrap,
- clippy::cast_precision_loss,
- clippy::expect_used,
- clippy::float_cmp,
- clippy::identity_op,
- clippy::too_many_lines,
- clippy::uninlined_format_args,
- clippy::map_unwrap_or,
- clippy::needless_raw_string_hashes,
- clippy::semicolon_if_nothing_returned,
- clippy::type_complexity,
- clippy::panic,
- clippy::unwrap_used
- )
-)]
#![deny(unsafe_op_in_unsafe_fn)]
//! Vulkan adapter facade and migration-ready backend surface contract.
//!
@@ -57,9 +38,8 @@ pub use self::capabilities::{
};
pub use self::instance::{
create_vulkan_instance_probe, plan_vulkan_instance, probe_vulkan_loader,
- render_instance_plan_json, render_loader_probe_report_json, vulkan_entry_symbol_name,
- VulkanInstanceConfig, VulkanInstanceError, VulkanInstancePlan, VulkanInstanceProbe,
- VulkanLoaderError, VulkanLoaderProbeReport,
+ vulkan_entry_symbol_name, VulkanInstanceConfig, VulkanInstanceError, VulkanInstancePlan,
+ VulkanInstanceProbe, VulkanLoaderError, VulkanLoaderProbeReport,
};
#[cfg(test)]
use self::instance::{cstring_vec, ensure_instance_extensions_available};
@@ -84,8 +64,8 @@ pub use self::smoke_types::{
#[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,
+ create_vulkan_surface_probe, plan_vulkan_surface, VulkanSurfaceError, VulkanSurfacePlan,
+ VulkanSurfaceProbe,
};
pub use self::swapchain::{
create_vulkan_swapchain_probe, create_vulkan_swapchain_probe_for_extent, VulkanSwapchainProbe,
diff --git a/adapters/fparkan-render-vulkan/src/ffi/instance.rs b/adapters/fparkan-render-vulkan/src/ffi/instance.rs
index 42b9bdb..463fa03 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/instance.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/instance.rs
@@ -1,7 +1,6 @@
#![allow(unsafe_code)]
use ash::vk;
-use serde::Serialize;
use std::collections::BTreeSet;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
@@ -10,7 +9,6 @@ use super::{
EXT_DEBUG_UTILS_EXTENSION, KHR_PORTABILITY_ENUMERATION_EXTENSION, MIN_VULKAN_API_VERSION,
VALIDATION_LAYER_NAME,
};
-use crate::policy::{format_api_version, serialize_json_or_fallback};
/// Vulkan instance bootstrap configuration.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -236,28 +234,6 @@ pub fn create_vulkan_instance_probe(
})
}
-/// Renders a deterministic JSON Vulkan instance plan.
-#[must_use]
-pub fn render_instance_plan_json(plan: &VulkanInstancePlan) -> String {
- #[derive(Serialize)]
- struct InstancePlanJson<'a> {
- schema: u32,
- create_flags: u32,
- validation_requested: bool,
- enabled_extensions: &'a [String],
- }
-
- serialize_json_or_fallback(
- &InstancePlanJson {
- schema: plan.schema,
- create_flags: plan.create_flags,
- validation_requested: plan.validation_requested,
- enabled_extensions: &plan.enabled_extensions,
- },
- "{\"schema\":0,\"create_flags\":0,\"validation_requested\":false,\"enabled_extensions\":[]}",
- )
-}
-
/// Opens the Vulkan loader and reports the supported instance API version.
///
/// # Errors
@@ -288,26 +264,6 @@ 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 {
- #[derive(Serialize)]
- struct LoaderProbeReportJson {
- schema: u32,
- loader_available: bool,
- instance_api: String,
- }
-
- serialize_json_or_fallback(
- &LoaderProbeReportJson {
- schema: report.schema,
- loader_available: report.loader_available,
- instance_api: format_api_version(report.instance_api_version),
- },
- "{\"schema\":0,\"loader_available\":false,\"instance_api\":\"0.0.0\"}",
- )
-}
-
fn available_instance_extensions(entry: &ash::Entry) -> Result<Vec<String>, VulkanInstanceError> {
let available_extensions =
// SAFETY: Enumerating instance extensions reads loader-owned immutable metadata.
diff --git a/adapters/fparkan-render-vulkan/src/ffi/resources.rs b/adapters/fparkan-render-vulkan/src/ffi/resources.rs
index 1d38503..f1fd579 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/resources.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/resources.rs
@@ -26,7 +26,6 @@ pub(super) struct VulkanDepthAttachment {
pub(super) format: vk::Format,
}
-#[allow(clippy::too_many_lines)]
pub(super) fn create_depth_attachment(
instance: &VulkanInstanceProbe,
device: &VulkanLogicalDeviceProbe,
@@ -249,7 +248,6 @@ pub(super) fn create_static_mesh_index_buffer(
)
}
-#[allow(clippy::too_many_lines)]
pub(super) fn create_static_texture_image(
instance: &VulkanInstanceProbe,
device: &VulkanLogicalDeviceProbe,
@@ -376,7 +374,6 @@ pub(super) fn create_static_texture_image(
})
}
-#[allow(clippy::too_many_lines)]
fn upload_static_texture(
device: &VulkanLogicalDeviceProbe,
command_pool: vk::CommandPool,
diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
index e41f0f9..e2f6c34 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
@@ -17,7 +17,7 @@ use super::{
VulkanValidationReport,
};
use crate::policy::KHR_PORTABILITY_SUBSET_EXTENSION;
-use crate::shader_manifest::{triangle_shader_manifest, validate_shader_manifest};
+use crate::shader::validate_triangle_shaders;
#[cfg(test)]
fn take_runtime_owners_in_dependency_order<Instance, Validation, Surface, Device, Swapchain>(
@@ -103,7 +103,7 @@ impl VulkanSmokeRenderer {
///
/// Returns [`VulkanSmokeRendererError`] when Vulkan bootstrap, pipeline creation,
/// memory allocation, or synchronization resource creation fails.
- #[allow(clippy::too_many_lines)]
+
pub fn new(
create_info: &VulkanSmokeRendererCreateInfo,
) -> Result<Self, VulkanSmokeRendererError> {
@@ -120,8 +120,7 @@ impl VulkanSmokeRenderer {
resolve_draw_texture_indices(&create_info.mesh.draw_ranges, &create_info.materials)
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
let bootstrap_progress = create_info.bootstrap_progress.as_ref();
- let shader_manifest = validate_shader_manifest(&triangle_shader_manifest())
- .map_err(VulkanSmokeRendererError::ShaderManifest)?;
+ validate_triangle_shaders().map_err(VulkanSmokeRendererError::Shader)?;
let surface_plan = plan_vulkan_surface(Some(create_info.native_handles))
.map_err(VulkanSmokeRendererError::Surface)?;
let mut instance_config = VulkanInstanceConfig::smoke(&create_info.application_name);
@@ -240,7 +239,6 @@ impl VulkanSmokeRenderer {
pending_extent: None,
swapchain_recreate_count: 0,
report: VulkanSmokeRendererReport {
- shader_manifest_hash: shader_manifest.manifest_hash.clone(),
portability_enumeration: instance_config.enable_portability_enumeration,
portability_subset_enabled: false,
device_name: String::new(),
@@ -260,7 +258,6 @@ impl VulkanSmokeRenderer {
let device_ref = renderer.device_ref()?;
let swapchain_ref = renderer.swapchain_ref()?;
renderer.report = VulkanSmokeRendererReport {
- shader_manifest_hash: shader_manifest.manifest_hash,
portability_enumeration: renderer
.instance
.as_ref()
@@ -426,7 +423,7 @@ impl VulkanSmokeRenderer {
///
/// Returns [`VulkanSmokeRendererError`] when synchronization, command recording,
/// submission, or presentation fails.
- #[allow(clippy::too_many_lines)]
+
pub fn draw_frame(&mut self) -> Result<VulkanSmokeFrameOutcome, VulkanSmokeRendererError> {
if let Some(extent) = self.pending_extent.take() {
if extent.0 == 0 || extent.1 == 0 {
@@ -632,7 +629,6 @@ impl VulkanSmokeRenderer {
Ok(())
}
- #[allow(clippy::too_many_lines)]
fn record_command_buffer(
&mut self,
image_index: usize,
diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs
index 4686eaf..217df3f 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs
@@ -10,7 +10,7 @@ use super::{
VulkanSurfaceProbe, VulkanSwapchainProbe, VulkanSwapchainProbeError, VulkanSwapchainResources,
VulkanValidationMessenger,
};
-use crate::shader_manifest::VulkanShaderManifestError;
+use crate::shader::VulkanShaderError;
/// Creates a live native Vulkan renderer for the Stage 0 smoke loop.
#[derive(Clone, Debug)]
@@ -536,7 +536,7 @@ impl VulkanSmokeBootstrapProgress {
/// Stable snapshot of measured bootstrap progress.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-#[allow(clippy::struct_excessive_bools)]
+
pub struct VulkanSmokeBootstrapSnapshot {
/// Whether the Vulkan loader was resolved.
pub loader_available: bool,
@@ -562,8 +562,6 @@ const BOOTSTRAP_SWAPCHAIN_CREATED: u8 = 1 << 5;
/// Stable smoke renderer bootstrap report.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VulkanSmokeRendererReport {
- /// Checked-in shader manifest hash used by the renderer.
- pub shader_manifest_hash: String,
/// Whether portability enumeration was enabled at instance creation.
pub portability_enumeration: bool,
/// Whether the logical device enabled `VK_KHR_portability_subset`.
@@ -652,8 +650,8 @@ pub enum VulkanSmokeRendererError {
LogicalDevice(VulkanLogicalDeviceError),
/// Swapchain bootstrap failed.
Swapchain(VulkanSwapchainProbeError),
- /// Shader manifest validation failed.
- ShaderManifest(VulkanShaderManifestError),
+ /// Checked-in shader validation failed.
+ Shader(VulkanShaderError),
/// Vulkan operation failed.
VulkanOperation {
/// Operation context.
@@ -695,7 +693,7 @@ impl std::fmt::Display for VulkanSmokeRendererError {
Self::Surface(error) => write!(f, "{error}"),
Self::LogicalDevice(error) => write!(f, "{error}"),
Self::Swapchain(error) => write!(f, "{error}"),
- Self::ShaderManifest(error) => write!(f, "{error}"),
+ Self::Shader(error) => write!(f, "{error}"),
Self::VulkanOperation { context, result } => {
write!(f, "{context}: {result:?}")
}
diff --git a/adapters/fparkan-render-vulkan/src/ffi/surface.rs b/adapters/fparkan-render-vulkan/src/ffi/surface.rs
index 8fc9c5a..65a8b7f 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/surface.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/surface.rs
@@ -2,12 +2,10 @@
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)]
@@ -132,24 +130,6 @@ pub fn create_vulkan_surface_probe(
})
}
-/// 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) };
diff --git a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs
index 7558db6..c175f0d 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs
@@ -45,7 +45,6 @@ struct PartialSwapchainResources {
command_buffers: Vec<vk::CommandBuffer>,
}
-#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub(super) fn create_swapchain_resources(
instance: &VulkanInstanceProbe,
device: &VulkanLogicalDeviceProbe,
@@ -205,7 +204,6 @@ fn create_swapchain_image_views(
Ok(image_views)
}
-#[allow(clippy::type_complexity)]
fn create_swapchain_pipeline_bundle(
instance: &VulkanInstanceProbe,
device: &VulkanLogicalDeviceProbe,
@@ -433,7 +431,6 @@ fn create_pipeline_layout(
})
}
-#[allow(clippy::too_many_lines)]
fn create_texture_descriptor_bundle(
device: &VulkanLogicalDeviceProbe,
textures: &[VulkanAllocatedImage],
@@ -554,12 +551,10 @@ fn create_texture_descriptor_bundle(
Ok((layout, pool, descriptor_sets, sampler))
}
-#[allow(clippy::cast_precision_loss)]
fn extent_component_to_f32(value: u32) -> f32 {
value as f32
}
-#[allow(clippy::too_many_lines)]
fn create_graphics_pipeline_cache(
device: &VulkanLogicalDeviceProbe,
render_pass: vk::RenderPass,
@@ -596,7 +591,6 @@ fn create_graphics_pipeline_cache(
Ok(pipelines)
}
-#[allow(clippy::too_many_lines)]
fn create_graphics_pipeline(
device: &VulkanLogicalDeviceProbe,
render_pass: vk::RenderPass,
diff --git a/adapters/fparkan-render-vulkan/src/ffi/tests.rs b/adapters/fparkan-render-vulkan/src/ffi/tests.rs
index a842c73..740c13d 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/tests.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/tests.rs
@@ -1,114 +1,9 @@
use super::*;
+use crate::ffi::{SPIRV_MAGIC, SPIRV_VERSION_1_0};
use crate::policy::{KHR_PORTABILITY_SUBSET_EXTENSION, KHR_SWAPCHAIN_EXTENSION};
-use crate::shader_manifest::{
- SHADER_COMPILER_BINARY_SHA256, SHADER_COMPILER_NAME, SHADER_COMPILER_VERSION,
- SHADER_MANIFEST_SCHEMA, SHADER_TARGET_ENV, SPIRV_MAGIC, SPIRV_VALIDATOR_BINARY_SHA256,
- SPIRV_VALIDATOR_NAME, SPIRV_VALIDATOR_VERSION, SPIRV_VERSION_1_0,
- TRIANGLE_VERTEX_COMPILE_COMMAND, TRIANGLE_VERTEX_SOURCE_PATH, TRIANGLE_VERTEX_SOURCE_SHA256,
- TRIANGLE_VERTEX_SPIRV_PATH, TRIANGLE_VERTEX_VALIDATE_COMMAND,
-};
+use crate::shader::{validate_spirv_container, validate_triangle_shaders, VulkanShaderError};
use crate::*;
use fparkan_platform::{DepthStencilSupport, RenderRequest};
-use fparkan_render::{
- DrawCommand, DrawId, GpuMaterialId, GpuMeshId, IndexRange, LegacyPipelineState, RenderCommand,
- RenderPhase,
-};
-use fparkan_render::{RenderBackend, RenderError};
-
-#[test]
-fn planning_backend_tracks_render_request_and_simulated_present() -> Result<(), RenderError> {
- let mut backend = VulkanPlanningBackend::new();
- let request = RenderRequest {
- presentation: fparkan_platform::PresentationMode::Immediate,
- ..RenderRequest::conservative()
- };
- backend.set_render_request(request);
- assert_eq!(backend.render_request(), request);
- assert_eq!(backend.report().request.current_request, request);
- assert_eq!(backend.report().request.request_updates, 1);
-
- let commands = fparkan_render::RenderCommandList {
- commands: vec![
- RenderCommand::BeginFrame,
- RenderCommand::Draw(DrawCommand {
- id: DrawId(11),
- phase: RenderPhase::Opaque,
- object_id: None,
- mesh: GpuMeshId(1),
- material: GpuMaterialId(2),
- pipeline_key: LegacyPipelineState::default().into(),
- transform: [1.0; 16],
- range: IndexRange { start: 0, count: 3 },
- stable_order: 7,
- }),
- RenderCommand::EndFrame,
- ],
- };
-
- backend.execute(&commands)?;
- assert_eq!(backend.state(), VulkanPlanningBackendState::Configured);
- assert_eq!(backend.report().execution.planned_frames, 1);
- assert_eq!(backend.report().execution.submission_plans, 1);
- assert_eq!(backend.report().execution.simulated_presents, 1);
- assert!(backend.report().execution.last_capture_size > 0);
- assert_eq!(
- backend.report().last_frame_submission,
- Some(VulkanFrameSubmissionPlan {
- schema: 1,
- frames_in_flight: 2,
- command_buffers: 2,
- semaphores_per_frame: 2,
- fences_per_frame: 1,
- draw_count: 1,
- indexed_vertex_count: 3,
- })
- );
- Ok(())
-}
-
-#[test]
-fn frame_submission_plan_json_is_stable() -> Result<(), RenderError> {
- let commands = fparkan_render::RenderCommandList {
- commands: vec![
- RenderCommand::BeginFrame,
- RenderCommand::Draw(DrawCommand {
- id: DrawId(11),
- phase: RenderPhase::Opaque,
- object_id: None,
- mesh: GpuMeshId(1),
- material: GpuMaterialId(2),
- pipeline_key: LegacyPipelineState::default().into(),
- transform: [1.0; 16],
- range: IndexRange { start: 0, count: 3 },
- stable_order: 7,
- }),
- RenderCommand::EndFrame,
- ],
- };
- let swapchain = VulkanSwapchainPlan {
- schema: 1,
- extent: (1, 1),
- format: VulkanSurfaceFormat {
- format: vk::Format::B8G8R8A8_SRGB.as_raw(),
- color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR.as_raw(),
- },
- present_mode: vk::PresentModeKHR::FIFO.as_raw(),
- image_count: 3,
- image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw(),
- };
-
- let plan = plan_vulkan_frame_submission(&swapchain, &commands)?;
-
- assert_eq!(plan.frames_in_flight, 2);
- assert_eq!(plan.command_buffers, 3);
- assert_eq!(plan.draw_count, 1);
- assert_eq!(plan.indexed_vertex_count, 3);
- assert_eq!(
- render_frame_submission_plan_json(&plan),
- "{\"schema\":1,\"frames_in_flight\":2,\"command_buffers\":3,\"semaphores_per_frame\":2,\"fences_per_frame\":1,\"draw_count\":1,\"indexed_vertex_count\":3}"
- );
- Ok(())
-}
#[test]
fn device_scoring_is_deterministic_and_prefers_discrete_unified_queue() {
@@ -391,38 +286,6 @@ fn capability_report_preserves_informational_sampled_formats_and_limits() {
}
#[test]
-fn capability_report_json_is_stable() {
- let mut rejected = device("Rejected", VulkanDeviceType::IntegratedGpu, 0, true, false);
- rejected.present_modes.clear();
- let report = select_physical_device(&[
- rejected,
- device("GPU \"A\"", VulkanDeviceType::DiscreteGpu, 3, true, false),
- ])
- .expect("selected device");
-
- assert_eq!(
- render_capability_report_json(&report),
- "{\"schema\":1,\"vulkan_api\":\"1.1.0\",\"device_name\":\"GPU \\\"A\\\"\",\"score\":1101,\"graphics_queue_family\":3,\"present_queue_family\":3,\"portability_subset\":false,\"enabled_extensions\":[\"VK_KHR_swapchain\"],\"informational_capabilities\":{\"sampled_color_formats\":[50],\"sampled_depth_formats\":[126],\"limits\":{\"max_image_dimension_2d\":4096,\"max_sampler_allocation_count\":4096,\"max_per_stage_descriptor_samplers\":16,\"max_bound_descriptor_sets\":4}},\"rejected_devices\":[{\"device_name\":\"Rejected\",\"reason_code\":\"missing_present_mode\",\"reason\":\"Vulkan device Rejected has no supported present mode\"}]}"
- );
-}
-
-#[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 {
@@ -446,9 +309,15 @@ fn instance_plan_is_sorted_deduplicated_and_portability_aware() {
enable_validation: true,
});
+ assert_eq!(plan.create_flags, 1);
+ assert!(plan.validation_requested);
assert_eq!(
- render_instance_plan_json(&plan),
- "{\"schema\":1,\"create_flags\":1,\"validation_requested\":true,\"enabled_extensions\":[\"VK_EXT_debug_utils\",\"VK_KHR_portability_enumeration\",\"VK_KHR_surface\"]}"
+ plan.enabled_extensions,
+ vec![
+ "VK_EXT_debug_utils".to_string(),
+ KHR_PORTABILITY_ENUMERATION_EXTENSION.to_string(),
+ "VK_KHR_surface".to_string(),
+ ]
);
}
@@ -510,20 +379,6 @@ fn surface_plan_requires_native_handles() {
}
#[test]
-fn surface_plan_json_is_stable() {
- assert_eq!(
- render_surface_plan_json(&VulkanSurfacePlan {
- schema: 1,
- required_instance_extensions: vec![
- "VK_KHR_surface".to_string(),
- "VK_EXT_metal_surface".to_string(),
- ],
- }),
- "{\"schema\":1,\"required_instance_extensions\":[\"VK_KHR_surface\",\"VK_EXT_metal_surface\"]}"
- );
-}
-
-#[test]
fn static_surface_extension_name_is_decoded() {
let name = extension_name(ash::khr::surface::NAME.as_ptr()).expect("extension name");
@@ -620,128 +475,31 @@ fn swapchain_plan_rejects_missing_surface_data_and_empty_extent() {
}
#[test]
-fn swapchain_plan_json_and_recreation_reports_are_stable() {
- let plan = plan_vulkan_swapchain(&swapchain_request()).expect("swapchain plan");
- assert_eq!(
- render_swapchain_plan_json(&plan),
- "{\"schema\":1,\"extent\":[1024,720],\"format\":50,\"color_space\":0,\"present_mode\":1,\"image_count\":3,\"image_usage\":16}"
- );
-
- let report = swapchain_recreation_report(
- VulkanSwapchainRecreationReason::OutOfDate,
- (1024, 720),
- (1280, 720),
- );
- assert_eq!(
- render_swapchain_recreation_report_json(&report),
- "{\"schema\":1,\"reason\":\"out_of_date\",\"previous_extent\":[1024,720],\"next_extent\":[1280,720]}"
- );
-}
-
-#[test]
-fn triangle_shader_manifest_hashes_are_stable() {
- let report = validate_shader_manifest(&triangle_shader_manifest()).expect("shader manifest");
-
- assert_eq!(report.schema, SHADER_MANIFEST_SCHEMA);
- assert_eq!(report.target_env, SHADER_TARGET_ENV);
- assert_eq!(
- report.compiler,
- VulkanShaderToolManifest {
- name: SHADER_COMPILER_NAME,
- version: SHADER_COMPILER_VERSION,
- binary_sha256: SHADER_COMPILER_BINARY_SHA256,
- }
- );
- assert_eq!(
- report.validator,
- VulkanShaderToolManifest {
- name: SPIRV_VALIDATOR_NAME,
- version: SPIRV_VALIDATOR_VERSION,
- binary_sha256: SPIRV_VALIDATOR_BINARY_SHA256,
- }
- );
- assert_eq!(report.modules.len(), 2);
- assert_eq!(report.modules[0].name, "triangle.vert");
- assert_eq!(report.modules[0].stage, VulkanShaderStage::Vertex);
- assert_eq!(report.modules[0].source_path, TRIANGLE_VERTEX_SOURCE_PATH);
- assert_eq!(
- report.modules[0].source_sha256,
- TRIANGLE_VERTEX_SOURCE_SHA256
- );
- assert_eq!(report.modules[0].spirv_path, TRIANGLE_VERTEX_SPIRV_PATH);
- assert_eq!(report.modules[0].word_count, 358);
- assert_eq!(
- report.modules[0].sha256,
- "4e2051ac43b933a57e5557e596bcc95952b4efbfcb45315180f89581e094398d"
- );
- assert_eq!(report.modules[0].descriptor_sets, 0);
- assert_eq!(report.modules[0].push_constant_bytes, 64);
- assert_eq!(
- report.modules[0].compile_command,
- TRIANGLE_VERTEX_COMPILE_COMMAND
- );
- assert_eq!(
- report.modules[0].validate_command,
- TRIANGLE_VERTEX_VALIDATE_COMMAND
- );
- assert!(!report.modules[0].interface_hash.is_empty());
- assert_eq!(
- report.modules[1].sha256,
- "8ab7bf835e166892b04b10fa1100258daf355d2f693ad311d014dbee22de5c7b"
- );
- assert_eq!(
- report.manifest_hash,
- "cf471972978ddb5c8919711ad64c93d0d64e18b1f590a89ce014f1e2a743cfb5"
- );
-}
-
-#[test]
-fn shader_manifest_report_json_is_stable() {
- let report = validate_shader_manifest(&triangle_shader_manifest()).expect("shader manifest");
- let json = render_shader_manifest_report_json(&report);
-
- assert!(json.contains(SHADER_COMPILER_NAME));
- assert!(json.contains(SPIRV_VALIDATOR_NAME));
- assert!(json.contains(TRIANGLE_VERTEX_SOURCE_PATH));
- assert!(json.contains(TRIANGLE_VERTEX_COMPILE_COMMAND));
-}
-
-#[test]
-fn checked_in_shader_manifest_matches_generated_report() {
- let report = validate_shader_manifest(&triangle_shader_manifest()).expect("shader manifest");
- assert_eq!(
- render_shader_manifest_report_json(&report),
- include_str!("../../shaders/manifest.json").trim()
- );
+fn checked_in_shaders_have_valid_spirv_containers() {
+ assert_eq!(validate_triangle_shaders(), Ok(()));
}
#[test]
-fn shader_manifest_rejects_invalid_spirv_containers() {
- let mut module = triangle_shader_manifest().remove(0);
- module.words = &[0xFFFF_FFFF, SPIRV_VERSION_1_0, 0, 1, 0];
+fn shader_container_rejects_invalid_spirv() {
assert_eq!(
- validate_shader_manifest(&[module]),
- Err(VulkanShaderManifestError::InvalidMagic {
+ validate_spirv_container("triangle.vert", &[0xFFFF_FFFF, SPIRV_VERSION_1_0, 0, 1, 0]),
+ Err(VulkanShaderError::InvalidMagic {
name: "triangle.vert",
found: 0xFFFF_FFFF,
})
);
- let mut module = triangle_shader_manifest().remove(0);
- module.words = &[SPIRV_MAGIC, 0, 0, 1, 0];
assert_eq!(
- validate_shader_manifest(&[module]),
- Err(VulkanShaderManifestError::UnsupportedVersion {
+ validate_spirv_container("triangle.vert", &[SPIRV_MAGIC, 0, 0, 1, 0]),
+ Err(VulkanShaderError::UnsupportedVersion {
name: "triangle.vert",
found: 0,
})
);
- let mut module = triangle_shader_manifest().remove(0);
- module.words = &[SPIRV_MAGIC, SPIRV_VERSION_1_0, 0, 0, 0];
assert_eq!(
- validate_shader_manifest(&[module]),
- Err(VulkanShaderManifestError::InvalidBound {
+ validate_spirv_container("triangle.vert", &[SPIRV_MAGIC, SPIRV_VERSION_1_0, 0, 0, 0]),
+ Err(VulkanShaderError::InvalidBound {
name: "triangle.vert",
})
);
diff --git a/adapters/fparkan-render-vulkan/src/lib.rs b/adapters/fparkan-render-vulkan/src/lib.rs
index b039cd1..a5beafd 100644
--- a/adapters/fparkan-render-vulkan/src/lib.rs
+++ b/adapters/fparkan-render-vulkan/src/lib.rs
@@ -2,11 +2,9 @@
//! Vulkan adapter public surface.
mod ffi;
-mod planning_backend;
mod policy;
-mod shader_manifest;
+mod shader;
pub use ffi::*;
-pub use planning_backend::*;
pub use policy::*;
-pub use shader_manifest::*;
+pub use shader::VulkanShaderError;
diff --git a/adapters/fparkan-render-vulkan/src/planning_backend.rs b/adapters/fparkan-render-vulkan/src/planning_backend.rs
deleted file mode 100644
index 9a91712..0000000
--- a/adapters/fparkan-render-vulkan/src/planning_backend.rs
+++ /dev/null
@@ -1,168 +0,0 @@
-use ash::vk;
-use fparkan_platform::RenderRequest;
-use fparkan_render::{
- canonical_capture, FrameOutput, RenderBackend, RenderCommandList, RenderError,
-};
-
-use crate::{
- plan_vulkan_frame_submission, VulkanFrameSubmissionPlan, VulkanSurfaceFormat,
- VulkanSwapchainPlan,
-};
-
-/// Vulkan backend migration readiness.
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum VulkanPlanningBackendState {
- /// Planning facade is configured and able to accept command lists.
- #[default]
- Configured,
- /// Adapter is tracking a recoverable runtime surface/depth pipeline fault.
- Degraded,
- /// Adapter has encountered a non-recoverable error.
- Error,
-}
-
-/// Diagnostics for planning-facade request tracking.
-#[derive(Clone, Debug, PartialEq)]
-pub struct VulkanPlanningRequestReport {
- /// Last render request observed by the planning facade.
- pub current_request: RenderRequest,
- /// Number of meaningful request updates applied to the facade.
- pub request_updates: u64,
-}
-
-impl Default for VulkanPlanningRequestReport {
- fn default() -> Self {
- Self {
- current_request: RenderRequest::conservative(),
- request_updates: 0,
- }
- }
-}
-
-/// Diagnostics for planning-facade execution telemetry.
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct VulkanPlanningExecutionReport {
- /// Total frames planned by the facade.
- pub planned_frames: u64,
- /// Total frame-submission plans emitted by the facade.
- pub submission_plans: u64,
- /// Last command-capture byte size.
- pub last_capture_size: usize,
- /// Number of simulated present calls issued by the planning facade.
- pub simulated_presents: u64,
-}
-
-/// Diagnostics for Vulkan planning backend setup and frame progression.
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct VulkanPlanningBackendReport {
- /// Request-tracking telemetry.
- pub request: VulkanPlanningRequestReport,
- /// Execution-planning telemetry.
- pub execution: VulkanPlanningExecutionReport,
- /// Last deterministic frame submission plan.
- pub last_frame_submission: Option<VulkanFrameSubmissionPlan>,
-}
-
-/// Vulkan planning backend facade used by the game entrypoint.
-#[derive(Debug)]
-pub struct VulkanPlanningBackend {
- state: VulkanPlanningBackendState,
- report: VulkanPlanningBackendReport,
- swapchain_plan: VulkanSwapchainPlan,
-}
-
-impl Default for VulkanPlanningBackend {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl VulkanPlanningBackend {
- /// Creates a new Vulkan planning backend facade.
- #[must_use]
- pub fn new() -> Self {
- Self {
- state: VulkanPlanningBackendState::Configured,
- report: VulkanPlanningBackendReport::default(),
- swapchain_plan: default_stage0_swapchain_plan(),
- }
- }
-
- /// Replaces active surface/profile request.
- pub fn set_render_request(&mut self, request: RenderRequest) {
- if self.report.request.current_request != request {
- self.report.request.current_request = request;
- self.report.request.request_updates =
- self.report.request.request_updates.saturating_add(1);
- }
- }
-
- /// Returns active render request policy.
- #[must_use]
- pub const fn render_request(&self) -> RenderRequest {
- self.report.request.current_request
- }
-
- /// Replaces active swapchain plan used for frame submission planning.
- pub fn set_swapchain_plan(&mut self, plan: VulkanSwapchainPlan) {
- self.swapchain_plan = plan;
- }
-
- /// Returns active swapchain plan.
- #[must_use]
- pub const fn swapchain_plan(&self) -> &VulkanSwapchainPlan {
- &self.swapchain_plan
- }
-
- /// Returns adapter state.
- #[must_use]
- pub const fn state(&self) -> VulkanPlanningBackendState {
- self.state
- }
-
- /// Returns backend report.
- #[must_use]
- pub fn report(&self) -> &VulkanPlanningBackendReport {
- &self.report
- }
-
- fn simulate_present(&mut self) {
- self.report.execution.simulated_presents =
- self.report.execution.simulated_presents.saturating_add(1);
- }
-}
-
-impl RenderBackend for VulkanPlanningBackend {
- fn execute(&mut self, commands: &RenderCommandList) -> Result<FrameOutput, RenderError> {
- if !matches!(
- self.state,
- VulkanPlanningBackendState::Configured | VulkanPlanningBackendState::Degraded
- ) {
- return Err(RenderError::InvalidRange);
- }
- let capture = canonical_capture(commands)?;
- let frame_plan = plan_vulkan_frame_submission(&self.swapchain_plan, commands)?;
- self.report.execution.planned_frames =
- self.report.execution.planned_frames.saturating_add(1);
- self.report.execution.submission_plans =
- self.report.execution.submission_plans.saturating_add(1);
- self.report.execution.last_capture_size = capture.len();
- self.report.last_frame_submission = Some(frame_plan);
- self.simulate_present();
- Ok(FrameOutput)
- }
-}
-
-fn default_stage0_swapchain_plan() -> VulkanSwapchainPlan {
- VulkanSwapchainPlan {
- schema: 1,
- extent: (1, 1),
- format: VulkanSurfaceFormat {
- format: vk::Format::B8G8R8A8_SRGB.as_raw(),
- color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR.as_raw(),
- },
- present_mode: vk::PresentModeKHR::FIFO.as_raw(),
- image_count: 2,
- image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw(),
- }
-}
diff --git a/adapters/fparkan-render-vulkan/src/policy.rs b/adapters/fparkan-render-vulkan/src/policy.rs
index c79b8dc..ce7b59e 100644
--- a/adapters/fparkan-render-vulkan/src/policy.rs
+++ b/adapters/fparkan-render-vulkan/src/policy.rs
@@ -1,7 +1,5 @@
use ash::vk;
use fparkan_platform::{DepthStencilSupport, RenderRequest};
-use fparkan_render::{validate_command_list, RenderCommand, RenderCommandList, RenderError};
-use serde::Serialize;
const MIN_VULKAN_API_VERSION: u32 = vk::API_VERSION_1_1;
pub(crate) const KHR_SWAPCHAIN_EXTENSION: &str = "VK_KHR_swapchain";
@@ -147,25 +145,6 @@ pub struct VulkanSwapchainRecreationReport {
pub next_extent: (u32, u32),
}
-/// Deterministic frame submission plan for command buffers and sync objects.
-#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
-pub struct VulkanFrameSubmissionPlan {
- /// Report schema version.
- pub schema: u32,
- /// Frames allowed in flight.
- pub frames_in_flight: u32,
- /// Swapchain-backed primary command buffers.
- pub command_buffers: u32,
- /// Binary semaphores allocated per frame.
- pub semaphores_per_frame: u32,
- /// Fences allocated per frame.
- pub fences_per_frame: u32,
- /// Draw commands encoded into the frame.
- pub draw_count: u32,
- /// Total indexed vertices submitted by draw commands.
- pub indexed_vertex_count: u32,
-}
-
/// Synthetic physical-device capabilities used by negative tests and reports.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VulkanPhysicalDeviceRecord {
@@ -204,7 +183,7 @@ impl VulkanPhysicalDeviceRecord {
}
/// Informational device limits relevant to future Stage 0 capability growth.
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VulkanDeviceLimits {
/// Maximum 2D image dimension supported by the device.
pub max_image_dimension_2d: u32,
@@ -217,7 +196,7 @@ pub struct VulkanDeviceLimits {
}
/// Informational capabilities preserved in deterministic Stage 0 reports.
-#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
+#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VulkanInformationalCapabilities {
/// Color formats that support sampled-image usage.
pub sampled_color_formats: Vec<i32>,
@@ -253,7 +232,7 @@ pub struct VulkanCapabilityReport {
}
/// Deterministic rejection reason for an unsuitable physical device.
-#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
+#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VulkanRejectedDeviceReport {
/// Human-readable device name.
pub device_name: String,
@@ -446,136 +425,6 @@ pub const fn swapchain_recreation_report(
}
}
-/// Builds a deterministic frame submission plan for a validated command list.
-///
-/// Stage 0 keeps this as a pure planning boundary so command-pool, command-buffer
-/// and synchronization policy can be tested without requiring a native surface.
-///
-/// # Errors
-///
-/// Returns [`RenderError`] when the command list has invalid frame framing,
-/// ordering, draw ranges, mesh bounds, or non-finite transforms.
-pub fn plan_vulkan_frame_submission(
- swapchain: &VulkanSwapchainPlan,
- commands: &RenderCommandList,
-) -> Result<VulkanFrameSubmissionPlan, RenderError> {
- validate_command_list(commands)?;
- let mut draw_count = 0_u32;
- let mut indexed_vertex_count = 0_u32;
- for command in &commands.commands {
- if let RenderCommand::Draw(draw) = command {
- draw_count = draw_count.saturating_add(1);
- indexed_vertex_count = indexed_vertex_count.saturating_add(draw.range.count);
- }
- }
- Ok(VulkanFrameSubmissionPlan {
- schema: 1,
- frames_in_flight: swapchain.image_count.clamp(1, 2),
- command_buffers: swapchain.image_count,
- semaphores_per_frame: 2,
- fences_per_frame: 1,
- draw_count,
- indexed_vertex_count,
- })
-}
-
-/// Renders a deterministic JSON capability report.
-#[must_use]
-pub fn render_capability_report_json(report: &VulkanCapabilityReport) -> String {
- #[derive(Serialize)]
- struct CapabilityReportJson<'a> {
- schema: u32,
- vulkan_api: String,
- device_name: &'a str,
- score: i32,
- graphics_queue_family: u32,
- present_queue_family: u32,
- portability_subset: bool,
- enabled_extensions: &'a [String],
- informational_capabilities: &'a VulkanInformationalCapabilities,
- rejected_devices: &'a [VulkanRejectedDeviceReport],
- }
-
- serialize_json_or_fallback(
- &CapabilityReportJson {
- schema: report.schema,
- vulkan_api: format_api_version(report.vulkan_api_version),
- device_name: &report.device_name,
- score: report.score,
- graphics_queue_family: report.graphics_queue_family,
- present_queue_family: report.present_queue_family,
- portability_subset: report.portability_subset,
- enabled_extensions: &report.enabled_extensions,
- informational_capabilities: &report.informational_capabilities,
- rejected_devices: &report.rejected_devices,
- },
- "{\"schema\":0,\"vulkan_api\":\"0.0.0\",\"device_name\":\"unknown\",\"score\":0,\"graphics_queue_family\":0,\"present_queue_family\":0,\"portability_subset\":false,\"enabled_extensions\":[],\"informational_capabilities\":{\"sampled_color_formats\":[],\"sampled_depth_formats\":[],\"limits\":{\"max_image_dimension_2d\":0,\"max_sampler_allocation_count\":0,\"max_per_stage_descriptor_samplers\":0,\"max_bound_descriptor_sets\":0}},\"rejected_devices\":[]}",
- )
-}
-
-/// Renders a deterministic JSON swapchain plan.
-#[must_use]
-pub fn render_swapchain_plan_json(plan: &VulkanSwapchainPlan) -> String {
- #[derive(Serialize)]
- struct SwapchainPlanJson {
- schema: u32,
- extent: [u32; 2],
- format: i32,
- color_space: i32,
- present_mode: i32,
- image_count: u32,
- image_usage: u32,
- }
-
- serialize_json_or_fallback(
- &SwapchainPlanJson {
- schema: plan.schema,
- extent: [plan.extent.0, plan.extent.1],
- format: plan.format.format,
- color_space: plan.format.color_space,
- present_mode: plan.present_mode,
- image_count: plan.image_count,
- image_usage: plan.image_usage,
- },
- "{\"schema\":0,\"extent\":[0,0],\"format\":0,\"color_space\":0,\"present_mode\":0,\"image_count\":0,\"image_usage\":0}",
- )
-}
-
-/// Renders a deterministic JSON swapchain recreation report.
-#[must_use]
-pub fn render_swapchain_recreation_report_json(report: &VulkanSwapchainRecreationReport) -> String {
- #[derive(Serialize)]
- struct SwapchainRecreationReportJson<'a> {
- schema: u32,
- reason: &'a str,
- previous_extent: [u32; 2],
- next_extent: [u32; 2],
- }
-
- serialize_json_or_fallback(
- &SwapchainRecreationReportJson {
- schema: report.schema,
- reason: match report.reason {
- VulkanSwapchainRecreationReason::Resize => "resize",
- VulkanSwapchainRecreationReason::OutOfDate => "out_of_date",
- VulkanSwapchainRecreationReason::Suboptimal => "suboptimal",
- },
- previous_extent: [report.previous_extent.0, report.previous_extent.1],
- next_extent: [report.next_extent.0, report.next_extent.1],
- },
- "{\"schema\":0,\"reason\":\"unknown\",\"previous_extent\":[0,0],\"next_extent\":[0,0]}",
- )
-}
-
-/// Renders a deterministic JSON frame submission plan.
-#[must_use]
-pub fn render_frame_submission_plan_json(plan: &VulkanFrameSubmissionPlan) -> String {
- serialize_json_or_fallback(
- plan,
- "{\"schema\":0,\"frames_in_flight\":0,\"command_buffers\":0,\"semaphores_per_frame\":0,\"fences_per_frame\":0,\"draw_count\":0,\"indexed_vertex_count\":0}",
- )
-}
-
pub(crate) fn select_composite_alpha(
supported: vk::CompositeAlphaFlagsKHR,
) -> vk::CompositeAlphaFlagsKHR {
@@ -590,13 +439,6 @@ pub(crate) fn select_composite_alpha(
}
}
-pub(crate) fn serialize_json_or_fallback<T: Serialize>(value: &T, fallback: &str) -> String {
- match serde_json::to_string(value) {
- Ok(json) => json,
- Err(_) => fallback.to_string(),
- }
-}
-
pub(crate) fn format_api_version(version: u32) -> String {
format!(
"{}.{}.{}",
diff --git a/adapters/fparkan-render-vulkan/src/shader.rs b/adapters/fparkan-render-vulkan/src/shader.rs
new file mode 100644
index 0000000..2e3184a
--- /dev/null
+++ b/adapters/fparkan-render-vulkan/src/shader.rs
@@ -0,0 +1,82 @@
+use crate::ffi::{
+ SPIRV_MAGIC, SPIRV_VERSION_1_0, TRIANGLE_FRAGMENT_SHADER_WORDS, TRIANGLE_VERTEX_SHADER_WORDS,
+};
+
+/// Error found while checking a checked-in SPIR-V module.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum VulkanShaderError {
+ /// SPIR-V module is too short to contain a header.
+ TooShort {
+ /// Shader name.
+ name: &'static str,
+ },
+ /// SPIR-V module has an invalid magic word.
+ InvalidMagic {
+ /// Shader name.
+ name: &'static str,
+ /// Found magic word.
+ found: u32,
+ },
+ /// SPIR-V module version is below 1.0.
+ UnsupportedVersion {
+ /// Shader name.
+ name: &'static str,
+ /// Found version word.
+ found: u32,
+ },
+ /// SPIR-V module declares an invalid bound.
+ InvalidBound {
+ /// Shader name.
+ name: &'static str,
+ },
+}
+
+impl std::fmt::Display for VulkanShaderError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::TooShort { name } => write!(f, "shader {name} SPIR-V module is too short"),
+ Self::InvalidMagic { name, found } => {
+ write!(f, "shader {name} has invalid SPIR-V magic 0x{found:08x}")
+ }
+ Self::UnsupportedVersion { name, found } => write!(
+ f,
+ "shader {name} has unsupported SPIR-V version 0x{found:08x}"
+ ),
+ Self::InvalidBound { name } => write!(f, "shader {name} has invalid SPIR-V bound"),
+ }
+ }
+}
+
+impl std::error::Error for VulkanShaderError {}
+
+/// Checks the checked-in triangle shader containers before creating a pipeline.
+pub(crate) fn validate_triangle_shaders() -> Result<(), VulkanShaderError> {
+ validate_spirv_container("triangle.vert", TRIANGLE_VERTEX_SHADER_WORDS)?;
+ validate_spirv_container("triangle.frag", TRIANGLE_FRAGMENT_SHADER_WORDS)?;
+ Ok(())
+}
+
+pub(crate) fn validate_spirv_container(
+ name: &'static str,
+ words: &[u32],
+) -> Result<(), VulkanShaderError> {
+ if words.len() < 5 {
+ return Err(VulkanShaderError::TooShort { name });
+ }
+ if words[0] != SPIRV_MAGIC {
+ return Err(VulkanShaderError::InvalidMagic {
+ name,
+ found: words[0],
+ });
+ }
+ if words[1] < SPIRV_VERSION_1_0 {
+ return Err(VulkanShaderError::UnsupportedVersion {
+ name,
+ found: words[1],
+ });
+ }
+ if words[3] == 0 {
+ return Err(VulkanShaderError::InvalidBound { name });
+ }
+ Ok(())
+}
diff --git a/adapters/fparkan-render-vulkan/src/shader_manifest.rs b/adapters/fparkan-render-vulkan/src/shader_manifest.rs
deleted file mode 100644
index be86e50..0000000
--- a/adapters/fparkan-render-vulkan/src/shader_manifest.rs
+++ /dev/null
@@ -1,399 +0,0 @@
-use fparkan_binary::{sha256, sha256_hex};
-use serde::Serialize;
-
-pub(crate) use crate::ffi::{
- SPIRV_MAGIC, SPIRV_VERSION_1_0, TRIANGLE_FRAGMENT_SHADER_WORDS, TRIANGLE_VERTEX_SHADER_WORDS,
-};
-use crate::policy::serialize_json_or_fallback;
-
-pub(crate) const SHADER_MANIFEST_SCHEMA: u32 = 2;
-pub(crate) const SHADER_TARGET_ENV: &str = "vulkan1.1";
-pub(crate) const SHADER_COMPILER_NAME: &str = "glslangValidator";
-pub(crate) const SHADER_COMPILER_VERSION: &str = "11:16.3.0";
-pub(crate) const SHADER_COMPILER_BINARY_SHA256: &str =
- "9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f";
-pub(crate) const SPIRV_VALIDATOR_NAME: &str = "spirv-val";
-pub(crate) const SPIRV_VALIDATOR_VERSION: &str =
- "SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00";
-pub(crate) const SPIRV_VALIDATOR_BINARY_SHA256: &str =
- "f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f";
-pub(crate) const TRIANGLE_VERTEX_SOURCE_PATH: &str =
- "adapters/fparkan-render-vulkan/shaders/triangle.vert";
-pub(crate) const TRIANGLE_VERTEX_SOURCE_SHA256: &str =
- "fe3721202477220d2d9677b6572d7f3baecb374e94f04eea9d7286ed5e98b6c9";
-pub(crate) const TRIANGLE_VERTEX_SPIRV_PATH: &str =
- "adapters/fparkan-render-vulkan/shaders/triangle.vert.spv";
-pub(crate) const TRIANGLE_VERTEX_COMPILE_COMMAND: &str =
- "glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv";
-pub(crate) const TRIANGLE_VERTEX_VALIDATE_COMMAND: &str =
- "spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv";
-const TRIANGLE_FRAGMENT_SOURCE_PATH: &str = "adapters/fparkan-render-vulkan/shaders/triangle.frag";
-const TRIANGLE_FRAGMENT_SOURCE_SHA256: &str =
- "fb3ed435af48e4fb4a817f160b467f7a561c7c547c87b6f0c1e89f5f58af7329";
-const TRIANGLE_FRAGMENT_SPIRV_PATH: &str =
- "adapters/fparkan-render-vulkan/shaders/triangle.frag.spv";
-const TRIANGLE_FRAGMENT_COMPILE_COMMAND: &str =
- "glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv";
-const TRIANGLE_FRAGMENT_VALIDATE_COMMAND: &str =
- "spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv";
-
-fn shader_compiler_name() -> &'static str {
- option_env!("FPARKAN_BUILD_SHADER_COMPILER_NAME").unwrap_or(SHADER_COMPILER_NAME)
-}
-
-fn shader_compiler_version() -> &'static str {
- option_env!("FPARKAN_BUILD_SHADER_COMPILER_VERSION").unwrap_or(SHADER_COMPILER_VERSION)
-}
-
-fn shader_compiler_binary_sha256() -> &'static str {
- option_env!("FPARKAN_BUILD_SHADER_COMPILER_SHA256").unwrap_or(SHADER_COMPILER_BINARY_SHA256)
-}
-
-fn spirv_validator_name() -> &'static str {
- option_env!("FPARKAN_BUILD_SPIRV_VALIDATOR_NAME").unwrap_or(SPIRV_VALIDATOR_NAME)
-}
-
-fn spirv_validator_version() -> &'static str {
- option_env!("FPARKAN_BUILD_SPIRV_VALIDATOR_VERSION").unwrap_or(SPIRV_VALIDATOR_VERSION)
-}
-
-fn spirv_validator_binary_sha256() -> &'static str {
- option_env!("FPARKAN_BUILD_SPIRV_VALIDATOR_SHA256").unwrap_or(SPIRV_VALIDATOR_BINARY_SHA256)
-}
-
-/// Shader tool metadata pinned in the Stage 0 manifest.
-#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
-pub struct VulkanShaderToolManifest {
- /// Tool executable name.
- pub name: &'static str,
- /// Tool version string.
- pub version: &'static str,
- /// Tool binary SHA-256.
- pub binary_sha256: &'static str,
-}
-
-/// Vulkan shader stage.
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
-#[serde(rename_all = "lowercase")]
-pub enum VulkanShaderStage {
- /// Vertex stage.
- Vertex,
- /// Fragment stage.
- Fragment,
-}
-
-/// Offline SPIR-V shader manifest entry.
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct VulkanShaderModuleManifest {
- /// Logical shader name.
- pub name: &'static str,
- /// Shader stage.
- pub stage: VulkanShaderStage,
- /// SPIR-V entry point.
- pub entry_point: &'static str,
- /// Descriptor set count.
- pub descriptor_sets: u32,
- /// Push constant byte count.
- pub push_constant_bytes: u32,
- /// Checked-in GLSL source path.
- pub source_path: &'static str,
- /// Checked-in GLSL source SHA-256.
- pub source_sha256: &'static str,
- /// Checked-in SPIR-V module path.
- pub spirv_path: &'static str,
- /// Exact offline compile command used for the checked-in SPIR-V artifact.
- pub compile_command: &'static str,
- /// Exact offline validation command used for the checked-in SPIR-V artifact.
- pub validate_command: &'static str,
- /// SPIR-V words.
- pub words: &'static [u32],
-}
-
-/// Shader manifest validation report.
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct VulkanShaderManifestReport {
- /// Report schema version.
- pub schema: u32,
- /// Explicit Vulkan target environment for the checked-in SPIR-V.
- pub target_env: &'static str,
- /// Pinned compiler metadata.
- pub compiler: VulkanShaderToolManifest,
- /// Pinned validator metadata.
- pub validator: VulkanShaderToolManifest,
- /// Shader module reports.
- pub modules: Vec<VulkanShaderModuleReport>,
- /// Hash of the normalized shader manifest.
- pub manifest_hash: String,
-}
-
-/// Shader module validation report.
-#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
-pub struct VulkanShaderModuleReport {
- /// Logical shader name.
- pub name: &'static str,
- /// Shader stage.
- pub stage: VulkanShaderStage,
- /// SPIR-V entry point.
- pub entry_point: &'static str,
- /// Checked-in GLSL source path.
- pub source_path: &'static str,
- /// Checked-in GLSL source SHA-256.
- pub source_sha256: &'static str,
- /// Checked-in SPIR-V module path.
- pub spirv_path: &'static str,
- /// SPIR-V word count.
- pub word_count: usize,
- /// SPIR-V byte hash.
- pub sha256: String,
- /// Descriptor set count.
- pub descriptor_sets: u32,
- /// Push constant byte count.
- pub push_constant_bytes: u32,
- /// Exact offline compile command used for the checked-in SPIR-V artifact.
- pub compile_command: &'static str,
- /// Exact offline validation command used for the checked-in SPIR-V artifact.
- pub validate_command: &'static str,
- /// Stable hash of the reflected interface contract for this module.
- pub interface_hash: String,
-}
-
-/// Shader manifest validation error.
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum VulkanShaderManifestError {
- /// SPIR-V module is too short to contain a header.
- TooShort {
- /// Shader name.
- name: &'static str,
- },
- /// SPIR-V module has an invalid magic word.
- InvalidMagic {
- /// Shader name.
- name: &'static str,
- /// Found magic word.
- found: u32,
- },
- /// SPIR-V module version is below 1.0.
- UnsupportedVersion {
- /// Shader name.
- name: &'static str,
- /// Found version word.
- found: u32,
- },
- /// SPIR-V module declares an invalid bound.
- InvalidBound {
- /// Shader name.
- name: &'static str,
- },
-}
-
-impl std::fmt::Display for VulkanShaderManifestError {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::TooShort { name } => write!(f, "shader {name} SPIR-V module is too short"),
- Self::InvalidMagic { name, found } => {
- write!(f, "shader {name} has invalid SPIR-V magic 0x{found:08x}")
- }
- Self::UnsupportedVersion { name, found } => write!(
- f,
- "shader {name} has unsupported SPIR-V version 0x{found:08x}"
- ),
- Self::InvalidBound { name } => write!(f, "shader {name} has invalid SPIR-V bound"),
- }
- }
-}
-
-impl std::error::Error for VulkanShaderManifestError {}
-
-/// Returns the built-in Stage 0 indexed-triangle shader manifest.
-#[must_use]
-pub fn triangle_shader_manifest() -> Vec<VulkanShaderModuleManifest> {
- vec![
- VulkanShaderModuleManifest {
- name: "triangle.vert",
- stage: VulkanShaderStage::Vertex,
- entry_point: "main",
- descriptor_sets: 0,
- push_constant_bytes: 64,
- source_path: TRIANGLE_VERTEX_SOURCE_PATH,
- source_sha256: TRIANGLE_VERTEX_SOURCE_SHA256,
- spirv_path: TRIANGLE_VERTEX_SPIRV_PATH,
- compile_command: TRIANGLE_VERTEX_COMPILE_COMMAND,
- validate_command: TRIANGLE_VERTEX_VALIDATE_COMMAND,
- words: TRIANGLE_VERTEX_SHADER_WORDS,
- },
- VulkanShaderModuleManifest {
- name: "triangle.frag",
- stage: VulkanShaderStage::Fragment,
- entry_point: "main",
- descriptor_sets: 1,
- push_constant_bytes: 68,
- source_path: TRIANGLE_FRAGMENT_SOURCE_PATH,
- source_sha256: TRIANGLE_FRAGMENT_SOURCE_SHA256,
- spirv_path: TRIANGLE_FRAGMENT_SPIRV_PATH,
- compile_command: TRIANGLE_FRAGMENT_COMPILE_COMMAND,
- validate_command: TRIANGLE_FRAGMENT_VALIDATE_COMMAND,
- words: TRIANGLE_FRAGMENT_SHADER_WORDS,
- },
- ]
-}
-
-/// Validates shader SPIR-V containers and renders a deterministic report.
-///
-/// # Errors
-///
-/// Returns [`VulkanShaderManifestError`] when a module fails Stage 0 SPIR-V
-/// container validation.
-pub fn validate_shader_manifest(
- modules: &[VulkanShaderModuleManifest],
-) -> Result<VulkanShaderManifestReport, VulkanShaderManifestError> {
- let mut reports = Vec::with_capacity(modules.len());
- for module in modules {
- validate_spirv_container(module)?;
- let bytes = spirv_words_to_bytes(module.words);
- reports.push(VulkanShaderModuleReport {
- name: module.name,
- stage: module.stage,
- entry_point: module.entry_point,
- source_path: module.source_path,
- source_sha256: module.source_sha256,
- spirv_path: module.spirv_path,
- word_count: module.words.len(),
- sha256: sha256_hex(&sha256(&bytes)),
- descriptor_sets: module.descriptor_sets,
- push_constant_bytes: module.push_constant_bytes,
- compile_command: module.compile_command,
- validate_command: module.validate_command,
- interface_hash: shader_interface_hash(module),
- });
- }
- let normalized = render_shader_manifest_without_hash_json(&reports);
- Ok(VulkanShaderManifestReport {
- schema: SHADER_MANIFEST_SCHEMA,
- target_env: SHADER_TARGET_ENV,
- compiler: VulkanShaderToolManifest {
- name: shader_compiler_name(),
- version: shader_compiler_version(),
- binary_sha256: shader_compiler_binary_sha256(),
- },
- validator: VulkanShaderToolManifest {
- name: spirv_validator_name(),
- version: spirv_validator_version(),
- binary_sha256: spirv_validator_binary_sha256(),
- },
- modules: reports,
- manifest_hash: sha256_hex(&sha256(normalized.as_bytes())),
- })
-}
-
-/// Renders a deterministic JSON shader manifest report.
-#[must_use]
-pub fn render_shader_manifest_report_json(report: &VulkanShaderManifestReport) -> String {
- #[derive(Serialize)]
- struct ShaderManifestReportJson<'a> {
- schema: u32,
- target_env: &'a str,
- compiler: &'a VulkanShaderToolManifest,
- validator: &'a VulkanShaderToolManifest,
- modules: &'a [VulkanShaderModuleReport],
- manifest_hash: &'a str,
- }
-
- serialize_json_or_fallback(
- &ShaderManifestReportJson {
- schema: report.schema,
- target_env: report.target_env,
- compiler: &report.compiler,
- validator: &report.validator,
- modules: &report.modules,
- manifest_hash: &report.manifest_hash,
- },
- "{\"schema\":0,\"target_env\":\"unknown\",\"compiler\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"validator\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"modules\":[],\"manifest_hash\":\"unknown\"}",
- )
-}
-
-fn shader_interface_hash(module: &VulkanShaderModuleManifest) -> String {
- #[derive(Serialize)]
- struct ShaderInterfaceHashJson<'a> {
- stage: VulkanShaderStage,
- entry_point: &'a str,
- descriptor_sets: u32,
- push_constant_bytes: u32,
- }
-
- let normalized = serialize_json_or_fallback(
- &ShaderInterfaceHashJson {
- stage: module.stage,
- entry_point: module.entry_point,
- descriptor_sets: module.descriptor_sets,
- push_constant_bytes: module.push_constant_bytes,
- },
- "{\"stage\":\"vertex\",\"entry_point\":\"main\",\"descriptor_sets\":0,\"push_constant_bytes\":0}",
- );
- sha256_hex(&sha256(normalized.as_bytes()))
-}
-
-fn validate_spirv_container(
- module: &VulkanShaderModuleManifest,
-) -> Result<(), VulkanShaderManifestError> {
- if module.words.len() < 5 {
- return Err(VulkanShaderManifestError::TooShort { name: module.name });
- }
- if module.words[0] != SPIRV_MAGIC {
- return Err(VulkanShaderManifestError::InvalidMagic {
- name: module.name,
- found: module.words[0],
- });
- }
- if module.words[1] < SPIRV_VERSION_1_0 {
- return Err(VulkanShaderManifestError::UnsupportedVersion {
- name: module.name,
- found: module.words[1],
- });
- }
- if module.words[3] == 0 {
- return Err(VulkanShaderManifestError::InvalidBound { name: module.name });
- }
- Ok(())
-}
-
-fn spirv_words_to_bytes(words: &[u32]) -> Vec<u8> {
- let mut out = Vec::with_capacity(words.len() * 4);
- for word in words {
- out.extend_from_slice(&word.to_le_bytes());
- }
- out
-}
-
-fn render_shader_manifest_without_hash_json(modules: &[VulkanShaderModuleReport]) -> String {
- #[derive(Serialize)]
- struct ShaderManifestWithoutHashJson<'a> {
- schema: u32,
- target_env: &'a str,
- compiler: VulkanShaderToolManifest,
- validator: VulkanShaderToolManifest,
- modules: &'a [VulkanShaderModuleReport],
- }
-
- let json = serialize_json_or_fallback(
- &ShaderManifestWithoutHashJson {
- schema: SHADER_MANIFEST_SCHEMA,
- target_env: SHADER_TARGET_ENV,
- compiler: VulkanShaderToolManifest {
- name: SHADER_COMPILER_NAME,
- version: SHADER_COMPILER_VERSION,
- binary_sha256: SHADER_COMPILER_BINARY_SHA256,
- },
- validator: VulkanShaderToolManifest {
- name: SPIRV_VALIDATOR_NAME,
- version: SPIRV_VALIDATOR_VERSION,
- binary_sha256: SPIRV_VALIDATOR_BINARY_SHA256,
- },
- modules,
- },
- "{\"schema\":0,\"target_env\":\"unknown\",\"compiler\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"validator\":{\"name\":\"unknown\",\"version\":\"unknown\",\"binary_sha256\":\"unknown\"},\"modules\":[]}",
- );
- match json.strip_suffix('}') {
- Some(stripped) => stripped.to_string(),
- None => json,
- }
-}