diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index d6541337..99d33cf1 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -14,7 +14,7 @@ use prometeu_hal::glyph::Glyph; use prometeu_hal::glyph_bank::{GlyphBank, TileSize}; use prometeu_hal::sample::Sample; use prometeu_hal::scene_bank::SceneBank; -use prometeu_hal::scene_layer::{MotionFactor, SceneLayer}; +use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; use prometeu_hal::sound_bank::SoundBank; use prometeu_hal::tile::Tile; use prometeu_hal::tilemap::TileMap; @@ -748,7 +748,7 @@ impl AssetManager { active: false, glyph_bank_id: 0, tile_size: TileSize::Size8, - motion_factor: MotionFactor { x: 1.0, y: 1.0 }, + parallax_factor: ParallaxFactor { x: 1.0, y: 1.0 }, tilemap: TileMap { width: 0, height: 0, tiles: Vec::new() }, }); let mut layers = layers; @@ -770,20 +770,20 @@ impl AssetManager { 32 => TileSize::Size32, other => return Err(format!("Invalid SCENE tile size: {}", other)), }; - let motion_factor_x = f32::from_le_bytes([ + let parallax_factor_x = f32::from_le_bytes([ buffer[offset + 4], buffer[offset + 5], buffer[offset + 6], buffer[offset + 7], ]); - let motion_factor_y = f32::from_le_bytes([ + let parallax_factor_y = f32::from_le_bytes([ buffer[offset + 8], buffer[offset + 9], buffer[offset + 10], buffer[offset + 11], ]); - if !motion_factor_x.is_finite() || !motion_factor_y.is_finite() { - return Err("Invalid SCENE motion_factor".to_string()); + if !parallax_factor_x.is_finite() || !parallax_factor_y.is_finite() { + return Err("Invalid SCENE parallax_factor".to_string()); } let width = u32::from_le_bytes([ @@ -847,7 +847,7 @@ impl AssetManager { active: (flags & 0b0000_0001) != 0, glyph_bank_id, tile_size, - motion_factor: MotionFactor { x: motion_factor_x, y: motion_factor_y }, + parallax_factor: ParallaxFactor { x: parallax_factor_x, y: parallax_factor_y }, tilemap: TileMap { width, height, tiles }, }; } @@ -1105,7 +1105,7 @@ mod tests { SCENE_PAYLOAD_MAGIC_V1, SCENE_PAYLOAD_VERSION_V1, }; use prometeu_hal::glyph::Glyph; - use prometeu_hal::scene_layer::{MotionFactor, SceneLayer}; + use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; use prometeu_hal::tile::Tile; use prometeu_hal::tilemap::TileMap; @@ -1144,11 +1144,11 @@ mod tests { fn test_scene() -> SceneBank { let make_layer = - |glyph_bank_id: u8, motion_x: f32, motion_y: f32, tile_size: TileSize| SceneLayer { + |glyph_bank_id: u8, parallax_x: f32, parallax_y: f32, tile_size: TileSize| SceneLayer { active: glyph_bank_id != 3, glyph_bank_id, tile_size, - motion_factor: MotionFactor { x: motion_x, y: motion_y }, + parallax_factor: ParallaxFactor { x: parallax_x, y: parallax_y }, tilemap: TileMap { width: 2, height: 2, @@ -1227,8 +1227,8 @@ mod tests { data.push(layer.glyph_bank_id); data.push(layer.tile_size as u8); data.push(0); - data.extend_from_slice(&layer.motion_factor.x.to_le_bytes()); - data.extend_from_slice(&layer.motion_factor.y.to_le_bytes()); + data.extend_from_slice(&layer.parallax_factor.x.to_le_bytes()); + data.extend_from_slice(&layer.parallax_factor.y.to_le_bytes()); data.extend_from_slice(&(layer.tilemap.width as u32).to_le_bytes()); data.extend_from_slice(&(layer.tilemap.height as u32).to_le_bytes()); data.extend_from_slice(&(layer.tilemap.tiles.len() as u32).to_le_bytes()); @@ -1359,7 +1359,7 @@ mod tests { let decoded = AssetManager::decode_scene_bank_from_buffer(&entry, &data).expect("scene"); assert_eq!(decoded.layers[1].glyph_bank_id, 1); - assert_eq!(decoded.layers[1].motion_factor.x, 0.5); + assert_eq!(decoded.layers[1].parallax_factor.x, 0.5); assert_eq!(decoded.layers[2].tile_size, TileSize::Size32); assert_eq!(decoded.layers[0].tilemap.tiles[1].flip_x, true); assert_eq!(decoded.layers[2].tilemap.tiles[2].flip_y, true); diff --git a/crates/console/prometeu-drivers/src/frame_composer.rs b/crates/console/prometeu-drivers/src/frame_composer.rs new file mode 100644 index 00000000..1a7b77cc --- /dev/null +++ b/crates/console/prometeu-drivers/src/frame_composer.rs @@ -0,0 +1,197 @@ +use crate::memory_banks::SceneBankPoolAccess; +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::sprite::Sprite; +use std::sync::Arc; + +const EMPTY_SPRITE: Sprite = Sprite { + glyph: Glyph { glyph_id: 0, palette_id: 0 }, + x: 0, + y: 0, + bank_id: 0, + active: false, + flip_x: false, + flip_y: false, + priority: 0, +}; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum SceneStatus { + #[default] + Unbound, +} + +#[derive(Clone, Debug)] +pub struct SpriteController { + sprites: [Sprite; 512], +} + +impl Default for SpriteController { + fn default() -> Self { + Self::new() + } +} + +impl SpriteController { + pub fn new() -> Self { + Self { sprites: [EMPTY_SPRITE; 512] } + } + + pub fn sprites(&self) -> &[Sprite; 512] { + &self.sprites + } + + pub fn sprites_mut(&mut self) -> &mut [Sprite; 512] { + &mut self.sprites + } +} + +pub struct FrameComposer { + scene_bank_pool: Arc, + viewport_width_px: usize, + viewport_height_px: usize, + active_scene_id: Option, + active_scene: Option>, + scene_status: SceneStatus, + camera_x_px: i32, + camera_y_px: i32, + cache: Option, + resolver: Option, + sprite_controller: SpriteController, +} + +impl FrameComposer { + pub fn new( + viewport_width_px: usize, + viewport_height_px: usize, + scene_bank_pool: Arc, + ) -> Self { + Self { + scene_bank_pool, + viewport_width_px, + viewport_height_px, + active_scene_id: None, + active_scene: None, + scene_status: SceneStatus::Unbound, + camera_x_px: 0, + camera_y_px: 0, + cache: None, + resolver: None, + sprite_controller: SpriteController::new(), + } + } + + pub fn viewport_size(&self) -> (usize, usize) { + (self.viewport_width_px, self.viewport_height_px) + } + + pub fn scene_bank_pool(&self) -> &Arc { + &self.scene_bank_pool + } + + pub fn scene_bank_slot(&self, slot: usize) -> Option> { + self.scene_bank_pool.scene_bank_slot(slot) + } + + pub fn scene_bank_slot_count(&self) -> usize { + self.scene_bank_pool.scene_bank_slot_count() + } + + pub fn active_scene_id(&self) -> Option { + self.active_scene_id + } + + pub fn active_scene(&self) -> Option<&Arc> { + self.active_scene.as_ref() + } + + pub fn scene_status(&self) -> SceneStatus { + self.scene_status + } + + pub fn camera(&self) -> (i32, i32) { + (self.camera_x_px, self.camera_y_px) + } + + pub fn cache(&self) -> Option<&SceneViewportCache> { + self.cache.as_ref() + } + + pub fn resolver(&self) -> Option<&SceneViewportResolver> { + self.resolver.as_ref() + } + + pub fn sprite_controller(&self) -> &SpriteController { + &self.sprite_controller + } + + pub fn sprite_controller_mut(&mut self) -> &mut SpriteController { + &mut self.sprite_controller + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::memory_banks::{MemoryBanks, SceneBankPoolInstaller}; + use prometeu_hal::glyph::Glyph; + use prometeu_hal::glyph_bank::TileSize; + use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; + use prometeu_hal::tile::Tile; + use prometeu_hal::tilemap::TileMap; + + fn make_scene() -> SceneBank { + let layer = SceneLayer { + active: true, + glyph_bank_id: 1, + tile_size: TileSize::Size8, + parallax_factor: ParallaxFactor { x: 1.0, y: 0.5 }, + tilemap: TileMap { + width: 2, + height: 2, + tiles: vec![ + Tile { + active: true, + glyph: Glyph { glyph_id: 9, palette_id: 1 }, + flip_x: false, + flip_y: false, + }; + 4 + ], + }, + }; + + SceneBank { layers: std::array::from_fn(|_| layer.clone()) } + } + + #[test] + fn frame_composer_starts_unbound_with_empty_owned_state() { + let frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new())); + + assert_eq!(frame_composer.viewport_size(), (320, 180)); + assert_eq!(frame_composer.active_scene_id(), None); + assert!(frame_composer.active_scene().is_none()); + assert_eq!(frame_composer.scene_status(), SceneStatus::Unbound); + assert_eq!(frame_composer.camera(), (0, 0)); + assert!(frame_composer.cache().is_none()); + assert!(frame_composer.resolver().is_none()); + assert_eq!(frame_composer.sprite_controller().sprites().len(), 512); + assert!(frame_composer.sprite_controller().sprites().iter().all(|sprite| !sprite.active)); + } + + #[test] + fn frame_composer_exposes_shared_scene_bank_access() { + let banks = Arc::new(MemoryBanks::new()); + banks.install_scene_bank(3, Arc::new(make_scene())); + + let frame_composer = FrameComposer::new(320, 180, banks); + let scene = + frame_composer.scene_bank_slot(3).expect("scene bank slot 3 should be resident"); + + assert_eq!(frame_composer.scene_bank_slot_count(), 16); + assert_eq!(scene.layers[0].tile_size, TileSize::Size8); + assert_eq!(scene.layers[0].parallax_factor.y, 0.5); + } +} diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index 26d09ebf..9462d8dc 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -822,7 +822,7 @@ mod tests { use crate::memory_banks::{GlyphBankPoolInstaller, MemoryBanks}; use prometeu_hal::glyph_bank::TileSize; use prometeu_hal::scene_bank::SceneBank; - use prometeu_hal::scene_layer::{MotionFactor, SceneLayer}; + use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; use prometeu_hal::scene_viewport_cache::SceneViewportCache; use prometeu_hal::scene_viewport_resolver::SceneViewportResolver; use prometeu_hal::tile::Tile; @@ -853,7 +853,7 @@ mod tests { active: true, glyph_bank_id, tile_size: TileSize::Size8, - motion_factor: MotionFactor { x: 1.0, y: 1.0 }, + parallax_factor: ParallaxFactor { x: 1.0, y: 1.0 }, tilemap: TileMap { width, height, @@ -875,7 +875,7 @@ mod tests { active: false, glyph_bank_id, tile_size: TileSize::Size8, - motion_factor: MotionFactor { x: 1.0, y: 1.0 }, + parallax_factor: ParallaxFactor { x: 1.0, y: 1.0 }, tilemap: TileMap { width, height, tiles: vec![Tile::default(); width * height] }, } } diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 3d7a2194..e1ef2d3f 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -1,9 +1,10 @@ use crate::asset::AssetManager; use crate::audio::Audio; +use crate::frame_composer::FrameComposer; use crate::gfx::Gfx; use crate::memory_banks::{ - GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolInstaller, - SoundBankPoolAccess, SoundBankPoolInstaller, + GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess, + SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller, }; use crate::pad::Pad; use crate::touch::Touch; @@ -26,6 +27,8 @@ use std::sync::Arc; pub struct Hardware { /// The Graphics Processing Unit (GPU). Handles drawing primitives, sprites, and tilemaps. pub gfx: Gfx, + /// Canonical frame orchestration owner for scene/camera/cache/resolver/sprites. + pub frame_composer: FrameComposer, /// The Sound Processing Unit (SPU). Manages sample playback and volume control. pub audio: Audio, /// The standard digital gamepad. Provides state for D-Pad, face buttons, and triggers. @@ -98,6 +101,11 @@ impl Hardware { Self::H, Arc::clone(&memory_banks) as Arc, ), + frame_composer: FrameComposer::new( + Self::W, + Self::H, + Arc::clone(&memory_banks) as Arc, + ), audio: Audio::new(Arc::clone(&memory_banks) as Arc), pad: Pad::default(), touch: Touch::default(), @@ -122,7 +130,7 @@ mod tests { use prometeu_hal::glyph::Glyph; use prometeu_hal::glyph_bank::{GlyphBank, TileSize}; use prometeu_hal::scene_bank::SceneBank; - use prometeu_hal::scene_layer::{MotionFactor, SceneLayer}; + use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; use prometeu_hal::scene_viewport_cache::SceneViewportCache; use prometeu_hal::scene_viewport_resolver::SceneViewportResolver; use prometeu_hal::tile::Tile; @@ -142,7 +150,7 @@ mod tests { active: true, glyph_bank_id: 0, tile_size: TileSize::Size8, - motion_factor: MotionFactor { x: 1.0, y: 1.0 }, + parallax_factor: ParallaxFactor { x: 1.0, y: 1.0 }, tilemap: TileMap { width: 4, height: 4, @@ -182,4 +190,20 @@ mod tests { assert_eq!(hardware.gfx.front_buffer()[0], Color::RED.raw()); } + + #[test] + fn hardware_constructs_frame_composer_with_shared_scene_bank_access() { + let banks = Arc::new(MemoryBanks::new()); + banks.install_scene_bank(2, Arc::new(make_scene())); + + let hardware = Hardware::new_with_memory_banks(banks); + let scene = hardware + .frame_composer + .scene_bank_slot(2) + .expect("scene bank slot 2 should be resident"); + + assert_eq!(hardware.frame_composer.viewport_size(), (Hardware::W, Hardware::H)); + assert_eq!(hardware.frame_composer.scene_bank_slot_count(), 16); + assert_eq!(scene.layers[0].tile_size, TileSize::Size8); + } } diff --git a/crates/console/prometeu-drivers/src/lib.rs b/crates/console/prometeu-drivers/src/lib.rs index f9d82bc5..9a088424 100644 --- a/crates/console/prometeu-drivers/src/lib.rs +++ b/crates/console/prometeu-drivers/src/lib.rs @@ -1,5 +1,6 @@ mod asset; mod audio; +mod frame_composer; mod gfx; pub mod hardware; mod memory_banks; @@ -8,6 +9,7 @@ mod touch; 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::memory_banks::{ GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess, diff --git a/crates/console/prometeu-hal/src/scene_bank.rs b/crates/console/prometeu-hal/src/scene_bank.rs index c5f4b296..6abb47d5 100644 --- a/crates/console/prometeu-hal/src/scene_bank.rs +++ b/crates/console/prometeu-hal/src/scene_bank.rs @@ -10,16 +10,16 @@ mod tests { use super::*; use crate::glyph::Glyph; use crate::glyph_bank::TileSize; - use crate::scene_layer::MotionFactor; + use crate::scene_layer::ParallaxFactor; use crate::tile::Tile; use crate::tilemap::TileMap; - fn layer(glyph_bank_id: u8, motion_x: f32, motion_y: f32, glyph_id: u16) -> SceneLayer { + fn layer(glyph_bank_id: u8, parallax_x: f32, parallax_y: f32, glyph_id: u16) -> SceneLayer { SceneLayer { active: true, glyph_bank_id, tile_size: TileSize::Size16, - motion_factor: MotionFactor { x: motion_x, y: motion_y }, + parallax_factor: ParallaxFactor { x: parallax_x, y: parallax_y }, tilemap: TileMap { width: 1, height: 1, diff --git a/crates/console/prometeu-hal/src/scene_layer.rs b/crates/console/prometeu-hal/src/scene_layer.rs index a7c448f3..90feec93 100644 --- a/crates/console/prometeu-hal/src/scene_layer.rs +++ b/crates/console/prometeu-hal/src/scene_layer.rs @@ -2,7 +2,7 @@ use crate::glyph_bank::TileSize; use crate::tilemap::TileMap; #[derive(Clone, Copy, Debug)] -pub struct MotionFactor { +pub struct ParallaxFactor { pub x: f32, pub y: f32, } @@ -12,7 +12,7 @@ pub struct SceneLayer { pub active: bool, pub glyph_bank_id: u8, pub tile_size: TileSize, - pub motion_factor: MotionFactor, + pub parallax_factor: ParallaxFactor, pub tilemap: TileMap, } @@ -23,12 +23,12 @@ mod tests { use crate::tile::Tile; #[test] - fn scene_layer_preserves_motion_factor_and_tilemap_ownership() { + fn scene_layer_preserves_parallax_factor_and_tilemap_ownership() { let layer = SceneLayer { active: true, glyph_bank_id: 7, tile_size: TileSize::Size16, - motion_factor: MotionFactor { x: 0.5, y: 0.75 }, + parallax_factor: ParallaxFactor { x: 0.5, y: 0.75 }, tilemap: TileMap { width: 2, height: 1, @@ -50,8 +50,8 @@ mod tests { }; assert_eq!(layer.glyph_bank_id, 7); - assert_eq!(layer.motion_factor.x, 0.5); - assert_eq!(layer.motion_factor.y, 0.75); + assert_eq!(layer.parallax_factor.x, 0.5); + assert_eq!(layer.parallax_factor.y, 0.75); assert_eq!(layer.tilemap.width, 2); assert_eq!(layer.tilemap.tiles[1].glyph.glyph_id, 22); assert!(layer.tilemap.tiles[1].flip_x); diff --git a/crates/console/prometeu-hal/src/scene_viewport_cache.rs b/crates/console/prometeu-hal/src/scene_viewport_cache.rs index c0c3bcdb..4c2ee614 100644 --- a/crates/console/prometeu-hal/src/scene_viewport_cache.rs +++ b/crates/console/prometeu-hal/src/scene_viewport_cache.rs @@ -270,7 +270,7 @@ mod tests { use super::*; use crate::glyph::Glyph; use crate::glyph_bank::TileSize; - use crate::scene_layer::MotionFactor; + use crate::scene_layer::ParallaxFactor; use crate::tile::Tile; use crate::tilemap::TileMap; @@ -295,7 +295,7 @@ mod tests { active: true, glyph_bank_id, tile_size: TileSize::Size16, - motion_factor: MotionFactor { x: 1.0, y: 1.0 }, + parallax_factor: ParallaxFactor { x: 1.0, y: 1.0 }, tilemap: TileMap { width: 4, height: 4, tiles }, } } diff --git a/crates/console/prometeu-hal/src/scene_viewport_resolver.rs b/crates/console/prometeu-hal/src/scene_viewport_resolver.rs index 41fad0f2..a9e99902 100644 --- a/crates/console/prometeu-hal/src/scene_viewport_resolver.rs +++ b/crates/console/prometeu-hal/src/scene_viewport_resolver.rs @@ -96,8 +96,8 @@ impl SceneViewportResolver { let layer_inputs: [(i32, i32, i32, i32, i32); 4] = std::array::from_fn(|i| { let layer = &scene.layers[i]; let tile_size_px = layer.tile_size as i32; - let layer_camera_x_px = ((camera_x_px as f32) * layer.motion_factor.x).floor() as i32; - let layer_camera_y_px = ((camera_y_px as f32) * layer.motion_factor.y).floor() as i32; + let layer_camera_x_px = ((camera_x_px as f32) * layer.parallax_factor.x).floor() as i32; + let layer_camera_y_px = ((camera_y_px as f32) * layer.parallax_factor.y).floor() as i32; let layer_center_x_px = layer_camera_x_px + self.viewport_width_px / 2; let layer_center_y_px = layer_camera_y_px + self.viewport_height_px / 2; ( @@ -388,14 +388,14 @@ mod tests { use super::*; use crate::glyph::Glyph; use crate::glyph_bank::TileSize; - use crate::scene_layer::{MotionFactor, SceneLayer}; + use crate::scene_layer::{ParallaxFactor, SceneLayer}; use crate::tile::Tile; use crate::tilemap::TileMap; fn make_layer( tile_size: TileSize, - motion_x: f32, - motion_y: f32, + parallax_x: f32, + parallax_y: f32, width: usize, height: usize, ) -> SceneLayer { @@ -413,7 +413,7 @@ mod tests { active: true, glyph_bank_id: 1, tile_size, - motion_factor: MotionFactor { x: motion_x, y: motion_y }, + parallax_factor: ParallaxFactor { x: parallax_x, y: parallax_y }, tilemap: TileMap { width, height, tiles }, } } @@ -443,7 +443,7 @@ mod tests { } #[test] - fn per_layer_copy_requests_follow_motion_factor() { + fn per_layer_copy_requests_follow_parallax_factor() { let scene = make_scene(); let mut resolver = SceneViewportResolver::new(320, 180, 25, 16, 12, 20);