dev/render-all-scene-cache-and-camera-integration #16
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user