715 lines
22 KiB
Rust
715 lines
22 KiB
Rust
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::{CacheRefreshRequest, 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,
|
|
layer: 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,
|
|
Available {
|
|
scene_bank_id: usize,
|
|
},
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct SpriteController {
|
|
sprites: [Sprite; 512],
|
|
sprite_count: usize,
|
|
frame_counter: u64,
|
|
dropped_sprites: usize,
|
|
layer_buckets: [Vec<usize>; 4],
|
|
}
|
|
|
|
impl Default for SpriteController {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl SpriteController {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
sprites: [EMPTY_SPRITE; 512],
|
|
sprite_count: 0,
|
|
frame_counter: 0,
|
|
dropped_sprites: 0,
|
|
layer_buckets: std::array::from_fn(|_| Vec::with_capacity(128)),
|
|
}
|
|
}
|
|
|
|
pub fn sprites(&self) -> &[Sprite; 512] {
|
|
&self.sprites
|
|
}
|
|
|
|
pub fn sprites_mut(&mut self) -> &mut [Sprite; 512] {
|
|
&mut self.sprites
|
|
}
|
|
|
|
pub fn begin_frame(&mut self) {
|
|
self.frame_counter = self.frame_counter.wrapping_add(1);
|
|
self.sprite_count = 0;
|
|
self.dropped_sprites = 0;
|
|
for bucket in &mut self.layer_buckets {
|
|
bucket.clear();
|
|
}
|
|
}
|
|
|
|
pub fn emit_sprite(&mut self, mut sprite: Sprite) -> bool {
|
|
let Some(bucket) = self.layer_buckets.get_mut(sprite.layer as usize) else {
|
|
self.dropped_sprites += 1;
|
|
return false;
|
|
};
|
|
if self.sprite_count >= self.sprites.len() {
|
|
self.dropped_sprites += 1;
|
|
return false;
|
|
}
|
|
|
|
sprite.active = true;
|
|
let index = self.sprite_count;
|
|
self.sprites[index] = sprite;
|
|
self.sprite_count += 1;
|
|
bucket.push(index);
|
|
true
|
|
}
|
|
|
|
pub fn sprite_count(&self) -> usize {
|
|
self.sprite_count
|
|
}
|
|
|
|
pub fn frame_counter(&self) -> u64 {
|
|
self.frame_counter
|
|
}
|
|
|
|
pub fn dropped_sprites(&self) -> usize {
|
|
self.dropped_sprites
|
|
}
|
|
|
|
pub fn ordered_sprites(&self) -> Vec<Sprite> {
|
|
let mut ordered = Vec::with_capacity(self.sprite_count);
|
|
for bucket in &self.layer_buckets {
|
|
let mut indices = bucket.clone();
|
|
indices.sort_by_key(|&index| self.sprites[index].priority);
|
|
for index in indices {
|
|
ordered.push(self.sprites[index]);
|
|
}
|
|
}
|
|
ordered
|
|
}
|
|
}
|
|
|
|
pub struct FrameComposer {
|
|
scene_bank_pool: Arc<dyn SceneBankPoolAccess>,
|
|
viewport_width_px: usize,
|
|
viewport_height_px: usize,
|
|
active_scene_id: Option<usize>,
|
|
active_scene: Option<Arc<SceneBank>>,
|
|
scene_status: SceneStatus,
|
|
camera_x_px: i32,
|
|
camera_y_px: i32,
|
|
cache: Option<SceneViewportCache>,
|
|
resolver: Option<SceneViewportResolver>,
|
|
sprite_controller: SpriteController,
|
|
}
|
|
|
|
impl FrameComposer {
|
|
pub fn new(
|
|
viewport_width_px: usize,
|
|
viewport_height_px: usize,
|
|
scene_bank_pool: Arc<dyn SceneBankPoolAccess>,
|
|
) -> 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<dyn SceneBankPoolAccess> {
|
|
&self.scene_bank_pool
|
|
}
|
|
|
|
pub fn scene_bank_slot(&self, slot: usize) -> Option<Arc<SceneBank>> {
|
|
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<usize> {
|
|
self.active_scene_id
|
|
}
|
|
|
|
pub fn active_scene(&self) -> Option<&Arc<SceneBank>> {
|
|
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 bind_scene(&mut self, scene_bank_id: usize) -> bool {
|
|
let Some(scene) = self.scene_bank_pool.scene_bank_slot(scene_bank_id) else {
|
|
self.unbind_scene();
|
|
return false;
|
|
};
|
|
|
|
let (cache, resolver) =
|
|
Self::build_scene_runtime(self.viewport_width_px, self.viewport_height_px, &scene);
|
|
self.active_scene_id = Some(scene_bank_id);
|
|
self.active_scene = Some(scene);
|
|
self.scene_status = SceneStatus::Available { scene_bank_id };
|
|
self.cache = Some(cache);
|
|
self.resolver = Some(resolver);
|
|
true
|
|
}
|
|
|
|
pub fn unbind_scene(&mut self) {
|
|
self.active_scene_id = None;
|
|
self.active_scene = None;
|
|
self.scene_status = SceneStatus::Unbound;
|
|
self.cache = None;
|
|
self.resolver = None;
|
|
}
|
|
|
|
pub fn set_camera(&mut self, x: i32, y: i32) {
|
|
self.camera_x_px = x;
|
|
self.camera_y_px = y;
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pub fn begin_frame(&mut self) {
|
|
self.sprite_controller.begin_frame();
|
|
}
|
|
|
|
pub fn emit_sprite(&mut self, sprite: Sprite) -> bool {
|
|
self.sprite_controller.emit_sprite(sprite)
|
|
}
|
|
|
|
pub fn ordered_sprites(&self) -> Vec<Sprite> {
|
|
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,
|
|
scene: &SceneBank,
|
|
) -> (SceneViewportCache, SceneViewportResolver) {
|
|
let min_tile_px =
|
|
scene.layers.iter().map(|layer| layer.tile_size as usize).min().unwrap_or(8);
|
|
let cache_width_tiles = viewport_width_px.div_ceil(min_tile_px) + 5;
|
|
let cache_height_tiles = viewport_height_px.div_ceil(min_tile_px) + 4;
|
|
let hysteresis_safe_px = min_tile_px.saturating_sub(4) as i32;
|
|
let hysteresis_trigger_px = (min_tile_px + 4) as i32;
|
|
|
|
(
|
|
SceneViewportCache::new(scene, cache_width_tiles, cache_height_tiles),
|
|
SceneViewportResolver::new(
|
|
viewport_width_px as i32,
|
|
viewport_height_px as i32,
|
|
cache_width_tiles,
|
|
cache_height_tiles,
|
|
hysteresis_safe_px,
|
|
hysteresis_trigger_px,
|
|
),
|
|
)
|
|
}
|
|
|
|
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::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,
|
|
tile_size,
|
|
parallax_factor: ParallaxFactor { x: 1.0, y: 0.5 },
|
|
tilemap: TileMap {
|
|
width: 2,
|
|
height: 2,
|
|
tiles: vec![
|
|
Tile {
|
|
active: true,
|
|
glyph: Glyph { glyph_id: 0, palette_id },
|
|
flip_x: false,
|
|
flip_y: false,
|
|
};
|
|
4
|
|
],
|
|
},
|
|
};
|
|
|
|
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()));
|
|
|
|
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_eq!(frame_composer.sprite_controller().sprite_count(), 0);
|
|
assert_eq!(frame_composer.sprite_controller().dropped_sprites(), 0);
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
|
|
#[test]
|
|
fn bind_scene_stores_scene_identity_and_shared_reference() {
|
|
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 mut frame_composer = FrameComposer::new(320, 180, banks);
|
|
|
|
assert!(frame_composer.bind_scene(3));
|
|
|
|
assert_eq!(frame_composer.active_scene_id(), Some(3));
|
|
assert!(Arc::ptr_eq(
|
|
frame_composer.active_scene().expect("active scene should exist"),
|
|
&expected_scene,
|
|
));
|
|
assert_eq!(frame_composer.scene_status(), SceneStatus::Available { scene_bank_id: 3 });
|
|
assert!(frame_composer.cache().is_some());
|
|
assert!(frame_composer.resolver().is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn unbind_scene_clears_scene_and_cache_state() {
|
|
let banks = Arc::new(MemoryBanks::new());
|
|
banks.install_scene_bank(1, Arc::new(make_scene()));
|
|
|
|
let mut frame_composer = FrameComposer::new(320, 180, banks);
|
|
assert!(frame_composer.bind_scene(1));
|
|
|
|
frame_composer.unbind_scene();
|
|
|
|
assert_eq!(frame_composer.active_scene_id(), None);
|
|
assert!(frame_composer.active_scene().is_none());
|
|
assert_eq!(frame_composer.scene_status(), SceneStatus::Unbound);
|
|
assert!(frame_composer.cache().is_none());
|
|
assert!(frame_composer.resolver().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn set_camera_stores_top_left_pixel_coordinates() {
|
|
let mut frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new()));
|
|
|
|
frame_composer.set_camera(-12, 48);
|
|
|
|
assert_eq!(frame_composer.camera(), (-12, 48));
|
|
}
|
|
|
|
#[test]
|
|
fn bind_scene_derives_cache_and_resolver_from_eight_pixel_layers() {
|
|
let banks = Arc::new(MemoryBanks::new());
|
|
banks.install_scene_bank(0, Arc::new(make_scene()));
|
|
|
|
let mut frame_composer = FrameComposer::new(320, 180, banks);
|
|
assert!(frame_composer.bind_scene(0));
|
|
|
|
let cache = frame_composer.cache().expect("cache should exist for bound scene");
|
|
assert_eq!((cache.width(), cache.height()), (45, 27));
|
|
}
|
|
|
|
#[test]
|
|
fn missing_scene_binding_falls_back_to_no_scene_state() {
|
|
let mut frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new()));
|
|
|
|
assert!(!frame_composer.bind_scene(7));
|
|
|
|
assert_eq!(frame_composer.scene_status(), SceneStatus::Unbound);
|
|
assert!(frame_composer.cache().is_none());
|
|
assert!(frame_composer.resolver().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn sprite_controller_begin_frame_resets_sprite_count_and_buckets() {
|
|
let mut controller = SpriteController::new();
|
|
let emitted = controller.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 1, palette_id: 2 },
|
|
x: 4,
|
|
y: 5,
|
|
layer: 2,
|
|
bank_id: 3,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 1,
|
|
});
|
|
assert!(emitted);
|
|
|
|
controller.begin_frame();
|
|
|
|
assert_eq!(controller.frame_counter(), 1);
|
|
assert_eq!(controller.sprite_count(), 0);
|
|
assert_eq!(controller.dropped_sprites(), 0);
|
|
assert!(controller.ordered_sprites().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn sprite_controller_orders_by_layer_then_priority_then_fifo() {
|
|
let mut controller = SpriteController::new();
|
|
controller.begin_frame();
|
|
|
|
assert!(controller.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 10, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 1,
|
|
bank_id: 0,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 2,
|
|
}));
|
|
assert!(controller.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 11, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 0,
|
|
bank_id: 0,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 3,
|
|
}));
|
|
assert!(controller.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 12, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 1,
|
|
bank_id: 0,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 1,
|
|
}));
|
|
assert!(controller.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 13, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 1,
|
|
bank_id: 0,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 2,
|
|
}));
|
|
|
|
let ordered = controller.ordered_sprites();
|
|
let ordered_ids: Vec<u16> = ordered.iter().map(|sprite| sprite.glyph.glyph_id).collect();
|
|
|
|
assert_eq!(ordered_ids, vec![11, 12, 10, 13]);
|
|
assert!(ordered.iter().all(|sprite| sprite.active));
|
|
}
|
|
|
|
#[test]
|
|
fn sprite_controller_drops_overflow_without_panicking() {
|
|
let mut controller = SpriteController::new();
|
|
controller.begin_frame();
|
|
|
|
for glyph_id in 0..512 {
|
|
assert!(controller.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 0,
|
|
bank_id: 0,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 0,
|
|
}));
|
|
}
|
|
|
|
let overflowed = controller.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 999, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 0,
|
|
bank_id: 0,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 0,
|
|
});
|
|
|
|
assert!(!overflowed);
|
|
assert_eq!(controller.sprite_count(), 512);
|
|
assert_eq!(controller.dropped_sprites(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_composer_emits_ordered_sprites_for_rendering() {
|
|
let mut frame_composer = FrameComposer::new(320, 180, Arc::new(MemoryBanks::new()));
|
|
frame_composer.begin_frame();
|
|
|
|
assert!(frame_composer.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 21, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 2,
|
|
bank_id: 1,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 1,
|
|
}));
|
|
assert!(frame_composer.emit_sprite(Sprite {
|
|
glyph: Glyph { glyph_id: 20, palette_id: 0 },
|
|
x: 0,
|
|
y: 0,
|
|
layer: 1,
|
|
bank_id: 1,
|
|
active: false,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
priority: 0,
|
|
}));
|
|
|
|
let ordered = frame_composer.ordered_sprites();
|
|
|
|
assert_eq!(ordered.len(), 2);
|
|
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<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());
|
|
}
|
|
}
|