dev/new-scene-plt #15

Merged
bquarkz merged 8 commits from dev/new-scene-plt into master 2026-04-14 04:26:50 +00:00
Showing only changes of commit f7c31592bb - Show all commits

View File

@ -3,6 +3,8 @@ use prometeu_hal::GfxBridge;
use prometeu_hal::color::Color;
use prometeu_hal::glyph::Glyph;
use prometeu_hal::glyph_bank::GlyphBank;
use prometeu_hal::scene_viewport_cache::{CachedTileEntry, SceneViewportCache};
use prometeu_hal::scene_viewport_resolver::{LayerCopyRequest, ResolverUpdate};
use prometeu_hal::sprite::Sprite;
use std::sync::Arc;
@ -531,17 +533,7 @@ impl Gfx {
/// correct priority order into the back buffer.
/// Follows the hardware model where layers and sprites are composed every frame.
pub fn render_all(&mut self) {
// 0. Preparation Phase: Filter and group sprites by their priority levels.
// This avoids iterating through all 512 sprites for every layer.
for bucket in self.priority_buckets.iter_mut() {
bucket.clear();
}
for (idx, sprite) in self.sprites.iter().enumerate() {
if sprite.active && sprite.priority < 5 {
self.priority_buckets[sprite.priority as usize].push(idx);
}
}
self.populate_priority_buckets();
// 1. Priority 0 sprites: drawn at the very back, behind everything else.
Self::draw_bucket_on_buffer(
@ -590,6 +582,48 @@ impl Gfx {
Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color);
}
/// Composes the world from the viewport cache using resolver copy requests.
///
/// This is the cache-backed world path accepted by DEC-0013. The canonical scene
/// is not consulted here; the renderer only consumes prepared cache materialization
/// plus sprite state and fade controls.
pub fn render_scene_from_cache(&mut self, cache: &SceneViewportCache, update: &ResolverUpdate) {
self.back.fill(Color::BLACK.raw());
self.populate_priority_buckets();
Self::draw_bucket_on_buffer(
&mut self.back,
self.w,
self.h,
&self.priority_buckets[0],
&self.sprites,
&*self.glyph_banks,
);
for layer_index in 0..cache.layers.len() {
Self::draw_cache_layer_to_buffer(
&mut self.back,
self.w,
self.h,
cache,
&update.copy_requests[layer_index],
&*self.glyph_banks,
);
Self::draw_bucket_on_buffer(
&mut self.back,
self.w,
self.h,
&self.priority_buckets[layer_index + 1],
&self.sprites,
&*self.glyph_banks,
);
}
Self::apply_fade_to_buffer(&mut self.back, self.scene_fade_level, self.scene_fade_color);
Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color);
}
// /// Renders a specific game layer.
// pub fn render_layer(&mut self, layer_idx: usize) {
// if layer_idx >= self.layers.len() {
@ -721,6 +755,104 @@ impl Gfx {
// }
// }
fn populate_priority_buckets(&mut self) {
for bucket in self.priority_buckets.iter_mut() {
bucket.clear();
}
for (idx, sprite) in self.sprites.iter().enumerate() {
if sprite.active && sprite.priority < 5 {
self.priority_buckets[sprite.priority as usize].push(idx);
}
}
}
fn draw_cache_layer_to_buffer(
back: &mut [u16],
screen_w: usize,
screen_h: usize,
cache: &SceneViewportCache,
request: &LayerCopyRequest,
glyph_banks: &dyn GlyphBankPoolAccess,
) {
let layer_cache = &cache.layers[request.layer_index];
if !layer_cache.valid {
return;
}
let Some(bank) = glyph_banks.glyph_bank_slot(layer_cache.glyph_bank_id as usize) else {
return;
};
let tile_size_px = request.tile_size as i32;
for cache_y in 0..layer_cache.height() {
let screen_tile_y = cache_y as i32 * tile_size_px - request.source_offset_y_px;
if screen_tile_y >= screen_h as i32 || screen_tile_y + tile_size_px <= 0 {
continue;
}
for cache_x in 0..layer_cache.width() {
let screen_tile_x = cache_x as i32 * tile_size_px - request.source_offset_x_px;
if screen_tile_x >= screen_w as i32 || screen_tile_x + tile_size_px <= 0 {
continue;
}
let entry = layer_cache.entry(cache_x, cache_y);
if !entry.active {
continue;
}
Self::draw_cached_tile_pixels(
back,
screen_w,
screen_h,
screen_tile_x,
screen_tile_y,
entry,
&bank,
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;
for local_y in 0..size {
let world_y = y + local_y as i32;
if world_y < 0 || world_y >= 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 {
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);
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();
}
}
}
fn draw_bucket_on_buffer(
back: &mut [u16],
screen_w: usize,
@ -844,7 +976,88 @@ impl Gfx {
#[cfg(test)]
mod tests {
use super::*;
use crate::memory_banks::MemoryBanks;
use crate::memory_banks::{GlyphBankPoolInstaller, MemoryBanks};
use prometeu_hal::glyph_bank::TileSize;
use prometeu_hal::scene_bank::SceneBank;
use prometeu_hal::scene_layer::{MotionFactor, SceneLayer};
use prometeu_hal::scene_viewport_cache::SceneViewportCache;
use prometeu_hal::scene_viewport_resolver::SceneViewportResolver;
use prometeu_hal::tile::Tile;
use prometeu_hal::tilemap::TileMap;
fn make_glyph_bank(tile_size: TileSize, palette_colors: &[(u8, Color)]) -> GlyphBank {
let size = tile_size as usize;
let mut bank = GlyphBank::new(tile_size, size, size);
for (palette_id, color) in palette_colors {
bank.palettes[*palette_id as usize][1] = *color;
}
for y in 0..size {
for x in 0..size {
bank.pixel_indices[y * bank.width + x] = 1;
}
}
bank
}
fn make_layer(
glyph_bank_id: u8,
glyph_id: u16,
palette_id: u8,
width: usize,
height: usize,
) -> SceneLayer {
SceneLayer {
active: true,
glyph_bank_id,
tile_size: TileSize::Size8,
motion_factor: MotionFactor { x: 1.0, y: 1.0 },
tilemap: TileMap {
width,
height,
tiles: vec![
Tile {
active: true,
glyph: Glyph { glyph_id, palette_id },
flip_x: false,
flip_y: false,
};
width * height
],
},
}
}
fn make_inactive_layer(glyph_bank_id: u8, width: usize, height: usize) -> SceneLayer {
SceneLayer {
active: false,
glyph_bank_id,
tile_size: TileSize::Size8,
motion_factor: MotionFactor { x: 1.0, y: 1.0 },
tilemap: TileMap { width, height, tiles: vec![Tile::default(); width * height] },
}
}
fn make_scene(palette_ids: [u8; 4]) -> SceneBank {
SceneBank {
layers: [
make_layer(0, 0, palette_ids[0], 8, 8),
make_layer(0, 0, palette_ids[1], 8, 8),
make_layer(0, 0, palette_ids[2], 8, 8),
make_layer(0, 0, palette_ids[3], 8, 8),
],
}
}
fn make_scene_with_inactive_top_layers() -> SceneBank {
SceneBank {
layers: [
make_layer(0, 0, 0, 8, 8),
make_layer(0, 0, 1, 8, 8),
make_layer(0, 0, 2, 8, 8),
make_inactive_layer(0, 8, 8),
],
}
}
#[test]
fn test_draw_pixel() {
@ -896,6 +1109,79 @@ mod tests {
// Fill
assert_eq!(gfx.back[3 * 10 + 3], Color::BLACK.0);
}
#[test]
fn render_scene_from_cache_uses_materialized_cache_not_canonical_scene() {
let banks = Arc::new(MemoryBanks::new());
banks.install_glyph_bank(
0,
Arc::new(make_glyph_bank(TileSize::Size8, &[(0, Color::RED), (1, Color::GREEN)])),
);
let mut scene = make_scene([0, 0, 0, 0]);
let mut cache = SceneViewportCache::new(&scene, 4, 4);
cache.materialize_all_layers(&scene);
scene.layers[0].tilemap.tiles[0].glyph.palette_id = 1;
let mut resolver = SceneViewportResolver::new(16, 16, 4, 4, 12, 20);
let update = resolver.update(&scene, 0, 0);
let mut gfx = Gfx::new(16, 16, banks);
gfx.scene_fade_level = 31;
gfx.hud_fade_level = 31;
gfx.render_scene_from_cache(&cache, &update);
assert_eq!(gfx.back[0], Color::RED.raw());
}
#[test]
fn render_scene_from_cache_preserves_layer_and_sprite_order() {
let banks = Arc::new(MemoryBanks::new());
banks.install_glyph_bank(
0,
Arc::new(make_glyph_bank(
TileSize::Size8,
&[(0, Color::RED), (1, Color::GREEN), (2, Color::BLUE), (4, Color::WHITE)],
)),
);
let scene = make_scene_with_inactive_top_layers();
let mut cache = SceneViewportCache::new(&scene, 4, 4);
cache.materialize_all_layers(&scene);
let mut resolver = SceneViewportResolver::new(16, 16, 4, 4, 12, 20);
let update = resolver.update(&scene, 0, 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;
gfx.sprites[0] = Sprite {
glyph: Glyph { glyph_id: 0, palette_id: 4 },
x: 0,
y: 0,
bank_id: 0,
active: true,
flip_x: false,
flip_y: false,
priority: 0,
};
gfx.sprites[1] = Sprite {
glyph: Glyph { glyph_id: 0, palette_id: 4 },
x: 0,
y: 0,
bank_id: 0,
active: true,
flip_x: false,
flip_y: false,
priority: 2,
};
gfx.render_scene_from_cache(&cache, &update);
assert_eq!(gfx.back[0], Color::BLUE.raw());
}
}
/// Blends in RGB565 per channel with saturation.