diff --git a/crates/console/prometeu-drivers/src/frame_composer.rs b/crates/console/prometeu-drivers/src/frame_composer.rs index 63930e94..378423c4 100644 --- a/crates/console/prometeu-drivers/src/frame_composer.rs +++ b/crates/console/prometeu-drivers/src/frame_composer.rs @@ -1,8 +1,9 @@ use crate::memory_banks::SceneBankPoolAccess; +use prometeu_hal::GfxBridge; use prometeu_hal::glyph::Glyph; use prometeu_hal::scene_bank::SceneBank; use prometeu_hal::scene_viewport_cache::SceneViewportCache; -use prometeu_hal::scene_viewport_resolver::SceneViewportResolver; +use prometeu_hal::scene_viewport_resolver::{CacheRefreshRequest, SceneViewportResolver}; use prometeu_hal::sprite::Sprite; use std::sync::Arc; @@ -22,7 +23,9 @@ const EMPTY_SPRITE: Sprite = Sprite { pub enum SceneStatus { #[default] Unbound, - Available { scene_bank_id: usize }, + Available { + scene_bank_id: usize, + }, } #[derive(Clone, Debug)] @@ -235,6 +238,22 @@ impl FrameComposer { self.sprite_controller.ordered_sprites() } + pub fn render_frame(&mut self, gfx: &mut dyn GfxBridge) { + let ordered_sprites = self.ordered_sprites(); + gfx.load_frame_sprites(&ordered_sprites); + + if let (Some(scene), Some(cache), Some(resolver)) = + (self.active_scene.as_deref(), self.cache.as_mut(), self.resolver.as_mut()) + { + let update = resolver.update(scene, self.camera_x_px, self.camera_y_px); + Self::apply_refresh_requests(cache, scene, &update.refresh_requests); + gfx.render_scene_from_cache(cache, &update); + return; + } + + gfx.render_all(); + } + fn build_scene_runtime( viewport_width_px: usize, viewport_height_px: usize, @@ -259,22 +278,57 @@ impl FrameComposer { ), ) } + + fn apply_refresh_requests( + cache: &mut SceneViewportCache, + scene: &SceneBank, + refresh_requests: &[CacheRefreshRequest], + ) { + for request in refresh_requests { + match *request { + CacheRefreshRequest::InvalidateLayer { layer_index } => { + cache.layers[layer_index].invalidate_all(); + } + CacheRefreshRequest::RefreshLine { layer_index, cache_y } => { + cache.refresh_layer_line(scene, layer_index, cache_y); + } + CacheRefreshRequest::RefreshColumn { layer_index, cache_x } => { + cache.refresh_layer_column(scene, layer_index, cache_x); + } + CacheRefreshRequest::RefreshRegion { layer_index, region } => { + cache.refresh_layer_region(scene, layer_index, region); + } + } + } + } } #[cfg(test)] mod tests { use super::*; - use crate::memory_banks::{MemoryBanks, SceneBankPoolInstaller}; - use prometeu_hal::glyph_bank::TileSize; + use crate::gfx::Gfx; + use crate::memory_banks::{ + GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolInstaller, + }; + use prometeu_hal::color::Color; + use prometeu_hal::glyph_bank::{GlyphBank, TileSize}; use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; use prometeu_hal::tile::Tile; use prometeu_hal::tilemap::TileMap; fn make_scene() -> SceneBank { + make_scene_with_palette(1, 1, TileSize::Size8) + } + + fn make_scene_with_palette( + glyph_bank_id: u8, + palette_id: u8, + tile_size: TileSize, + ) -> SceneBank { let layer = SceneLayer { active: true, - glyph_bank_id: 1, - tile_size: TileSize::Size8, + glyph_bank_id, + tile_size, parallax_factor: ParallaxFactor { x: 1.0, y: 0.5 }, tilemap: TileMap { width: 2, @@ -282,7 +336,7 @@ mod tests { tiles: vec![ Tile { active: true, - glyph: Glyph { glyph_id: 9, palette_id: 1 }, + glyph: Glyph { glyph_id: 0, palette_id }, flip_x: false, flip_y: false, }; @@ -294,6 +348,16 @@ mod tests { SceneBank { layers: std::array::from_fn(|_| layer.clone()) } } + fn make_glyph_bank(tile_size: TileSize, palette_id: u8, color: Color) -> GlyphBank { + let size = tile_size as usize; + let mut bank = GlyphBank::new(tile_size, size, size); + bank.palettes[palette_id as usize][1] = color; + for pixel in &mut bank.pixel_indices { + *pixel = 1; + } + bank + } + #[test] fn frame_composer_starts_unbound_with_empty_owned_state() { let frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new())); @@ -329,7 +393,8 @@ mod tests { let banks = Arc::new(MemoryBanks::new()); banks.install_scene_bank(3, Arc::new(make_scene())); - let expected_scene = banks.scene_bank_slot(3).expect("scene bank slot 3 should be resident"); + let expected_scene = + banks.scene_bank_slot(3).expect("scene bank slot 3 should be resident"); let mut frame_composer = FrameComposer::new(320, 180, banks); assert!(frame_composer.bind_scene(3)); @@ -544,4 +609,106 @@ mod tests { assert_eq!(ordered[0].glyph.glyph_id, 20); assert_eq!(ordered[1].glyph.glyph_id, 21); } + + #[test] + fn render_frame_without_scene_uses_sprite_only_path() { + let banks = Arc::new(MemoryBanks::new()); + banks.install_glyph_bank(1, Arc::new(make_glyph_bank(TileSize::Size8, 3, Color::WHITE))); + + let mut frame_composer = + FrameComposer::new(16, 16, Arc::clone(&banks) as Arc); + frame_composer.begin_frame(); + assert!(frame_composer.emit_sprite(Sprite { + glyph: Glyph { glyph_id: 0, palette_id: 3 }, + x: 0, + y: 0, + layer: 0, + bank_id: 1, + active: false, + flip_x: false, + flip_y: false, + priority: 0, + })); + + let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); + gfx.scene_fade_level = 31; + gfx.hud_fade_level = 31; + + frame_composer.render_frame(&mut gfx); + gfx.present(); + + assert_eq!(gfx.front_buffer()[0], Color::WHITE.raw()); + } + + #[test] + fn render_frame_with_scene_applies_refreshes_before_composition() { + let banks = Arc::new(MemoryBanks::new()); + banks.install_glyph_bank(0, Arc::new(make_glyph_bank(TileSize::Size8, 2, Color::BLUE))); + banks.install_scene_bank(0, Arc::new(make_scene_with_palette(0, 2, TileSize::Size8))); + + let mut frame_composer = + FrameComposer::new(16, 16, Arc::clone(&banks) as Arc); + assert!(frame_composer.bind_scene(0)); + + let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); + gfx.scene_fade_level = 31; + gfx.hud_fade_level = 31; + + frame_composer.render_frame(&mut gfx); + gfx.present(); + + assert!( + frame_composer + .cache() + .expect("cache should exist") + .layers + .iter() + .all(|layer| layer.valid) + ); + assert_eq!(gfx.front_buffer()[0], Color::BLUE.raw()); + } + + #[test] + fn render_frame_survives_scene_transition_through_unbind_and_rebind() { + let banks = Arc::new(MemoryBanks::new()); + banks.install_glyph_bank(0, Arc::new(make_glyph_bank(TileSize::Size8, 1, Color::RED))); + banks.install_glyph_bank(1, Arc::new(make_glyph_bank(TileSize::Size8, 2, Color::BLUE))); + banks.install_glyph_bank(2, Arc::new(make_glyph_bank(TileSize::Size8, 3, Color::WHITE))); + banks.install_scene_bank(0, Arc::new(make_scene_with_palette(0, 1, TileSize::Size8))); + banks.install_scene_bank(1, Arc::new(make_scene_with_palette(1, 2, TileSize::Size8))); + + let mut frame_composer = + FrameComposer::new(16, 16, Arc::clone(&banks) as Arc); + let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc); + gfx.scene_fade_level = 31; + gfx.hud_fade_level = 31; + + assert!(frame_composer.bind_scene(0)); + frame_composer.render_frame(&mut gfx); + gfx.present(); + assert_eq!(gfx.front_buffer()[0], Color::RED.raw()); + + frame_composer.unbind_scene(); + frame_composer.begin_frame(); + assert!(frame_composer.emit_sprite(Sprite { + glyph: Glyph { glyph_id: 0, palette_id: 3 }, + x: 0, + y: 0, + layer: 0, + bank_id: 2, + active: false, + flip_x: false, + flip_y: false, + priority: 0, + })); + frame_composer.render_frame(&mut gfx); + gfx.present(); + assert_eq!(gfx.front_buffer()[0], Color::WHITE.raw()); + + frame_composer.begin_frame(); + assert!(frame_composer.bind_scene(1)); + frame_composer.render_frame(&mut gfx); + gfx.present(); + assert_eq!(gfx.front_buffer()[0], Color::BLUE.raw()); + } }