241 lines
7.9 KiB
Rust
241 lines
7.9 KiB
Rust
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<MemoryBanks>) -> Self {
|
|
Self {
|
|
gfx: Gfx::new(
|
|
Self::W,
|
|
Self::H,
|
|
Arc::clone(&memory_banks) as Arc<dyn GlyphBankPoolAccess>,
|
|
),
|
|
frame_composer: FrameComposer::new(
|
|
Self::W,
|
|
Self::H,
|
|
Arc::clone(&memory_banks) as Arc<dyn SceneBankPoolAccess>,
|
|
),
|
|
audio: Audio::new(Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolAccess>),
|
|
pad: Pad::default(),
|
|
touch: Touch::default(),
|
|
assets: AssetManager::new(
|
|
vec![],
|
|
AssetsPayloadSource::empty(),
|
|
Arc::clone(&memory_banks) as Arc<dyn GlyphBankPoolInstaller>,
|
|
Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolInstaller>,
|
|
Arc::clone(&memory_banks) as Arc<dyn SceneBankPoolInstaller>,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
}
|