implements PLN-0020

This commit is contained in:
bQUARKz 2026-04-17 13:28:34 +01:00
parent 3931e86b41
commit 5ef43045bc
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8

View File

@ -1,8 +1,9 @@
use crate::memory_banks::SceneBankPoolAccess; use crate::memory_banks::SceneBankPoolAccess;
use prometeu_hal::GfxBridge;
use prometeu_hal::glyph::Glyph; use prometeu_hal::glyph::Glyph;
use prometeu_hal::scene_bank::SceneBank; use prometeu_hal::scene_bank::SceneBank;
use prometeu_hal::scene_viewport_cache::SceneViewportCache; 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 prometeu_hal::sprite::Sprite;
use std::sync::Arc; use std::sync::Arc;
@ -22,7 +23,9 @@ const EMPTY_SPRITE: Sprite = Sprite {
pub enum SceneStatus { pub enum SceneStatus {
#[default] #[default]
Unbound, Unbound,
Available { scene_bank_id: usize }, Available {
scene_bank_id: usize,
},
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -235,6 +238,22 @@ impl FrameComposer {
self.sprite_controller.ordered_sprites() 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( fn build_scene_runtime(
viewport_width_px: usize, viewport_width_px: usize,
viewport_height_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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::memory_banks::{MemoryBanks, SceneBankPoolInstaller}; use crate::gfx::Gfx;
use prometeu_hal::glyph_bank::TileSize; 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::scene_layer::{ParallaxFactor, SceneLayer};
use prometeu_hal::tile::Tile; use prometeu_hal::tile::Tile;
use prometeu_hal::tilemap::TileMap; use prometeu_hal::tilemap::TileMap;
fn make_scene() -> SceneBank { 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 { let layer = SceneLayer {
active: true, active: true,
glyph_bank_id: 1, glyph_bank_id,
tile_size: TileSize::Size8, tile_size,
parallax_factor: ParallaxFactor { x: 1.0, y: 0.5 }, parallax_factor: ParallaxFactor { x: 1.0, y: 0.5 },
tilemap: TileMap { tilemap: TileMap {
width: 2, width: 2,
@ -282,7 +336,7 @@ mod tests {
tiles: vec![ tiles: vec![
Tile { Tile {
active: true, active: true,
glyph: Glyph { glyph_id: 9, palette_id: 1 }, glyph: Glyph { glyph_id: 0, palette_id },
flip_x: false, flip_x: false,
flip_y: false, flip_y: false,
}; };
@ -294,6 +348,16 @@ mod tests {
SceneBank { layers: std::array::from_fn(|_| layer.clone()) } 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] #[test]
fn frame_composer_starts_unbound_with_empty_owned_state() { fn frame_composer_starts_unbound_with_empty_owned_state() {
let frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new())); let frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new()));
@ -329,7 +393,8 @@ mod tests {
let banks = Arc::new(MemoryBanks::new()); let banks = Arc::new(MemoryBanks::new());
banks.install_scene_bank(3, Arc::new(make_scene())); 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); let mut frame_composer = FrameComposer::new(320, 180, banks);
assert!(frame_composer.bind_scene(3)); assert!(frame_composer.bind_scene(3));
@ -544,4 +609,106 @@ mod tests {
assert_eq!(ordered[0].glyph.glyph_id, 20); assert_eq!(ordered[0].glyph.glyph_id, 20);
assert_eq!(ordered[1].glyph.glyph_id, 21); 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<dyn SceneBankPoolAccess>);
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<dyn GlyphBankPoolAccess>);
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<dyn SceneBankPoolAccess>);
assert!(frame_composer.bind_scene(0));
let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc<dyn GlyphBankPoolAccess>);
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<dyn SceneBankPoolAccess>);
let mut gfx = Gfx::new(16, 16, Arc::clone(&banks) as Arc<dyn GlyphBankPoolAccess>);
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());
}
} }