diff options
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/lib.rs')
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/lib.rs | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/adapters/fparkan-render-vulkan/src/lib.rs b/adapters/fparkan-render-vulkan/src/lib.rs new file mode 100644 index 0000000..3d4f44d --- /dev/null +++ b/adapters/fparkan-render-vulkan/src/lib.rs @@ -0,0 +1,175 @@ +#![forbid(unsafe_code)] +#![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. + +use fparkan_render::{ + canonical_capture, FrameOutput, RenderBackend, RenderCommandList, RenderError, +}; +use fparkan_platform::RenderRequest; +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> { + if !matches!(self.state, VulkanBackendState::Ready | VulkanBackendState::Degraded) { + 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(()) + } +} |
