dev/render-all-scene-cache-and-camera-integration #16

Merged
bquarkz merged 21 commits from dev/render-all-scene-cache-and-camera-integration into master 2026-04-18 16:20:50 +00:00
2 changed files with 170 additions and 28 deletions
Showing only changes of commit f4260d0cf4 - Show all commits

View File

@ -79,6 +79,21 @@ pub struct Gfx {
const GLYPH_UNKNOWN: [u8; 5] = [0x7, 0x7, 0x7, 0x7, 0x7]; const GLYPH_UNKNOWN: [u8; 5] = [0x7, 0x7, 0x7, 0x7, 0x7];
struct RenderTarget<'a> {
back: &'a mut [u16],
screen_w: usize,
screen_h: usize,
}
#[derive(Clone, Copy)]
struct CachedTileDraw<'a> {
x: i32,
y: i32,
entry: CachedTileEntry,
bank: &'a GlyphBank,
tile_size: prometeu_hal::glyph_bank::TileSize,
}
#[inline] #[inline]
fn glyph_for_char(c: char) -> &'static [u8; 5] { fn glyph_for_char(c: char) -> &'static [u8; 5] {
match c.to_ascii_uppercase() { match c.to_ascii_uppercase() {
@ -629,6 +644,7 @@ impl Gfx {
request: &LayerCopyRequest, request: &LayerCopyRequest,
glyph_banks: &dyn GlyphBankPoolAccess, glyph_banks: &dyn GlyphBankPoolAccess,
) { ) {
let mut target = RenderTarget { back, screen_w, screen_h };
let layer_cache = &cache.layers[request.layer_index]; let layer_cache = &cache.layers[request.layer_index];
if !layer_cache.valid { if !layer_cache.valid {
return; return;
@ -657,52 +673,43 @@ impl Gfx {
} }
Self::draw_cached_tile_pixels( Self::draw_cached_tile_pixels(
back, &mut target,
screen_w, CachedTileDraw {
screen_h, x: screen_tile_x,
screen_tile_x, y: screen_tile_y,
screen_tile_y, entry,
entry, bank: &bank,
&bank, tile_size: request.tile_size,
request.tile_size, },
); );
} }
} }
} }
fn draw_cached_tile_pixels( fn draw_cached_tile_pixels(target: &mut RenderTarget<'_>, tile: CachedTileDraw<'_>) {
back: &mut [u16], let size = tile.tile_size as usize;
screen_w: usize,
screen_h: usize,
x: i32,
y: i32,
entry: CachedTileEntry,
bank: &GlyphBank,
tile_size: prometeu_hal::glyph_bank::TileSize,
) {
let size = tile_size as usize;
for local_y in 0..size { for local_y in 0..size {
let world_y = y + local_y as i32; let world_y = tile.y + local_y as i32;
if world_y < 0 || world_y >= screen_h as i32 { if world_y < 0 || world_y >= target.screen_h as i32 {
continue; continue;
} }
for local_x in 0..size { for local_x in 0..size {
let world_x = x + local_x as i32; let world_x = tile.x + local_x as i32;
if world_x < 0 || world_x >= screen_w as i32 { if world_x < 0 || world_x >= target.screen_w as i32 {
continue; continue;
} }
let fetch_x = if entry.flip_x() { size - 1 - local_x } else { local_x }; let fetch_x = if tile.entry.flip_x() { size - 1 - local_x } else { local_x };
let fetch_y = if entry.flip_y() { size - 1 - local_y } else { local_y }; let fetch_y = if tile.entry.flip_y() { size - 1 - local_y } else { local_y };
let px_index = bank.get_pixel_index(entry.glyph_id, fetch_x, fetch_y); let px_index = tile.bank.get_pixel_index(tile.entry.glyph_id, fetch_x, fetch_y);
if px_index == 0 { if px_index == 0 {
continue; continue;
} }
let color = bank.resolve_color(entry.palette_id, px_index); let color = tile.bank.resolve_color(tile.entry.palette_id, px_index);
back[world_y as usize * screen_w + world_x as usize] = color.raw(); target.back[world_y as usize * target.screen_w + world_x as usize] = color.raw();
} }
} }
} }

View File

@ -325,6 +325,34 @@ mod tests {
assert_eq!(cache.layers[0].ring_origin(), (1, 1)); assert_eq!(cache.layers[0].ring_origin(), (1, 1));
} }
#[test]
fn layer_cache_wraps_ring_origin_for_negative_and_large_movements() {
let scene = make_scene();
let mut cache = SceneViewportCache::new(&scene, 3, 3);
cache.move_layer_window_by(0, -1, -4);
assert_eq!(cache.layers[0].logical_origin(), (-1, -4));
assert_eq!(cache.layers[0].ring_origin(), (2, 2));
cache.move_layer_window_by(0, 7, 8);
assert_eq!(cache.layers[0].logical_origin(), (6, 4));
assert_eq!(cache.layers[0].ring_origin(), (0, 1));
}
#[test]
fn move_window_to_matches_incremental_ring_movement() {
let scene = make_scene();
let mut direct = SceneViewportCache::new(&scene, 4, 4);
let mut incremental = SceneViewportCache::new(&scene, 4, 4);
direct.move_layer_window_to(0, 9, -6);
incremental.move_layer_window_by(0, 5, -2);
incremental.move_layer_window_by(0, 4, -4);
assert_eq!(direct.layers[0].logical_origin(), incremental.layers[0].logical_origin());
assert_eq!(direct.layers[0].ring_origin(), incremental.layers[0].ring_origin());
}
#[test] #[test]
fn cache_entry_fields_are_derived_from_scene_tiles() { fn cache_entry_fields_are_derived_from_scene_tiles() {
let scene = make_scene(); let scene = make_scene();
@ -415,6 +443,113 @@ mod tests {
assert_eq!(cache.layers[3].entry(3, 3).glyph_id, 415); assert_eq!(cache.layers[3].entry(3, 3).glyph_id, 415);
} }
#[test]
fn refresh_after_wrapped_window_move_materializes_new_logical_tiles() {
let scene = make_scene();
let mut cache = SceneViewportCache::new(&scene, 3, 3);
cache.refresh_layer_all(&scene, 0);
cache.move_layer_window_to(0, 1, 2);
cache.refresh_layer_all(&scene, 0);
assert_eq!(cache.layers[0].logical_origin(), (1, 2));
assert_eq!(cache.layers[0].ring_origin(), (1, 2));
assert_eq!(cache.layers[0].entry(0, 0).glyph_id, 109);
assert_eq!(cache.layers[0].entry(1, 0).glyph_id, 110);
assert_eq!(cache.layers[0].entry(2, 0).glyph_id, 111);
assert_eq!(cache.layers[0].entry(0, 1).glyph_id, 113);
assert_eq!(cache.layers[0].entry(2, 1).glyph_id, 115);
assert_eq!(cache.layers[0].entry(0, 2), CachedTileEntry::default());
}
#[test]
fn partial_refresh_uses_wrapped_physical_slots_after_window_move() {
let scene = make_scene();
let mut cache = SceneViewportCache::new(&scene, 3, 3);
cache.move_layer_window_to(0, 1, 0);
cache.refresh_layer_column(&scene, 0, 0);
assert_eq!(cache.layers[0].ring_origin(), (1, 0));
assert_eq!(cache.layers[0].entry(0, 0).glyph_id, 101);
assert_eq!(cache.layers[0].entry(0, 1).glyph_id, 105);
assert_eq!(cache.layers[0].entry(0, 2).glyph_id, 109);
assert_eq!(cache.layers[0].entry(1, 0), CachedTileEntry::default());
assert_eq!(cache.layers[0].entry(2, 0), CachedTileEntry::default());
}
#[test]
fn out_of_bounds_logical_origins_materialize_default_entries_after_wrap() {
let scene = make_scene();
let mut cache = SceneViewportCache::new(&scene, 2, 2);
cache.move_layer_window_to(0, -2, 3);
cache.refresh_layer_all(&scene, 0);
for y in 0..2 {
for x in 0..2 {
assert_eq!(cache.layers[0].entry(x, y), CachedTileEntry::default());
}
}
}
#[test]
fn ringbuffer_preserves_logical_tile_mapping_across_long_mixed_movements() {
let scene = make_scene();
let mut cache = SceneViewportCache::new(&scene, 3, 3);
let motions = [
(1, 0),
(0, 1),
(2, 2),
(-1, 0),
(0, -2),
(4, 1),
(-3, 3),
(5, -4),
(-6, 2),
(3, -3),
(7, 7),
(-8, -5),
];
for &(dx, dy) in &motions {
cache.move_layer_window_by(0, dx, dy);
cache.refresh_layer_all(&scene, 0);
let (origin_x, origin_y) = cache.layers[0].logical_origin();
for cache_y in 0..cache.height() {
for cache_x in 0..cache.width() {
let expected_scene_x = origin_x + cache_x as i32;
let expected_scene_y = origin_y + cache_y as i32;
let expected = if expected_scene_x < 0
|| expected_scene_y < 0
|| expected_scene_x as usize >= scene.layers[0].tilemap.width
|| expected_scene_y as usize >= scene.layers[0].tilemap.height
{
CachedTileEntry::default()
} else {
let tile_x = expected_scene_x as usize;
let tile_y = expected_scene_y as usize;
let tile = scene.layers[0].tilemap.tiles
[tile_y * scene.layers[0].tilemap.width + tile_x];
CachedTileEntry::from_tile(&scene.layers[0], tile)
};
assert_eq!(
cache.layers[0].entry(cache_x, cache_y),
expected,
"mismatch at logical origin ({}, {}), cache ({}, {})",
origin_x,
origin_y,
cache_x,
cache_y
);
}
}
}
}
#[test] #[test]
fn materialization_populates_all_four_layers() { fn materialization_populates_all_four_layers() {
let scene = make_scene(); let scene = make_scene();