diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index b74176f6..c33b2cf4 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -1,4 +1,3 @@ -use crate::gfx_overlay::{DeferredGfxOverlay, OverlayCommand}; use crate::memory_banks::GlyphBankPoolAccess; use prometeu_hal::GfxBridge; use prometeu_hal::color::Color; @@ -34,9 +33,7 @@ pub enum BlendMode { /// /// `Gfx` owns the framebuffer backend and the canonical game-frame raster path /// consumed by `FrameComposer`. That canonical path covers scene composition, -/// sprite composition, and fades. Public `gfx.*` primitives remain valid, but -/// they do not define the canonical game composition contract; they belong to a -/// separate final overlay/debug stage. +/// sprite composition, and fades. Public `gfx.*` primitives remain valid. pub struct Gfx { /// Width of the internal framebuffer in pixels. w: usize, @@ -45,15 +42,10 @@ pub struct Gfx { /// Front buffer: the "VRAM" currently being displayed by the Host window. front: Vec, /// Back buffer: the working buffer where canonical game frames are composed - /// before any final overlay/debug drain. back: Vec, /// Shared access to graphical memory banks (tiles and palettes). pub glyph_banks: Arc, - /// Deferred overlay/debug capture kept separate from canonical game composition. - overlay: DeferredGfxOverlay, - /// Internal guard to replay deferred overlay commands without re-enqueueing them. - is_draining_overlay: bool, /// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory). pub sprites: [Sprite; 512], @@ -298,8 +290,6 @@ impl Gfx { front: vec![0; len], back: vec![0; len], glyph_banks, - overlay: DeferredGfxOverlay::default(), - is_draining_overlay: false, sprites: [EMPTY_SPRITE; 512], sprite_count: 0, scene_fade_level: 31, @@ -319,42 +309,6 @@ impl Gfx { (self.w, self.h) } - pub fn begin_overlay_frame(&mut self) { - self.overlay.begin_frame(); - } - - pub fn overlay(&self) -> &DeferredGfxOverlay { - &self.overlay - } - - pub fn drain_overlay_debug(&mut self) { - let commands = self.overlay.take_commands(); - self.is_draining_overlay = true; - - for command in commands { - match command { - OverlayCommand::FillRectBlend { x, y, w, h, color, mode } => { - self.fill_rect_blend(x, y, w, h, color, mode) - } - OverlayCommand::DrawLine { x0, y0, x1, y1, color } => { - self.draw_line(x0, y0, x1, y1, color) - } - OverlayCommand::DrawCircle { x, y, r, color } => self.draw_circle(x, y, r, color), - OverlayCommand::DrawDisc { x, y, r, border_color, fill_color } => { - self.draw_disc(x, y, r, border_color, fill_color) - } - OverlayCommand::DrawSquare { x, y, w, h, border_color, fill_color } => { - self.draw_square(x, y, w, h, border_color, fill_color) - } - OverlayCommand::DrawText { x, y, text, color } => { - self.draw_text(x, y, &text, color) - } - } - } - - self.is_draining_overlay = false; - } - /// The buffer that the host should display (RGB565). pub fn front_buffer(&self) -> &[u16] { &self.front @@ -374,10 +328,6 @@ impl Gfx { color: Color, mode: BlendMode, ) { - if !self.is_draining_overlay { - self.overlay.push(OverlayCommand::FillRectBlend { x, y, w, h, color, mode }); - return; - } if color == Color::COLOR_KEY { return; } @@ -419,10 +369,6 @@ impl Gfx { /// Draws a line between two points using Bresenham's algorithm. pub fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: Color) { - if !self.is_draining_overlay { - self.overlay.push(OverlayCommand::DrawLine { x0, y0, x1, y1, color }); - return; - } if color == Color::COLOR_KEY { return; } @@ -455,10 +401,6 @@ impl Gfx { /// Draws a circle outline using Midpoint Circle Algorithm. pub fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { - if !self.is_draining_overlay { - self.overlay.push(OverlayCommand::DrawCircle { x: xc, y: yc, r, color }); - return; - } if color == Color::COLOR_KEY { return; } @@ -527,10 +469,6 @@ impl Gfx { /// Draws a disc (filled circle with border). pub fn draw_disc(&mut self, x: i32, y: i32, r: i32, border_color: Color, fill_color: Color) { - if !self.is_draining_overlay { - self.overlay.push(OverlayCommand::DrawDisc { x, y, r, border_color, fill_color }); - return; - } self.fill_circle(x, y, r, fill_color); self.draw_circle(x, y, r, border_color); } @@ -560,10 +498,6 @@ impl Gfx { border_color: Color, fill_color: Color, ) { - if !self.is_draining_overlay { - self.overlay.push(OverlayCommand::DrawSquare { x, y, w, h, border_color, fill_color }); - return; - } self.fill_rect(x, y, w, h, fill_color); self.draw_rect(x, y, w, h, border_color); } @@ -863,10 +797,6 @@ impl Gfx { } pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) { - if !self.is_draining_overlay { - self.overlay.push(OverlayCommand::DrawText { x, y, text: text.to_string(), color }); - return; - } let mut cx = x; for c in text.chars() { self.draw_char(cx, y, c, color); @@ -961,9 +891,7 @@ fn blend_rgb565(dst: u16, src: u16, mode: BlendMode) -> u16 { #[cfg(test)] mod tests { use super::*; - use crate::FrameComposer; - use crate::asset::GlyphAssetSlotIndex; - use crate::memory_banks::{GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess}; + use crate::memory_banks::{GlyphBankPoolInstaller, MemoryBanks}; use prometeu_hal::asset::AssetId; use prometeu_hal::glyph_bank::TileSize; use prometeu_hal::scene_bank::SceneBank; @@ -1047,16 +975,6 @@ mod tests { } } - fn make_glyph_slot_index(bindings: &[(AssetId, usize)]) -> GlyphAssetSlotIndex { - let index = GlyphAssetSlotIndex::new(); - let mut slots = [None; 16]; - for (asset_id, slot) in bindings { - slots[*slot] = Some(*asset_id); - } - index.rebuild_from_slots(&slots); - index - } - #[test] fn test_draw_pixel() { let banks = Arc::new(MemoryBanks::new()); @@ -1073,12 +991,9 @@ mod tests { fn test_draw_line() { let banks = Arc::new(MemoryBanks::new()); let mut gfx = Gfx::new(10, 10, banks); - gfx.begin_overlay_frame(); gfx.draw_line(0, 0, 9, 9, Color::WHITE); - gfx.drain_overlay_debug(); assert_eq!(gfx.back[0], Color::WHITE.0); assert_eq!(gfx.back[9 * 10 + 9], Color::WHITE.0); - assert_eq!(gfx.overlay().command_count(), 0); } #[test] @@ -1104,56 +1019,9 @@ mod tests { fn test_draw_square() { let banks = Arc::new(MemoryBanks::new()); let mut gfx = Gfx::new(10, 10, banks); - gfx.begin_overlay_frame(); gfx.draw_square(2, 2, 6, 6, Color::WHITE, Color::BLACK); - gfx.drain_overlay_debug(); assert_eq!(gfx.back[2 * 10 + 2], Color::WHITE.0); assert_eq!(gfx.back[3 * 10 + 3], Color::BLACK.0); - assert_eq!(gfx.overlay().command_count(), 0); - } - - #[test] - fn draw_text_captures_overlay_command() { - let banks = Arc::new(MemoryBanks::new()); - let mut gfx = Gfx::new(32, 18, banks); - gfx.begin_overlay_frame(); - - gfx.draw_text(4, 5, "HUD", Color::WHITE); - - assert_eq!( - gfx.overlay().commands(), - &[OverlayCommand::DrawText { x: 4, y: 5, text: "HUD".into(), color: Color::WHITE }] - ); - } - - #[test] - fn overlay_state_is_separate_from_frame_composer_sprite_state() { - let banks = Arc::new(MemoryBanks::new()); - let mut gfx = Gfx::new(32, 18, Arc::clone(&banks) as Arc); - let mut frame_composer = FrameComposer::new( - 32, - 18, - Arc::clone(&banks) as Arc, - make_glyph_slot_index(&[]), - ); - - gfx.begin_overlay_frame(); - frame_composer.begin_frame(); - frame_composer.emit_sprite(Sprite { - glyph: Glyph { glyph_id: 0, palette_id: 0 }, - x: 1, - y: 2, - layer: 0, - bank_id: 0, - active: true, - flip_x: false, - flip_y: false, - priority: 0, - }); - gfx.draw_text(1, 1, "X", Color::WHITE); - - assert_eq!(frame_composer.sprite_controller().sprite_count(), 1); - assert_eq!(gfx.overlay().command_count(), 1); } #[test] diff --git a/crates/console/prometeu-drivers/src/gfx_overlay.rs b/crates/console/prometeu-drivers/src/gfx_overlay.rs deleted file mode 100644 index be77e9c4..00000000 --- a/crates/console/prometeu-drivers/src/gfx_overlay.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::gfx::BlendMode; -use prometeu_hal::color::Color; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OverlayCommand { - FillRectBlend { x: i32, y: i32, w: i32, h: i32, color: Color, mode: BlendMode }, - DrawLine { x0: i32, y0: i32, x1: i32, y1: i32, color: Color }, - DrawCircle { x: i32, y: i32, r: i32, color: Color }, - DrawDisc { x: i32, y: i32, r: i32, border_color: Color, fill_color: Color }, - DrawSquare { x: i32, y: i32, w: i32, h: i32, border_color: Color, fill_color: Color }, - DrawText { x: i32, y: i32, text: String, color: Color }, -} - -#[derive(Debug, Clone, Default)] -pub struct DeferredGfxOverlay { - commands: Vec, -} - -impl DeferredGfxOverlay { - pub fn begin_frame(&mut self) { - self.commands.clear(); - } - - pub fn push(&mut self, command: OverlayCommand) { - self.commands.push(command); - } - - pub fn commands(&self) -> &[OverlayCommand] { - &self.commands - } - - pub fn command_count(&self) -> usize { - self.commands.len() - } - - pub fn take_commands(&mut self) -> Vec { - std::mem::take(&mut self.commands) - } -} diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 6cf23447..005a870a 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -48,7 +48,6 @@ impl Default for Hardware { impl HardwareBridge for Hardware { fn begin_frame(&mut self) { - self.gfx.begin_overlay_frame(); self.frame_composer.begin_frame(); } @@ -70,7 +69,6 @@ impl HardwareBridge for Hardware { fn render_frame(&mut self) { self.frame_composer.render_frame(&mut self.gfx); - self.gfx.drain_overlay_debug(); } fn has_glyph_bank(&self, bank_id: usize) -> bool { diff --git a/crates/console/prometeu-drivers/src/lib.rs b/crates/console/prometeu-drivers/src/lib.rs index 397e9a07..9a088424 100644 --- a/crates/console/prometeu-drivers/src/lib.rs +++ b/crates/console/prometeu-drivers/src/lib.rs @@ -2,7 +2,6 @@ mod asset; mod audio; mod frame_composer; mod gfx; -mod gfx_overlay; pub mod hardware; mod memory_banks; mod pad; @@ -12,7 +11,6 @@ pub use crate::asset::AssetManager; pub use crate::audio::{Audio, AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE}; pub use crate::frame_composer::{FrameComposer, SceneStatus, SpriteController}; pub use crate::gfx::Gfx; -pub use crate::gfx_overlay::{DeferredGfxOverlay, OverlayCommand}; pub use crate::memory_banks::{ GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess, SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller, diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs index 8fc88a2c..a8a0cc8c 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs @@ -11,6 +11,7 @@ pub struct HubHomeStep; impl HubHomeStep { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering HubHome".to_string()); + ctx.os.windows().remove_all_windows(); ctx.hub.init(); } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs index 57b65486..f471823e 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_shell_running.rs @@ -67,12 +67,7 @@ impl ShellRunningStep { } if outcome.action == Some(SystemProfileAction::CloseShell) { - if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) { - ctx.os.error( - LogSource::Hub, - format!("Failed to close shell task {:?}: {:?}", self.task_id, error), - ); - } + self.close_current_shell(ctx); return Some(FirmwareState::HubHome(HubHomeStep)); } @@ -84,18 +79,22 @@ impl ShellRunningStep { self.task_id, ), ); - if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) { - ctx.os.error( - LogSource::Hub, - format!("Failed to close shell task {:?}: {:?}", self.task_id, error), - ); - } + self.close_current_shell(ctx); return Some(FirmwareState::HubHome(HubHomeStep)); } None } + fn close_current_shell(&mut self, ctx: &mut PrometeuContext) { + if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) { + ctx.os.error( + LogSource::Hub, + format!("Failed to close shell task {:?}: {:?}", self.task_id, error), + ); + } + } + pub fn on_exit(&mut self, ctx: &mut PrometeuContext) { ctx.os.info(LogSource::Hub, "Exiting ShellRunning".to_string()); } 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 8b69b441..e6aa0479 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 @@ -101,7 +101,7 @@ impl PrometeuHub { os: &mut SystemOS, hw: &mut dyn HardwareBridge, ) -> Option { - hw.gfx_mut().clear(Color::BLACK); + hw.gfx_mut().clear(Color::INDIGO); let in_shell = os.windows().focused_window().is_some(); 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 8a840997..84dd8a61 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/tests.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/tests.rs @@ -636,82 +636,6 @@ fn tick_renders_scene_through_public_composer_syscalls() { assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); } -#[test] -fn tick_draw_text_survives_scene_backed_frame_composition() { - let mut runtime = VirtualMachineRuntime::new(None); - let mut log_service = LogService::new(4096); - let mut fs = VirtualFS::new(); - let mut fs_state = FsState::Unmounted; - let mut memcard = MemcardService::new(); - let mut open_files: HashMap = HashMap::new(); - let mut next_handle = 1; - let mut vm = VirtualMachine::default(); - let signals = InputSignals::default(); - let code = assemble( - "PUSH_I32 0\nHOSTCALL 0\nPOP_N 1\n\ - PUSH_I32 0\nPUSH_I32 0\nHOSTCALL 1\n\ - PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nPUSH_I32 65535\nHOSTCALL 2\n\ - FRAME_SYNC\nHALT", - ) - .expect("assemble"); - let program = serialized_single_function_module_with_consts( - code, - vec![ConstantPoolEntry::String("I".into())], - vec![ - SyscallDecl { - module: "composer".into(), - name: "bind_scene".into(), - version: 1, - arg_slots: 1, - ret_slots: 1, - }, - SyscallDecl { - module: "composer".into(), - name: "set_camera".into(), - version: 1, - arg_slots: 2, - ret_slots: 0, - }, - SyscallDecl { - module: "gfx".into(), - name: "draw_text".into(), - version: 1, - arg_slots: 4, - ret_slots: 0, - }, - ], - ); - let cartridge = cartridge_with_program(program, caps::GFX); - - let banks = Arc::new(MemoryBanks::new()); - banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE))); - banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8))); - let mut hardware = Hardware::new_with_memory_banks(Arc::clone(&banks)); - let glyph_slot_index = hardware.assets.glyph_asset_slot_index(); - let mut slots = [None; 16]; - slots[0] = Some(0); - glyph_slot_index.rebuild_from_slots(&slots); - hardware.gfx.scene_fade_level = 31; - hardware.gfx.hud_fade_level = 31; - - runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize"); - let report = runtime.tick( - &mut log_service, - &mut fs, - &mut fs_state, - &mut memcard, - &mut open_files, - &mut next_handle, - &mut vm, - &signals, - &mut hardware, - ); - - assert!(report.is_none(), "scene-backed overlay text must not crash"); - hardware.gfx.present(); - assert_eq!(hardware.gfx.front_buffer()[0], Color::WHITE.raw()); -} - #[test] fn tick_draw_text_survives_no_scene_frame_path() { let mut runtime = VirtualMachineRuntime::new(None); diff --git a/crates/host/prometeu-host-desktop-winit/src/input.rs b/crates/host/prometeu-host-desktop-winit/src/input.rs index 21e7334c..53f71c62 100644 --- a/crates/host/prometeu-host-desktop-winit/src/input.rs +++ b/crates/host/prometeu-host-desktop-winit/src/input.rs @@ -4,6 +4,50 @@ use winit::event::{ElementState, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::Window; +#[derive(Debug, Clone, Copy)] +struct FbViewport { + pub x: f32, + pub y: f32, + pub w: f32, + pub h: f32, + pub scale: f32, +} + +impl FbViewport { + fn framebuffer_viewport(window: &Window) -> Option { + let size = window.inner_size(); + + let win_w = size.width as f32; + let win_h = size.height as f32; + + if win_w <= 0.0 || win_h <= 0.0 { + return None; + } + + let fb_w = Hardware::W as f32; + let fb_h = Hardware::H as f32; + + let scale_x = (win_w / fb_w).floor(); + let scale_y = (win_h / fb_h).floor(); + + let scale = scale_x.min(scale_y).max(1.0); + + let viewport_w = fb_w * scale; + let viewport_h = fb_h * scale; + + let viewport_x = ((win_w - viewport_w) * 0.5).floor(); + let viewport_y = ((win_h - viewport_h) * 0.5).floor(); + + Some(FbViewport { + x: viewport_x, + y: viewport_y, + w: viewport_w, + h: viewport_h, + scale, + }) + } +} + pub struct HostInputHandler { pub signals: InputSignals, } @@ -49,9 +93,10 @@ impl HostInputHandler { } WindowEvent::CursorMoved { position, .. } => { - let v = window_to_fb(position.x as f32, position.y as f32, window); - self.signals.x_pos = v.0; - self.signals.y_pos = v.1; + if let Some((x, y)) = window_to_fb(position.x as f32, position.y as f32, window) { + self.signals.x_pos = x; + self.signals.y_pos = y; + } } WindowEvent::MouseInput { state, button, .. } if *button == MouseButton::Left => { @@ -67,16 +112,24 @@ impl HostInputHandler { _ => {} } + + fn window_to_fb(wx: f32, wy: f32, window: &Window) -> Option<(i32, i32)> { + let viewport = FbViewport::framebuffer_viewport(window)?; + + let local_x = wx - viewport.x; + let local_y = wy - viewport.y; + + if local_x < 0.0 || local_y < 0.0 || local_x >= viewport.w || local_y >= viewport.h { + return None; + } + + let fb_x = (local_x / viewport.scale).floor() as i32; + let fb_y = (local_y / viewport.scale).floor() as i32; + + Some(( + fb_x.clamp(0, Hardware::W as i32 - 1), + fb_y.clamp(0, Hardware::H as i32 - 1), + )) + } } -} - -pub fn window_to_fb(wx: f32, wy: f32, window: &Window) -> (i32, i32) { - let size = window.inner_size(); - let fb_w = Hardware::W as f32; - let fb_h = Hardware::H as f32; - - let x = (wx * fb_w / size.width as f32).floor() as i32; - let y = (wy * fb_h / size.height as f32).floor() as i32; - - (x.clamp(0, Hardware::W as i32 - 1), y.clamp(0, Hardware::H as i32 - 1)) -} +} \ No newline at end of file