implements PLN-0014
This commit is contained in:
parent
2d2c320638
commit
f7c31592bb
@ -3,6 +3,8 @@ use prometeu_hal::GfxBridge;
|
|||||||
use prometeu_hal::color::Color;
|
use prometeu_hal::color::Color;
|
||||||
use prometeu_hal::glyph::Glyph;
|
use prometeu_hal::glyph::Glyph;
|
||||||
use prometeu_hal::glyph_bank::GlyphBank;
|
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 prometeu_hal::sprite::Sprite;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -531,17 +533,7 @@ impl Gfx {
|
|||||||
/// correct priority order into the back buffer.
|
/// correct priority order into the back buffer.
|
||||||
/// Follows the hardware model where layers and sprites are composed every frame.
|
/// Follows the hardware model where layers and sprites are composed every frame.
|
||||||
pub fn render_all(&mut self) {
|
pub fn render_all(&mut self) {
|
||||||
// 0. Preparation Phase: Filter and group sprites by their priority levels.
|
self.populate_priority_buckets();
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Priority 0 sprites: drawn at the very back, behind everything else.
|
// 1. Priority 0 sprites: drawn at the very back, behind everything else.
|
||||||
Self::draw_bucket_on_buffer(
|
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);
|
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.
|
// /// Renders a specific game layer.
|
||||||
// pub fn render_layer(&mut self, layer_idx: usize) {
|
// pub fn render_layer(&mut self, layer_idx: usize) {
|
||||||
// if layer_idx >= self.layers.len() {
|
// 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(
|
fn draw_bucket_on_buffer(
|
||||||
back: &mut [u16],
|
back: &mut [u16],
|
||||||
screen_w: usize,
|
screen_w: usize,
|
||||||
@ -844,7 +976,88 @@ impl Gfx {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn test_draw_pixel() {
|
fn test_draw_pixel() {
|
||||||
@ -896,6 +1109,79 @@ mod tests {
|
|||||||
// Fill
|
// Fill
|
||||||
assert_eq!(gfx.back[3 * 10 + 3], Color::BLACK.0);
|
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.
|
/// Blends in RGB565 per channel with saturation.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user