Files
fparkan/adapters/fparkan-render-vulkan/src/lib.rs
T

198 lines
5.9 KiB
Rust
Raw Normal View History

2026-06-23 22:05:16 +04:00
#![forbid(unsafe_code)]
2026-06-23 22:32:50 +04:00
#![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
)
)]
2026-06-23 22:05:16 +04:00
#![deny(unsafe_op_in_unsafe_fn)]
//! Vulkan adapter facade and migration-ready backend surface contract.
//!
//! This module intentionally keeps backend-agnostic command validation in the
//! shared render crate while exposing deterministic lifecycle telemetry used by
//! Stage 0 acceptance evidence.
//!
//! This crate is the declared low-level Vulkan boundary.
2026-06-23 22:32:50 +04:00
use fparkan_platform::RenderRequest;
2026-06-23 22:05:16 +04:00
use fparkan_render::{
canonical_capture, FrameOutput, RenderBackend, RenderCommandList, RenderError,
};
use std::time::{SystemTime, UNIX_EPOCH};
/// Vulkan backend migration readiness.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VulkanBackendState {
/// Adapter prepared and able to accept commands.
Ready,
/// Adapter is tracking a recoverable runtime surface/depth pipeline fault.
Degraded,
/// Adapter has encountered a non-recoverable error.
Error,
}
impl Default for VulkanBackendState {
fn default() -> Self {
Self::Degraded
}
}
/// Diagnostics for Vulkan backend setup and frame progression.
#[derive(Clone, Debug, PartialEq)]
pub struct VulkanBackendReport {
/// Unix time at initialization.
pub initialized_at: u64,
/// Total frames executed.
pub frames_executed: u64,
/// Total command submissions.
pub submissions: u64,
/// Last command-capture byte size.
pub last_capture_size: usize,
/// Number of simulated present calls.
pub presents: u64,
/// Number of resize-driven surface plan refreshes.
pub resize_rebuilds: u64,
/// Last render request observed.
pub request: RenderRequest,
}
impl Default for VulkanBackendReport {
fn default() -> Self {
Self {
initialized_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.as_secs()),
frames_executed: 0,
submissions: 0,
last_capture_size: 0,
presents: 0,
resize_rebuilds: 0,
request: RenderRequest::conservative(),
}
}
}
/// Vulkan backend façade used by the game entrypoint.
#[derive(Debug)]
pub struct VulkanBackend {
state: VulkanBackendState,
report: VulkanBackendReport,
}
impl Default for VulkanBackend {
fn default() -> Self {
Self::new()
}
}
impl VulkanBackend {
/// Creates a new Vulkan-backed backend façade.
#[must_use]
pub fn new() -> Self {
Self {
state: VulkanBackendState::Ready,
report: VulkanBackendReport::default(),
}
}
/// Replaces active surface/profile request.
pub fn set_render_request(&mut self, request: RenderRequest) {
self.report.request = request;
self.report.resize_rebuilds = self.report.resize_rebuilds.saturating_add(1);
}
/// Returns active render request policy.
#[must_use]
pub const fn render_request(&self) -> RenderRequest {
self.report.request
}
/// Returns adapter state.
#[must_use]
pub const fn state(&self) -> VulkanBackendState {
self.state
}
/// Returns backend report.
#[must_use]
pub fn report(&self) -> &VulkanBackendReport {
&self.report
}
fn simulate_present(&mut self) {
self.report.presents = self.report.presents.saturating_add(1);
}
}
impl RenderBackend for VulkanBackend {
fn execute(&mut self, commands: &RenderCommandList) -> Result<FrameOutput, RenderError> {
2026-06-23 22:32:50 +04:00
if !matches!(
self.state,
VulkanBackendState::Ready | VulkanBackendState::Degraded
) {
2026-06-23 22:05:16 +04:00
return Err(RenderError::InvalidRange);
}
let capture = canonical_capture(commands)?;
self.report.frames_executed = self.report.frames_executed.saturating_add(1);
self.report.submissions = self.report.submissions.saturating_add(1);
self.report.last_capture_size = capture.len();
self.simulate_present();
Ok(FrameOutput)
}
}
#[cfg(test)]
mod tests {
use super::*;
use fparkan_render::{
DrawCommand, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RenderCommand, RenderPhase,
};
#[test]
fn backend_tracks_render_request_and_presents() -> Result<(), RenderError> {
let mut backend = VulkanBackend::new();
let request = RenderRequest::conservative();
backend.set_render_request(request);
assert_eq!(backend.render_request(), request);
assert_eq!(backend.report().resize_rebuilds, 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),
transform: [1.0; 16],
range: IndexRange { start: 0, count: 3 },
stable_order: 7,
}),
RenderCommand::EndFrame,
],
};
backend.execute(&commands)?;
assert_eq!(backend.state(), VulkanBackendState::Ready);
assert_eq!(backend.report().frames_executed, 1);
assert_eq!(backend.report().submissions, 1);
assert_eq!(backend.report().presents, 1);
assert!(backend.report().last_capture_size > 0);
Ok(())
}
}