diff --git a/crates/core/src/machine.rs b/crates/core/src/machine.rs index c753b01d..82decf6a 100644 --- a/crates/core/src/machine.rs +++ b/crates/core/src/machine.rs @@ -46,7 +46,7 @@ impl Machine { /// Lógica do frame (demo hardcoded por enquanto). pub fn tick(&mut self) { // Limpa a tela com um azul escuro "estilo console" - self.gfx.clear(Color::rgb(0x20, 0x20, 0x40)); + self.gfx.clear(Color::BLACK); // Simula o carregamento de um banco se estiver vazio (apenas para teste) if self.gfx.banks[0].is_none() { @@ -62,22 +62,103 @@ impl Machine { let px = start_x + x; let py = start_y + y; let idx = py * 128 + px; - test_bank.pixels[idx] = Color::WHITE; + test_bank.pixels[idx] = Color::GREEN; } } self.gfx.banks[0] = Some(test_bank); } - // Coloca o Tile ID 1 no mapa do HUD (canto superior esquerdo) - self.gfx.hud.map.set_tile(0, 0, crate::model::Tile { id: 1, ..Default::default() }); + // Simula o carregamento de um banco se estiver vazio (apenas para teste) + if self.gfx.banks[1].is_none() { + let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128); + // Pinta o primeiro tile (ID 1) de Branco para teste + // Ele começa no pixel (16, 0) pois o ID 0 é o primeiro (0,0) + let tile_size = 16; + let start_x = 16; // ID 1 está na segunda posição da primeira linha + let start_y = 0; - for i in 0..10 { + for y in 0..tile_size { + for x in 0..tile_size { + let px = start_x + x; + let py = start_y + y; + let idx = py * 128 + px; + test_bank.pixels[idx] = Color::RED; + } + } + self.gfx.banks[1] = Some(test_bank); + } + + // Simula o carregamento de um banco se estiver vazio (apenas para teste) + if self.gfx.banks[2].is_none() { + let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128); + // Pinta o primeiro tile (ID 1) de Branco para teste + // Ele começa no pixel (16, 0) pois o ID 0 é o primeiro (0,0) + let tile_size = 16; + let start_x = 16; // ID 1 está na segunda posição da primeira linha + let start_y = 0; + + for y in 0..tile_size { + for x in 0..tile_size { + let px = start_x + x; + let py = start_y + y; + let idx = py * 128 + px; + test_bank.pixels[idx] = Color::INDIGO; + } + } + self.gfx.banks[2] = Some(test_bank); + } + + // Coloca o Tile ID 1 no mapa do HUD (canto superior esquerdo) + self.gfx.hud.map.set_tile(18, 6, crate::model::Tile { id: 1, ..Default::default() }); + + self.gfx.layers[0].bank_id = 1; + self.gfx.layers[0].map.set_tile(8, 1, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[0].map.set_tile(8, 2, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[0].map.set_tile(8, 3, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[0].map.set_tile(8, 4, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[0].map.set_tile(8, 5, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[1].bank_id = 1; + self.gfx.layers[1].map.set_tile(9, 1, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[1].map.set_tile(9, 2, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[1].map.set_tile(9, 3, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[1].map.set_tile(9, 4, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[1].map.set_tile(9, 5, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[2].bank_id = 1; + self.gfx.layers[2].map.set_tile(10, 1, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[2].map.set_tile(10, 2, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[2].map.set_tile(10, 3, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[2].map.set_tile(10, 4, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[2].map.set_tile(10, 5, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[3].bank_id = 1; + self.gfx.layers[3].map.set_tile(11, 1, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[3].map.set_tile(11, 2, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[3].map.set_tile(11, 3, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[3].map.set_tile(11, 4, crate::model::Tile { id: 1, ..Default::default() }); + self.gfx.layers[3].map.set_tile(11, 5, crate::model::Tile { id: 1, ..Default::default() }); + + for i in 0..512 { let s = &mut self.gfx.sprites[i]; s.active = true; - s.bank_id = 0; + + // Distribui os sprites entre os 3 bancos de teste que você criou + s.bank_id = (i % 2) as u8 + 1; s.tile.id = 1; - s.x = ((self.frame_index + (i as u64 * 20)) % 320) as i32; - s.y = (i as i32 * 15) + 20; + + // Cria um movimento caótico baseado no index do sprite e no frame_index + let speed = (1 + (i % 3)) as u64; + s.x = (((self.frame_index * speed) + (i as u64 * 10)) % 400) as i32 - 40; + s.y = (((self.frame_index * speed) + (i as u64 * 4)) % 180) as i32; + if i % 2 != 0 { + s.y = 179 - s.y; + } + + // Distribui as prioridades de 0 a 4 + // Isso vai fazer alguns sprites passarem atrás do cenário e outros na frente + s.priority = (i % 5) as u8; + + // Efeito de flip alternado para testar a lógica de espelhamento + s.flip_x = i % 2 == 0; + s.flip_y = i % 3 == 0; } } diff --git a/crates/core/src/model/color.rs b/crates/core/src/model/color.rs index 5009807f..f29dbfbd 100644 --- a/crates/core/src/model/color.rs +++ b/crates/core/src/model/color.rs @@ -9,6 +9,7 @@ pub struct Color(pub u16); impl Color { + pub const INDIGO: Self = Self::rgb(32, 32, 64); pub const BLACK: Self = Self::rgb(0, 0, 0); // 0x0000 pub const WHITE: Self = Self::rgb(255, 255, 255); // 0xFFFF pub const RED: Self = Self::rgb(255, 0, 0); // 0xF800 @@ -35,7 +36,7 @@ impl Color { pub const fn pack_from_native(r: u8, g: u8, b: u8) -> u16 { ((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F) } - + /// Cria uma cor RGB565 a partir de componentes 8-bit (0..255). pub const fn rgb(r: u8, g: u8, b: u8) -> Self { let r5 = (r as u16 >> 3) & 0x1F; diff --git a/crates/core/src/model/sprite.rs b/crates/core/src/model/sprite.rs index bca952fc..c491c093 100644 --- a/crates/core/src/model/sprite.rs +++ b/crates/core/src/model/sprite.rs @@ -9,5 +9,5 @@ pub struct Sprite { pub active: bool, pub flip_x: bool, pub flip_y: bool, - // No futuro: priority, palette override, etc. + pub priority: u8, } \ No newline at end of file diff --git a/crates/core/src/peripherals/gfx.rs b/crates/core/src/peripherals/gfx.rs index 6a1f0817..e102f619 100644 --- a/crates/core/src/peripherals/gfx.rs +++ b/crates/core/src/peripherals/gfx.rs @@ -38,6 +38,7 @@ impl Gfx { active: false, flip_x: false, flip_y: false, + priority: 4, }; let len = w * h; @@ -114,15 +115,20 @@ 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) { - // 1. Layers de jogo (0 a 3) + // 1. Sprites de prioridade 0 (Atrás da Layer 0 - o fundo do fundo) + self.render_sprites_by_priority(0); + for i in 0..self.layers.len() { + // 2. Layers de jogo (0 a 3) self.render_layer(i); + // 3. Sprites de acordo com prioridade + // i=0 desenha sprites priority 1 (sobre layer 0) + // 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); } - // 2. Sprites - self.render_sprites(); - - // 3. HUD (Sempre por cima) self.render_hud(); } @@ -189,9 +195,11 @@ impl Gfx { } } - fn render_sprites(&mut self) { + fn render_sprites_by_priority(&mut self, priority: u8) { for sprite in self.sprites.iter() { - if !sprite.active { continue; } + if !sprite.active || sprite.priority != priority { + continue; + } let bank = match self.banks.get(sprite.bank_id as usize) { Some(Some(b)) => b, diff --git a/crates/host_desktop/src/main.rs b/crates/host_desktop/src/main.rs index bcedf0b4..5d33fe14 100644 --- a/crates/host_desktop/src/main.rs +++ b/crates/host_desktop/src/main.rs @@ -31,6 +31,9 @@ struct PrometeuApp { frame_target_dt: Duration, next_frame: Instant, + + last_stats_update: Instant, + frames_since_last_update: u64, } impl PrometeuApp { @@ -45,6 +48,9 @@ impl PrometeuApp { frame_target_dt: Duration::from_nanos(1_000_000_000 / 60), next_frame: Instant::now(), + + last_stats_update: Instant::now(), + frames_since_last_update: 0, } } @@ -72,7 +78,7 @@ impl PrometeuApp { impl ApplicationHandler for PrometeuApp { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let attrs = WindowAttributes::default() - .with_title(format!("PROMETEU - host_desktop | GFX Mem: {:.2} KB | Frame: {}", 0.0, 0)) + .with_title(format!("PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Frame: {}", 0.0, 0.0, 0)) .with_inner_size(LogicalSize::new(960.0, 540.0)) .with_min_inner_size(LogicalSize::new(320.0, 180.0)); @@ -188,18 +194,25 @@ impl ApplicationHandler for PrometeuApp { // executa 1 frame do PROMETEU self.machine.step_frame(&self.input_signals); - // temp: atualiza o título da janela a cada 1 segundo (60 frames) - if self.machine.frame_index % 60 == 0 { + self.frames_since_last_update += 1; + + let elapsed = now.duration_since(self.last_stats_update); + if elapsed >= Duration::from_secs(1) { if let Some(window) = self.window { + let fps = self.frames_since_last_update as f64 / elapsed.as_secs_f64(); let usage_bytes = self.machine.gfx.memory_usage_bytes(); let kb = usage_bytes as f64 / 1024.0; let title = format!( - "PROMETEU - host_desktop | GFX Mem: {:.2} KB | Frame: {}", + "PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Frame: {}", kb, + fps, self.machine.frame_index); window.set_title(&title); } + + self.last_stats_update = now; + self.frames_since_last_update = 0; } // pede redraw