implemented tile cache and raised performance

This commit is contained in:
Nilton Constantino 2026-01-13 09:31:40 +00:00
parent 54ed1d0eaf
commit e6f4d0c255
No known key found for this signature in database
3 changed files with 76 additions and 32 deletions

View File

@ -8,6 +8,7 @@ pub struct Machine {
pub pad: Pad,
pub touch: Touch,
pub frame_index: u64,
pub last_frame_cpu_time_us: u64,
}
impl Machine {
@ -20,16 +21,19 @@ impl Machine {
pad: Pad::default(),
touch: Touch::default(),
frame_index: 0,
last_frame_cpu_time_us: 0,
}
}
/// "Contrato de frame" do PROMETEU (Template Method sem loop).
/// O Host controla tempo/event loop; o Core define a sequência do frame.
pub fn step_frame(&mut self, signals: &InputSignals) {
let start = std::time::Instant::now();
self.begin_frame(signals);
self.tick(); // futuro: executa cartucho/VM
self.gfx.render_all(); // A máquina executa o pipeline gráfico (Ação física)
self.end_frame(); // present/housekeeping
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; // Calcula quanto tempo a "CPU simulada" trabalhou de verdade
}
/// Início do frame: zera flags transitórias.

View File

@ -124,8 +124,24 @@ impl Gfx {
/// Pipeline principal de renderização do frame.
/// Segue a ordem de prioridade do manual (Capítulo 4.11).
pub fn render_all(&mut self) {
// 0. Fase de Preparação: Organiza quem deve ser desenhado em cada camada
// Criamos listas de índices para evitar percorrer os 512 sprites 5 vezes
let mut priority_buckets: [Vec<usize>; 5] = [
Vec::with_capacity(128),
Vec::with_capacity(128),
Vec::with_capacity(128),
Vec::with_capacity(128),
Vec::with_capacity(128),
];
for (idx, sprite) in self.sprites.iter().enumerate() {
if sprite.active && sprite.priority < 5 {
priority_buckets[sprite.priority as usize].push(idx);
}
}
// 1. Sprites de prioridade 0 (Atrás da Layer 0 - o fundo do fundo)
self.render_sprites_by_priority(0);
self.draw_bucket(&priority_buckets[0]);
for i in 0..self.layers.len() {
// 2. Layers de jogo (0 a 3)
@ -135,7 +151,7 @@ impl Gfx {
// i=1 desenha sprites priority 2 (sobre layer 1)
// i=2 desenha sprites priority 3 (sobre layer 2)
// i=3 desenha sprites priority 4 (sobre layer 3 - à frente de tudo)
self.render_sprites_by_priority((i + 1) as u8);
self.draw_bucket(&priority_buckets[i + 1]);
}
// 4. Aplica Scene Fade (Afeta tudo desenhado até agora)
@ -183,51 +199,72 @@ impl Gfx {
scroll_x: i32,
scroll_y: i32
) {
let size = bank.tile_size as usize;
let tile_size = bank.tile_size as usize;
for screen_y in 0..screen_h {
for screen_x in 0..screen_w {
let world_x = screen_x as i32 + scroll_x;
let world_y = screen_y as i32 + scroll_y;
// 1. Calcular quantos tiles cabem na tela (com margem de 1 para o scroll)
let visible_tiles_x = (screen_w / tile_size) + 1;
let visible_tiles_y = (screen_h / tile_size) + 1;
let tile_x = (world_x / size as i32) as usize;
let tile_y = (world_y / size as i32) as usize;
// 2. Calcular o offset inicial (onde o primeiro tile começa a ser desenhado)
let start_tile_x = scroll_x / tile_size as i32;
let start_tile_y = scroll_y / tile_size as i32;
if tile_x >= map.width || tile_y >= map.height { continue; }
let fine_scroll_x = scroll_x % tile_size as i32;
let fine_scroll_y = scroll_y % tile_size as i32;
let tile = map.tiles[tile_y * map.width + tile_x];
// 3. Loop por Tile (Muito mais eficiente)
for ty in 0..visible_tiles_y {
for tx in 0..visible_tiles_x {
let map_x = (start_tile_x + tx as i32) as usize;
let map_y = (start_tile_y + ty as i32) as usize;
// Skip se estiver fora dos limites do mapa
if map_x >= map.width || map_y >= map.height { continue; }
let tile = map.tiles[map_y * map.width + map_x];
if tile.id == 0 { continue; }
let local_x = (world_x % size as i32) as usize;
let local_y = (world_y % size as i32) as usize;
// 4. Desenha o bloco do tile na tela
let screen_tile_x = (tx as i32 * tile_size as i32) - fine_scroll_x;
let screen_tile_y = (ty as i32 * tile_size as i32) - fine_scroll_y;
let color = bank.get_pixel(tile.id, local_x, local_y);
if color == Color::COLOR_KEY { continue; }
let idx = (screen_y * screen_w) + screen_x;
back[idx] = color.0;
Self::draw_tile_pixels(back, screen_w, screen_h, screen_tile_x, screen_tile_y, tile, bank);
}
}
}
fn render_sprites_by_priority(&mut self, priority: u8) {
for sprite in self.sprites.iter() {
if !sprite.active || sprite.priority != priority {
continue;
}
// Função auxiliar para desenhar um bloco de 8x8, 16x16 ou 32x32 pixels
fn draw_tile_pixels(back: &mut [u16], screen_w: usize, screen_h: usize, x: i32, y: i32, tile: crate::model::Tile, bank: &TileBank) {
let size = bank.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 tile.flip_x { size - 1 - local_x } else { local_x };
let fetch_y = if tile.flip_y { size - 1 - local_y } else { local_y };
let color = bank.get_pixel(tile.id, fetch_x, fetch_y);
if color == Color::COLOR_KEY { continue; }
back[world_y as usize * screen_w + world_x as usize] = color.0;
}
}
}
fn draw_bucket(&mut self, bucket: &[usize]) {
for &idx in bucket {
let sprite = &self.sprites[idx];
let bank = match self.banks.get(sprite.bank_id as usize) {
Some(Some(b)) => b,
_ => continue,
};
Self::draw_sprite_pixel_by_pixel(
&mut self.back,
self.w,
self.h,
sprite,
bank
);
Self::draw_sprite_pixel_by_pixel(&mut self.back, self.w, self.h, sprite, bank);
}
}

View File

@ -213,9 +213,12 @@ impl ApplicationHandler for PrometeuApp {
let fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
let kb = self.machine.gfx.memory_usage_bytes() as f64 / 1024.0;
let frame_budget_us = 16666.0;
let cpu_load = (self.machine.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0;
let title = format!(
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Frame: {}",
kb, fps, self.machine.frame_index
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% | Frame: {}",
kb, fps, cpu_load, self.machine.frame_index
);
window.set_title(&title);
}