diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index e2c2534a..56b6814d 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -1,3 +1,4 @@ +use crate::gfx_overlay::{DeferredGfxOverlay, OverlayCommand}; use crate::memory_banks::GlyphBankPoolAccess; use prometeu_hal::GfxBridge; use prometeu_hal::color::Color; @@ -49,6 +50,8 @@ pub struct Gfx { /// 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, /// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory). pub sprites: [Sprite; 512], @@ -288,6 +291,7 @@ impl Gfx { front: vec![0; len], back: vec![0; len], glyph_banks, + overlay: DeferredGfxOverlay::default(), sprites: [EMPTY_SPRITE; 512], sprite_count: 0, scene_fade_level: 31, @@ -307,6 +311,14 @@ impl Gfx { (self.w, self.h) } + pub fn begin_overlay_frame(&mut self) { + self.overlay.begin_frame(); + } + + pub fn overlay(&self) -> &DeferredGfxOverlay { + &self.overlay + } + /// The buffer that the host should display (RGB565). pub fn front_buffer(&self) -> &[u16] { &self.front @@ -326,6 +338,7 @@ impl Gfx { color: Color, mode: BlendMode, ) { + self.overlay.push(OverlayCommand::FillRectBlend { x, y, w, h, color, mode }); if color == Color::COLOR_KEY { return; } @@ -367,6 +380,7 @@ 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) { + self.overlay.push(OverlayCommand::DrawLine { x0, y0, x1, y1, color }); if color == Color::COLOR_KEY { return; } @@ -399,6 +413,7 @@ impl Gfx { /// Draws a circle outline using Midpoint Circle Algorithm. pub fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { + self.overlay.push(OverlayCommand::DrawCircle { x: xc, y: yc, r, color }); if color == Color::COLOR_KEY { return; } @@ -467,6 +482,7 @@ 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) { + self.overlay.push(OverlayCommand::DrawDisc { x, y, r, border_color, fill_color }); self.fill_circle(x, y, r, fill_color); self.draw_circle(x, y, r, border_color); } @@ -496,6 +512,7 @@ impl Gfx { border_color: Color, fill_color: Color, ) { + self.overlay.push(OverlayCommand::DrawSquare { x, y, w, h, border_color, fill_color }); self.fill_rect(x, y, w, h, fill_color); self.draw_rect(x, y, w, h, border_color); } @@ -783,6 +800,7 @@ impl Gfx { } pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) { + self.overlay.push(OverlayCommand::DrawText { x, y, text: text.to_string(), color }); let mut cx = x; for c in text.chars() { self.draw_char(cx, y, c, color); @@ -827,7 +845,8 @@ impl Gfx { #[cfg(test)] mod tests { use super::*; - use crate::memory_banks::{GlyphBankPoolInstaller, MemoryBanks}; + use crate::FrameComposer; + use crate::memory_banks::{GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess}; use prometeu_hal::glyph_bank::TileSize; use prometeu_hal::scene_bank::SceneBank; use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; @@ -926,9 +945,14 @@ 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); assert_eq!(gfx.back[0], Color::WHITE.0); assert_eq!(gfx.back[9 * 10 + 9], Color::WHITE.0); + assert_eq!( + gfx.overlay().commands(), + &[OverlayCommand::DrawLine { x0: 0, y0: 0, x1: 9, y1: 9, color: Color::WHITE }] + ); } #[test] @@ -954,11 +978,53 @@ 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); // Border assert_eq!(gfx.back[2 * 10 + 2], Color::WHITE.0); // Fill assert_eq!(gfx.back[3 * 10 + 3], Color::BLACK.0); + assert_eq!(gfx.overlay().command_count(), 2); + } + + #[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); + + 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 new file mode 100644 index 00000000..6b9641ad --- /dev/null +++ b/crates/console/prometeu-drivers/src/gfx_overlay.rs @@ -0,0 +1,35 @@ +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() + } +} diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index d648356b..42d02f34 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -48,6 +48,7 @@ impl Default for Hardware { impl HardwareBridge for Hardware { fn begin_frame(&mut self) { + self.gfx.begin_overlay_frame(); self.frame_composer.begin_frame(); } diff --git a/crates/console/prometeu-drivers/src/lib.rs b/crates/console/prometeu-drivers/src/lib.rs index 9a088424..397e9a07 100644 --- a/crates/console/prometeu-drivers/src/lib.rs +++ b/crates/console/prometeu-drivers/src/lib.rs @@ -2,6 +2,7 @@ mod asset; mod audio; mod frame_composer; mod gfx; +mod gfx_overlay; pub mod hardware; mod memory_banks; mod pad; @@ -11,6 +12,7 @@ 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,