use crate::asset::AssetManager; use crate::audio::Audio; use crate::frame_composer::FrameComposer; use crate::gfx::Gfx; use crate::memory_banks::{ GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess, SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller, }; use crate::pad::Pad; use crate::touch::Touch; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::sprite::Sprite; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; use std::sync::Arc; /// Aggregate structure for all virtual hardware peripherals. /// /// This struct represents the "Mainboard" of the PROMETEU console. /// It acts as a container for all hardware subsystems. In the Prometeu /// architecture, hardware is decoupled from the OS and VM, allowing /// for easier testing and different host implementations (Desktop, Web, etc.). /// /// ### Console Specifications: /// - **Resolution**: 320x180 (16:9 Aspect Ratio). /// - **Color Depth**: RGB565 (16-bit). /// - **Audio**: Stereo, Command-based mixing. /// - **Input**: 12-button Digital Gamepad + Absolute Touch/Mouse. 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. pub pad: Pad, /// The absolute pointer input device (Mouse/Touchscreen). pub touch: Touch, /// The Asset Management system (DMA). Handles loading data into VRAM (GlyphBanks) and ARAM (SoundBanks). pub assets: AssetManager, } impl Default for Hardware { fn default() -> Self { Self::new() } } impl HardwareBridge for Hardware { fn begin_frame(&mut self) { self.gfx.begin_overlay_frame(); self.frame_composer.begin_frame(); } fn bind_scene(&mut self, scene_bank_id: usize) -> bool { self.frame_composer.bind_scene(scene_bank_id) } fn unbind_scene(&mut self) { self.frame_composer.unbind_scene(); } fn set_camera(&mut self, x: i32, y: i32) { self.frame_composer.set_camera(x, y); } fn emit_sprite(&mut self, sprite: Sprite) -> bool { self.frame_composer.emit_sprite(sprite) } 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 { self.gfx.glyph_banks.glyph_bank_slot(bank_id).is_some() } fn gfx(&self) -> &dyn GfxBridge { &self.gfx } fn gfx_mut(&mut self) -> &mut dyn GfxBridge { &mut self.gfx } fn audio(&self) -> &dyn AudioBridge { &self.audio } fn audio_mut(&mut self) -> &mut dyn AudioBridge { &mut self.audio } fn pad(&self) -> &dyn PadBridge { &self.pad } fn pad_mut(&mut self) -> &mut dyn PadBridge { &mut self.pad } fn touch(&self) -> &dyn TouchBridge { &self.touch } fn touch_mut(&mut self) -> &mut dyn TouchBridge { &mut self.touch } fn assets(&self) -> &dyn AssetBridge { &self.assets } fn assets_mut(&mut self) -> &mut dyn AssetBridge { &mut self.assets } } impl Hardware { /// Internal hardware width in pixels. pub const W: usize = 320; /// Internal hardware height in pixels. pub const H: usize = 180; /// Creates a fresh hardware instance with default settings. pub fn new() -> Self { Self::new_with_memory_banks(Arc::new(MemoryBanks::new())) } /// Creates hardware with explicit shared bank ownership. pub fn new_with_memory_banks(memory_banks: Arc) -> Self { Self { gfx: Gfx::new( Self::W, 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(), assets: AssetManager::new( vec![], AssetsPayloadSource::empty(), Arc::clone(&memory_banks) as Arc, Arc::clone(&memory_banks) as Arc, Arc::clone(&memory_banks) as Arc, ), } } } #[cfg(test)] mod tests { use super::*; use crate::memory_banks::{ GlyphBankPoolInstaller, SceneBankPoolAccess, SceneBankPoolInstaller, }; use prometeu_hal::color::Color; use prometeu_hal::glyph::Glyph; use prometeu_hal::glyph_bank::{GlyphBank, TileSize}; use prometeu_hal::scene_bank::SceneBank; 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; use prometeu_hal::tilemap::TileMap; fn make_glyph_bank() -> GlyphBank { let mut bank = GlyphBank::new(TileSize::Size8, 8, 8); bank.palettes[0][1] = Color::RED; for pixel in &mut bank.pixel_indices { *pixel = 1; } bank } fn make_scene() -> SceneBank { let layer = SceneLayer { active: true, glyph_bank_id: 0, tile_size: TileSize::Size8, parallax_factor: ParallaxFactor { x: 1.0, y: 1.0 }, tilemap: TileMap { width: 4, height: 4, tiles: vec![ Tile { active: true, glyph: Glyph { glyph_id: 0, palette_id: 0 }, flip_x: false, flip_y: false, }; 16 ], }, }; SceneBank { layers: std::array::from_fn(|_| layer.clone()) } } #[test] fn hardware_can_render_scene_from_shared_scene_bank_pipeline() { let banks = Arc::new(MemoryBanks::new()); banks.install_glyph_bank(0, Arc::new(make_glyph_bank())); banks.install_scene_bank(0, Arc::new(make_scene())); let mut hardware = Hardware::new_with_memory_banks(Arc::clone(&banks)); let scene = banks.scene_bank_slot(0).expect("scene bank slot 0 should be resident"); let mut cache = SceneViewportCache::new(&scene, 4, 4); cache.materialize_all_layers(&scene); let mut resolver = SceneViewportResolver::new(16, 16, 4, 4, 12, 20); let update = resolver.update(&scene, 0, 0); hardware.gfx.scene_fade_level = 31; hardware.gfx.hud_fade_level = 31; hardware.gfx.render_scene_from_cache(&cache, &update); hardware.gfx.present(); 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); } }