implements PLN-0116

This commit is contained in:
bQUARKz 2026-06-15 16:17:16 +01:00
parent 4b890850aa
commit 0c6a669e6e
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 150 additions and 31 deletions

View File

@ -3,6 +3,7 @@ mod audio;
mod frame_composer; mod frame_composer;
mod gfx; mod gfx;
pub mod hardware; pub mod hardware;
mod local_render_backend;
mod memory_banks; mod memory_banks;
mod pad; mod pad;
mod test_platform; mod test_platform;
@ -12,6 +13,7 @@ pub use crate::asset::AssetManager;
pub use crate::audio::{Audio, AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE}; pub use crate::audio::{Audio, AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
pub use crate::frame_composer::{ComposerBuffer, FrameComposer, SceneStatus, SpriteController}; pub use crate::frame_composer::{ComposerBuffer, FrameComposer, SceneStatus, SpriteController};
pub use crate::gfx::Gfx; pub use crate::gfx::Gfx;
pub use crate::local_render_backend::LocalFramebufferRenderBackend;
pub use crate::memory_banks::{ pub use crate::memory_banks::{
GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess, GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess,
SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller, SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller,

View File

@ -0,0 +1,111 @@
use crate::gfx::Gfx;
use crate::memory_banks::GlyphBankPoolAccess;
use prometeu_hal::app_mode::AppMode;
use prometeu_hal::{
OwnedRgba8888Frame, RenderSubmission, RenderSubmissionPacket, RenderWorkerBackend,
RenderWorkerError,
};
use std::sync::{Arc, Mutex};
pub struct LocalFramebufferRenderBackend {
gfx: Mutex<Gfx>,
}
impl LocalFramebufferRenderBackend {
pub fn new(width: usize, height: usize, glyph_banks: Arc<dyn GlyphBankPoolAccess>) -> Self {
Self { gfx: Mutex::new(Gfx::new(width, height, glyph_banks)) }
}
pub fn render_submission(
&self,
submission: &RenderSubmission,
) -> Result<OwnedRgba8888Frame, RenderWorkerError> {
let mut gfx = self.gfx.lock().map_err(|_| RenderWorkerError::InternalFailure)?;
match &submission.packet {
RenderSubmissionPacket::Game2D(packet) if submission.app_mode == AppMode::Game => {
gfx.render_game2d_frame_packet(packet);
}
RenderSubmissionPacket::ShellUi(packet) if submission.app_mode == AppMode::Shell => {
gfx.render_shell_ui_frame_packet(packet);
}
_ => return Err(RenderWorkerError::RenderFailed),
}
gfx.present();
let (width, height) = gfx.size();
OwnedRgba8888Frame::packed(
submission.frame_id,
submission.ownership,
width,
height,
gfx.front_buffer().to_vec(),
)
.map_err(|_| RenderWorkerError::InternalFailure)
}
}
impl RenderWorkerBackend for LocalFramebufferRenderBackend {
fn render(
&self,
submission: &RenderSubmission,
) -> Result<OwnedRgba8888Frame, RenderWorkerError> {
self.render_submission(submission)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory_banks::MemoryBanks;
use prometeu_hal::app_mode::AppMode;
use prometeu_hal::color::Color;
use prometeu_hal::primitives::Rect;
use prometeu_hal::{
FrameId, Game2DFramePacket, Gfx2dCommand, GfxUiCommand, RenderOwnership, ShellUiFramePacket,
};
fn backend() -> LocalFramebufferRenderBackend {
let banks = Arc::new(MemoryBanks::new());
LocalFramebufferRenderBackend::new(4, 4, banks)
}
#[test]
fn local_backend_renders_game2d_submission_to_owned_frame() {
let backend = backend();
let mut packet = Game2DFramePacket::default();
packet.gfx2d = vec![Gfx2dCommand::FillRect {
rect: Rect { x: 1, y: 1, w: 2, h: 2 },
color: Color::RED,
}];
let ownership = RenderOwnership::new(3, AppMode::Game, 9);
let submission =
RenderSubmission::game2d(FrameId::new(5), packet).with_ownership(ownership);
let frame = backend.render(&submission).expect("game frame should render");
assert_eq!(frame.frame_id, FrameId::new(5));
assert_eq!(frame.ownership, ownership);
assert_eq!(frame.width, 4);
assert_eq!(frame.height, 4);
assert_eq!(frame.pixels[1 + 4], Color::RED.raw());
assert_eq!(frame.pixels[2 + 2 * 4], Color::RED.raw());
}
#[test]
fn local_backend_renders_shell_ui_submission_to_owned_frame() {
let backend = backend();
let packet = ShellUiFramePacket::new(vec![GfxUiCommand::FillRect {
rect: Rect { x: 0, y: 0, w: 1, h: 1 },
color: Color::GREEN,
}]);
let ownership = RenderOwnership::new(4, AppMode::Shell, 10);
let submission =
RenderSubmission::shell_ui(FrameId::new(6), packet).with_ownership(ownership);
let frame = backend.render(&submission).expect("shell frame should render");
assert_eq!(frame.frame_id, FrameId::new(6));
assert_eq!(frame.ownership, ownership);
assert_eq!(frame.pixels[0], Color::GREEN.raw());
}
}

View File

@ -60,5 +60,7 @@ pub use render_submission::{
Gfx2dCommand, GfxUiCommand, HudCommand, HudPacket, RenderOwnership, RenderSubmission, Gfx2dCommand, GfxUiCommand, HudCommand, HudPacket, RenderOwnership, RenderSubmission,
RenderSubmissionPacket, ShellUiFramePacket, RenderSubmissionPacket, ShellUiFramePacket,
}; };
pub use render_worker::{RenderWorkerError, RenderWorkerTelemetry}; pub use render_worker::{
RenderWorkerBackend, RenderWorkerError, RenderWorkerFrameSink, RenderWorkerTelemetry,
};
pub use touch_bridge::TouchBridge; pub use touch_bridge::TouchBridge;

View File

@ -1,5 +1,7 @@
use std::fmt; use std::fmt;
use crate::{OwnedRgba8888Frame, RenderSubmission};
/// Typed failure surface for the real render worker and its backend. /// Typed failure surface for the real render worker and its backend.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderWorkerError { pub enum RenderWorkerError {
@ -30,6 +32,17 @@ impl fmt::Display for RenderWorkerError {
impl std::error::Error for RenderWorkerError {} impl std::error::Error for RenderWorkerError {}
pub trait RenderWorkerBackend: Send + 'static {
fn render(
&self,
submission: &RenderSubmission,
) -> Result<OwnedRgba8888Frame, RenderWorkerError>;
}
pub trait RenderWorkerFrameSink: Send + 'static {
fn publish(&self, frame: OwnedRgba8888Frame) -> Result<(), RenderWorkerError>;
}
/// Snapshot of render worker handoff, render, publish, and shutdown counters. /// Snapshot of render worker handoff, render, publish, and shutdown counters.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct RenderWorkerTelemetry { pub struct RenderWorkerTelemetry {

View File

@ -6,11 +6,12 @@ mod services;
pub use crash_report::CrashReport; pub use crash_report::CrashReport;
pub use os::{LifecycleError, LifecycleOperation, SystemOS}; pub use os::{LifecycleError, LifecycleOperation, SystemOS};
pub use programs::{NativeShellApp, PrometeuHub, SystemProfileAction, SystemProfileUpdate}; pub use programs::{NativeShellApp, PrometeuHub, SystemProfileAction, SystemProfileUpdate};
pub use prometeu_hal::{RenderWorkerBackend, RenderWorkerFrameSink};
pub use services::fs; pub use services::fs;
pub use services::process; pub use services::process;
pub use services::task; pub use services::task;
pub use services::vm_runtime::{ pub use services::vm_runtime::{
RenderWorkerBackend, RenderWorkerConfig, RenderWorkerController, RenderWorkerFrameSink, RenderWorkerConfig, RenderWorkerController, RenderWorkerHandoff, RenderWorkerHandoffWait,
RenderWorkerHandoff, RenderWorkerHandoffWait, VirtualMachineRuntime, VirtualMachineRuntime,
}; };
pub use services::windows; pub use services::windows;

View File

@ -21,9 +21,7 @@ use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier};
use prometeu_hal::{Gfx2dCommand, GfxUiCommand}; use prometeu_hal::{Gfx2dCommand, GfxUiCommand};
use prometeu_vm::VirtualMachine; use prometeu_vm::VirtualMachine;
pub use render_manager::{RenderManager, RenderRuntimeCapabilities}; pub use render_manager::{RenderManager, RenderRuntimeCapabilities};
pub use render_worker::{ pub use render_worker::{RenderWorkerConfig, RenderWorkerController};
RenderWorkerBackend, RenderWorkerConfig, RenderWorkerController, RenderWorkerFrameSink,
};
pub use render_worker_handoff::{RenderWorkerHandoff, RenderWorkerHandoffWait}; pub use render_worker_handoff::{RenderWorkerHandoff, RenderWorkerHandoffWait};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;

View File

@ -1,6 +1,6 @@
use crate::services::vm_runtime::{RenderWorkerHandoff, RenderWorkerHandoffWait}; use crate::services::vm_runtime::{RenderWorkerHandoff, RenderWorkerHandoffWait};
use prometeu_hal::{ use prometeu_hal::{
FrameId, OwnedRgba8888Frame, RenderSubmission, RenderWorkerError, RenderWorkerTelemetry, FrameId, RenderWorkerBackend, RenderWorkerError, RenderWorkerFrameSink, RenderWorkerTelemetry,
}; };
use std::panic::{AssertUnwindSafe, catch_unwind}; use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError}; use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
@ -8,17 +8,6 @@ use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle}; use std::thread::{self, JoinHandle};
use std::time::Duration; use std::time::Duration;
pub trait RenderWorkerBackend: Send + 'static {
fn render(
&self,
submission: &RenderSubmission,
) -> Result<OwnedRgba8888Frame, RenderWorkerError>;
}
pub trait RenderWorkerFrameSink: Send + 'static {
fn publish(&self, frame: OwnedRgba8888Frame) -> Result<(), RenderWorkerError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RenderWorkerConfig { pub struct RenderWorkerConfig {
pub shutdown_timeout: Duration, pub shutdown_timeout: Duration,
@ -173,20 +162,23 @@ mod tests {
use crate::services::vm_runtime::render_worker_test_harness::{ use crate::services::vm_runtime::render_worker_test_harness::{
FakeRenderBackend, RenderGate, game_submission, FakeRenderBackend, RenderGate, game_submission,
}; };
use prometeu_hal::{FrameId, OwnedRgba8888Frame}; use prometeu_hal::{FrameId, OwnedRgba8888Frame, RenderSubmission};
impl RenderWorkerBackend for Arc<FakeRenderBackend> { #[derive(Clone, Debug)]
struct SharedFakeBackend(Arc<FakeRenderBackend>);
impl RenderWorkerBackend for SharedFakeBackend {
fn render( fn render(
&self, &self,
submission: &RenderSubmission, submission: &RenderSubmission,
) -> Result<OwnedRgba8888Frame, RenderWorkerError> { ) -> Result<OwnedRgba8888Frame, RenderWorkerError> {
FakeRenderBackend::render(self, submission) self.0.render(submission)
} }
} }
impl RenderWorkerFrameSink for Arc<FakeRenderBackend> { impl RenderWorkerFrameSink for SharedFakeBackend {
fn publish(&self, frame: OwnedRgba8888Frame) -> Result<(), RenderWorkerError> { fn publish(&self, frame: OwnedRgba8888Frame) -> Result<(), RenderWorkerError> {
FakeRenderBackend::publish(self, frame); self.0.publish(frame);
Ok(()) Ok(())
} }
} }
@ -219,8 +211,8 @@ mod tests {
let mut controller = RenderWorkerController::start( let mut controller = RenderWorkerController::start(
RenderWorkerConfig::default(), RenderWorkerConfig::default(),
Arc::clone(&handoff), Arc::clone(&handoff),
Arc::clone(&backend), SharedFakeBackend(Arc::clone(&backend)),
backend, SharedFakeBackend(backend),
); );
assert_eq!(controller.stop(), Ok(())); assert_eq!(controller.stop(), Ok(()));
@ -234,8 +226,8 @@ mod tests {
let mut controller = RenderWorkerController::start( let mut controller = RenderWorkerController::start(
RenderWorkerConfig::default(), RenderWorkerConfig::default(),
Arc::clone(&handoff), Arc::clone(&handoff),
Arc::clone(&backend), SharedFakeBackend(Arc::clone(&backend)),
Arc::clone(&backend), SharedFakeBackend(Arc::clone(&backend)),
); );
handoff.publish(game_submission(11, 1, 1)); handoff.publish(game_submission(11, 1, 1));
@ -255,8 +247,8 @@ mod tests {
let mut controller = RenderWorkerController::start( let mut controller = RenderWorkerController::start(
RenderWorkerConfig { shutdown_timeout: Duration::from_millis(1) }, RenderWorkerConfig { shutdown_timeout: Duration::from_millis(1) },
Arc::clone(&handoff), Arc::clone(&handoff),
Arc::clone(&backend), SharedFakeBackend(Arc::clone(&backend)),
Arc::clone(&backend), SharedFakeBackend(Arc::clone(&backend)),
); );
handoff.publish(game_submission(12, 1, 1)); handoff.publish(game_submission(12, 1, 1));

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
id: PLN-0116 id: PLN-0116
ticket: real-render-worker-establishment ticket: real-render-worker-establishment
title: Implement Fake Local Render Backend title: Implement Fake Local Render Backend
status: open status: done
created: 2026-06-15 created: 2026-06-15
completed: completed:
ref_decisions: [DEC-0033] ref_decisions: [DEC-0033]