diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 44852431..8148df82 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -11,7 +11,7 @@ use crate::touch::Touch; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::sprite::Sprite; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; -use prometeu_hal::{Game2DFramePacket, ShellUiFramePacket}; +use prometeu_hal::{Game2DFramePacket, RenderSubmission, RenderSubmissionPacket}; use std::sync::Arc; /// Aggregate structure for all virtual hardware peripherals. @@ -72,8 +72,20 @@ impl HardwareBridge for Hardware { self.frame_composer.close_game2d_packet() } - fn render_shell_ui_packet(&mut self, packet: &ShellUiFramePacket) { - self.gfx.render_shell_ui_frame_packet(packet); + fn publish_render_submission(&mut self, submission: &RenderSubmission) { + match &submission.packet { + RenderSubmissionPacket::Game2D(packet) => { + if self.frame_composer.active_scene_id().is_none() { + self.gfx.render_game2d_frame_packet(packet); + } else { + self.frame_composer.render_frame(&mut self.gfx); + } + } + RenderSubmissionPacket::ShellUi(packet) => { + self.gfx.render_shell_ui_frame_packet(packet); + } + } + self.gfx.present(); } fn render_frame(&mut self) { @@ -85,10 +97,6 @@ impl HardwareBridge for Hardware { } } - fn present(&mut self) { - self.gfx.present(); - } - fn has_glyph_bank(&self, bank_id: usize) -> bool { self.gfx.glyph_banks.glyph_bank_slot(bank_id).is_some() } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 3679f393..f7e4a1c8 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -231,7 +231,7 @@ mod tests { debug_info: None, exports: vec![], syscalls: vec![SyscallDecl { - module: "gfx".into(), + module: "gfx2d".into(), name: "clear".into(), version: 1, arg_slots: 1, diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs index 8133a3cc..8c5d6a52 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs @@ -2,6 +2,7 @@ use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep}; use crate::firmware::prometeu_context::PrometeuContext; use prometeu_hal::color::Color; use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::{FrameId, GfxUiCommand, RenderSubmission, ShellUiFramePacket}; use prometeu_system::CrashReport; #[derive(Debug, Clone)] @@ -22,11 +23,8 @@ impl AppCrashesStep { // Update peripherals for input on the crash screen ctx.hw.pad_mut().begin_frame(ctx.signals); - // Error screen: red background, white text - ctx.hw.gfx_mut().clear(Color::RED); - // For now we just log or show something simple - // In the future, use draw_text - ctx.hw.gfx_mut().present(); + let packet = ShellUiFramePacket::new(vec![GfxUiCommand::Clear { color: Color::RED }]); + ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); // If START is pressed, return to the Hub if ctx.hw.pad().start().down { diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs index 5bd9579f..5a286f63 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs @@ -38,10 +38,6 @@ impl GameRunningStep { let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.hw); - if !ctx.os.vm().logical_frame_active() { - ctx.hw.gfx_mut().present(); - } - if let Some(report) = result { let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report)); return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs index 0ca52730..5ba02ae9 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs @@ -2,6 +2,8 @@ use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep}; use crate::firmware::prometeu_context::PrometeuContext; use prometeu_hal::color::Color; use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::primitives::Rect; +use prometeu_hal::{FrameId, GfxUiCommand, RenderSubmission, ShellUiFramePacket}; #[derive(Debug, Clone)] pub struct SplashScreenStep { @@ -22,9 +24,6 @@ impl SplashScreenStep { // Update peripherals for input ctx.hw.pad_mut().begin_frame(ctx.signals); - // Clear screen - ctx.hw.gfx_mut().clear(Color::ORANGE); - // Draw expanding square let (sw, sh) = ctx.hw.gfx().size(); let max_size = (sw.min(sh) as i32 / 2).max(1); @@ -38,8 +37,14 @@ impl SplashScreenStep { let x = (sw as i32 - current_size) / 2; let y = (sh as i32 - current_size) / 2; - ctx.hw.gfx_mut().fill_rect(x, y, current_size, current_size, Color::WHITE); - ctx.hw.gfx_mut().present(); + let packet = ShellUiFramePacket::new(vec![ + GfxUiCommand::Clear { color: Color::ORANGE }, + GfxUiCommand::FillRect { + rect: Rect { x, y, w: current_size, h: current_size }, + color: Color::WHITE, + }, + ]); + ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); // Transition logic // If any button is pressed at any time after the animation ends diff --git a/crates/console/prometeu-hal/src/hardware_bridge.rs b/crates/console/prometeu-hal/src/hardware_bridge.rs index 5c74454d..fc06d80c 100644 --- a/crates/console/prometeu-hal/src/hardware_bridge.rs +++ b/crates/console/prometeu-hal/src/hardware_bridge.rs @@ -2,7 +2,7 @@ use crate::asset_bridge::AssetBridge; use crate::audio_bridge::AudioBridge; use crate::gfx_bridge::GfxBridge; use crate::pad_bridge::PadBridge; -use crate::render_submission::{Game2DFramePacket, ShellUiFramePacket}; +use crate::render_submission::{Game2DFramePacket, RenderSubmission}; use crate::sprite::Sprite; use crate::touch_bridge::TouchBridge; @@ -13,9 +13,8 @@ pub trait HardwareBridge { fn set_camera(&mut self, x: i32, y: i32); fn emit_sprite(&mut self, sprite: Sprite) -> bool; fn close_game2d_packet(&self) -> Game2DFramePacket; - fn render_shell_ui_packet(&mut self, packet: &ShellUiFramePacket); + fn publish_render_submission(&mut self, submission: &RenderSubmission); fn render_frame(&mut self); - fn present(&mut self); fn has_glyph_bank(&self, bank_id: usize) -> bool; fn gfx(&self) -> &dyn GfxBridge; diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs index 86c59616..f2fb3624 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs @@ -1,7 +1,9 @@ use crate::{CrashReport, SystemOS}; use prometeu_hal::color::Color; use prometeu_hal::primitives::Rect; -use prometeu_hal::{GfxUiCommand, HardwareBridge, InputSignals, ShellUiFramePacket}; +use prometeu_hal::{ + FrameId, GfxUiCommand, HardwareBridge, InputSignals, RenderSubmission, ShellUiFramePacket, +}; use prometeu_vm::VirtualMachine; const SHELL_A_BUTTON: Rect = Rect { x: 112, y: 108, w: 112, h: 48 }; @@ -140,7 +142,7 @@ impl PrometeuHub { } ShellUiFramePacket::new(commands) }; - hw.render_shell_ui_packet(&packet); + hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet)); } pub fn update_shell_profile( @@ -162,7 +164,6 @@ impl PrometeuHub { } self.render(os, hw); - hw.present(); SystemProfileUpdate { crash, action } } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs index acdd5dd3..a1c748da 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -653,7 +653,6 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { ); assert!(report.is_none(), "frame render path must not crash"); - hardware.gfx.present(); assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); } @@ -729,7 +728,6 @@ fn tick_renders_scene_through_public_composer_syscalls() { assert!(report.is_none(), "public composer path must not crash"); assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]); - hardware.gfx.present(); assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); } @@ -785,7 +783,6 @@ fn tick_draw_text_survives_no_scene_frame_path() { }; assert_eq!(packet.gfx2d.len(), 1); assert!(matches!(packet.gfx2d[0], prometeu_hal::Gfx2dCommand::DrawText { .. })); - hardware.gfx.present(); assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw()); } diff --git a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs index 43cbf5a2..fa26df0b 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tick.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tick.rs @@ -1,4 +1,5 @@ use super::dispatch::VmRuntimeHost; +use super::render_manager::RenderSurface; use super::*; use crate::CrashReport; use crate::fs::{FsState, VirtualFS}; @@ -6,13 +7,24 @@ use crate::services::memcard::MemcardService; use prometeu_hal::asset::{BankTelemetry, BankType}; use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::{ - HardwareBridge, HostContext, InputSignals, RenderSubmissionPacket, ShellUiFramePacket, + HardwareBridge, HostContext, InputSignals, RenderSubmission, RenderSubmissionPacket, + ShellUiFramePacket, }; use prometeu_vm::LogicalFrameEndingReason; use std::collections::HashMap; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::sync::atomic::Ordering; +struct HardwareRenderSurface<'a> { + hw: &'a mut dyn HardwareBridge, +} + +impl RenderSurface for HardwareRenderSurface<'_> { + fn consume_submission(&mut self, submission: &RenderSubmission) { + self.hw.publish_render_submission(submission); + } +} + impl VirtualMachineRuntime { fn host_panic_payload_to_string(payload: Box) -> String { if let Some(message) = payload.downcast_ref::() { @@ -237,7 +249,10 @@ impl VirtualMachineRuntime { .expect("shell packet must match Shell app mode"); } - if let Err(payload) = catch_unwind(AssertUnwindSafe(|| hw.render_frame())) { + if let Err(payload) = catch_unwind(AssertUnwindSafe(|| { + let mut surface = HardwareRenderSurface { hw }; + self.render_manager.publish_latest(&mut surface); + })) { let message = Self::host_panic_payload_to_string(payload); let report = CrashReport::VmPanic { message, pc: Some(vm.pc() as u32) }; self.log( diff --git a/discussion/index.ndjson b/discussion/index.ndjson index a2cd986c..0f5f2c16 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} diff --git a/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md b/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md index 3afae455..93e5d0bf 100644 --- a/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md +++ b/discussion/workflow/plans/PLN-0082-frame-publication-and-present-boundary.md @@ -2,7 +2,7 @@ id: PLN-0082 ticket: render-frame-packet-boundary title: Frame Publication and Present Boundary -status: open +status: done created: 2026-05-25 ref_decisions: [DEC-0030] tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline]