diff --git a/crates/core/src/machine.rs b/crates/core/src/machine.rs index d14c0e97..8c4827ad 100644 --- a/crates/core/src/machine.rs +++ b/crates/core/src/machine.rs @@ -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. diff --git a/crates/core/src/peripherals/gfx.rs b/crates/core/src/peripherals/gfx.rs index 8fb6c61d..57574194 100644 --- a/crates/core/src/peripherals/gfx.rs +++ b/crates/core/src/peripherals/gfx.rs @@ -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; 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); } } diff --git a/crates/host_desktop/src/main.rs b/crates/host_desktop/src/main.rs index 9d5db4f0..132ca42b 100644 --- a/crates/host_desktop/src/main.rs +++ b/crates/host_desktop/src/main.rs @@ -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); }