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];
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]
fn glyph_for_char(c: char) -> &'static [u8; 5] {
match c.to_ascii_uppercase() {
@ -629,6 +644,7 @@ impl Gfx {
request: &LayerCopyRequest,
glyph_banks: &dyn GlyphBankPoolAccess,
) {
let mut target = RenderTarget { back, screen_w, screen_h };
let layer_cache = &cache.layers[request.layer_index];
if !layer_cache.valid {
return;
@ -657,52 +673,43 @@ impl Gfx {
}
Self::draw_cached_tile_pixels(
back,
screen_w,
screen_h,
screen_tile_x,
screen_tile_y,
&mut target,
CachedTileDraw {
x: screen_tile_x,
y: screen_tile_y,
entry,
&bank,
request.tile_size,
bank: &bank,
tile_size: request.tile_size,
},
);
}
}
}
fn draw_cached_tile_pixels(
back: &mut [u16],
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;
fn draw_cached_tile_pixels(target: &mut RenderTarget<'_>, tile: CachedTileDraw<'_>) {
let size = tile.tile_size as usize;
for local_y in 0..size {
let world_y = y + local_y as i32;
if world_y < 0 || world_y >= screen_h as i32 {
let world_y = tile.y + local_y as i32;
if world_y < 0 || world_y >= target.screen_h as i32 {
continue;
}
for local_x in 0..size {
let world_x = x + local_x as i32;
if world_x < 0 || world_x >= screen_w as i32 {
let world_x = tile.x + local_x as i32;
if world_x < 0 || world_x >= target.screen_w as i32 {
continue;
}
let fetch_x = if 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 px_index = bank.get_pixel_index(entry.glyph_id, fetch_x, fetch_y);
let fetch_x = if tile.entry.flip_x() { size - 1 - local_x } else { local_x };
let fetch_y = if tile.entry.flip_y() { size - 1 - local_y } else { local_y };
let px_index = tile.bank.get_pixel_index(tile.entry.glyph_id, fetch_x, fetch_y);
if px_index == 0 {
continue;
}
let color = bank.resolve_color(entry.palette_id, px_index);
back[world_y as usize * screen_w + world_x as usize] = color.raw();
let color = tile.bank.resolve_color(tile.entry.palette_id, px_index);
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));
}
#[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]
fn cache_entry_fields_are_derived_from_scene_tiles() {
let scene = make_scene();
@ -415,6 +443,113 @@ mod tests {
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]
fn materialization_populates_all_four_layers() {
let scene = make_scene();