From 48c6c6d7b096199e530314c13586f633ebd8d9a0 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 14 Jan 2026 08:46:23 +0000 Subject: [PATCH] added pallete system --- crates/core/src/machine.rs | 137 +++++++++++------------------ crates/core/src/model/tile_bank.rs | 35 ++++++-- crates/core/src/peripherals/gfx.rs | 73 ++++++++------- 3 files changed, 118 insertions(+), 127 deletions(-) diff --git a/crates/core/src/machine.rs b/crates/core/src/machine.rs index 8c4827ad..f202d076 100644 --- a/crates/core/src/machine.rs +++ b/crates/core/src/machine.rs @@ -49,132 +49,99 @@ impl Machine { /// Lógica do frame (demo hardcoded por enquanto). pub fn tick(&mut self) { - // Limpa a tela com um azul escuro "estilo console" + // Limpa a tela com Preto self.gfx.clear(Color::BLACK); - // Simula o carregamento de um banco se estiver vazio (apenas para teste) + // SETUP BANCO 0 (Tiles 8x8) if self.gfx.banks[0].is_none() { - let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size8, 128, 128); - // Pinta o primeiro tile (ID 1) de Branco para teste - // Ele começa no pixel (8, 0) pois o ID 0 é o primeiro (0,0) + let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size8, 128, 128); + // Define Cor na Paleta 0, Índice 1 = VERDE + bank.palettes[0][1] = Color::GREEN.raw(); + let tile_size = 8; - let start_x = 8; // ID 1 está na segunda posição da primeira linha - let start_y = 0; - + let start_x = 8; // Tile ID 1 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::GREEN; + let idx = y * 128 + (start_x + x); + bank.pixel_indices[idx] = 1; // Atribui o índice da paleta } } - self.gfx.banks[0] = Some(test_bank); + self.gfx.banks[0] = Some(bank); } - // Simula o carregamento de um banco se estiver vazio (apenas para teste) + // SETUP BANCO 1 (Tiles 16x16) 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; + let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128); + // Define Cor na Paleta 0, Índice 1 = VERMELHO + bank.palettes[0][1] = Color::RED.raw(); + let tile_size = 16; + let start_x = 16; // Tile ID 1 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; + let idx = y * 128 + (start_x + x); + bank.pixel_indices[idx] = 1; } } - self.gfx.banks[1] = Some(test_bank); + self.gfx.banks[1] = Some(bank); } - // Simula o carregamento de um banco se estiver vazio (apenas para teste) + // SETUP BANCO 2 (Tiles 16x16 para Sprites) 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; + let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128); + // Define Cor na Paleta 0, Índice 1 = INDIGO (Roxo) + bank.palettes[0][1] = Color::rgb(0x4B, 0x00, 0x82).raw(); + // Define Cor na Paleta 1, Índice 1 = AMARELO (para Palette Swap) + bank.palettes[1][1] = Color::YELLOW.raw(); + let tile_size = 16; + let start_x = 16; 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; + let idx = y * 128 + (start_x + x); + bank.pixel_indices[idx] = 1; } } - self.gfx.banks[2] = Some(test_bank); + self.gfx.banks[2] = Some(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() }); + // --- LÓGICA DE ESTADO --- - 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() }); + // HUD + self.gfx.hud.map.set_tile(18, 6, crate::model::Tile { id: 1, palette_id: 0, ..Default::default() }); - // Faz a cena pulsar entre claro e escuro a cada 2 segundos - // Usamos uma conta matemática simples para gerar um valor de 0 a 31 - let pulse = (self.frame_index / 4) % 64; - let level = if pulse > 31 { 63 - pulse } else { pulse }; - - self.gfx.scene_fade_level = level as u8; - self.gfx.scene_fade_color = Color::BLACK; - - // O HUD continuará 100% visível enquanto o resto escurece! - self.gfx.hud_fade_level = 31; + // Camadas de Jogo (usando Banco 1 - Vermelho) + for i in 0..4 { + self.gfx.layers[i].bank_id = 1; + self.gfx.layers[i].map.set_tile(8 + i, 1 + i, crate::model::Tile { id: 1, palette_id: 0, ..Default::default() }); + } + // 512 Sprites (usando Banco 2 - Indigo e Amarelo) for i in 0..512 { let s = &mut self.gfx.sprites[i]; s.active = true; - - // Distribui os sprites entre os 3 bancos de teste que você criou - s.bank_id = (i % 2) as u8 + 1; + s.bank_id = 2; s.tile.id = 1; - // Cria um movimento caótico baseado no index do sprite e no frame_index + // Palette Swap: pares Indigo (0), ímpares Amarelo (1) + s.tile.palette_id = (i % 2) as u8; + 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; - } + let y_base = (((self.frame_index * speed) + (i as u64 * 4)) % 180) as i32; + s.y = if i % 2 == 0 { y_base } else { 179 - y_base }; - // 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; } + + // Post-FX Fade Pulsante + let pulse = (self.frame_index / 4) % 64; + let level = if pulse > 31 { 63 - pulse } else { pulse }; + self.gfx.scene_fade_level = level as u8; + self.gfx.scene_fade_color = Color::BLACK; + self.gfx.hud_fade_level = 31; } /// Final do frame: troca buffers. diff --git a/crates/core/src/model/tile_bank.rs b/crates/core/src/model/tile_bank.rs index 372173fc..523fe581 100644 --- a/crates/core/src/model/tile_bank.rs +++ b/crates/core/src/model/tile_bank.rs @@ -11,7 +11,12 @@ pub struct TileBank { pub tile_size: TileSize, pub width: usize, // em pixels pub height: usize, // em pixels - pub pixels: Vec, + // pub pixels: Vec, + + /// Agora guardamos índices de 0 a 15 (4 bits por pixel simulados em 8 bits) + pub pixel_indices: Vec, + /// 256 paletas, cada uma com 16 cores (RGB565 como u16) + pub palettes: [[u16; 16]; 256], } impl TileBank { @@ -20,26 +25,38 @@ impl TileBank { tile_size, width, height, - pixels: vec![Color::BLACK; width * height], + pixel_indices: vec![0; width * height], // Índice 0 = Transparente + palettes: [[0; 16]; 256], } } /// Retorna a cor de um pixel específico dentro de um tile. /// tile_id: o índice do tile no banco /// local_x, local_y: a posição do pixel dentro do tile (0 até tile_size-1) - pub fn get_pixel(&self, tile_id: u16, local_x: usize, local_y: usize) -> Color { - let tile_size = self.tile_size as usize; - let tiles_per_row = self.width / tile_size; - let tile_x = (tile_id as usize % tiles_per_row) * tile_size; - let tile_y = (tile_id as usize / tiles_per_row) * tile_size; + pub fn get_pixel_index(&self, tile_id: u16, local_x: usize, local_y: usize) -> u8 { + let size = self.tile_size as usize; + let tiles_per_row = self.width / size; + let tile_x = (tile_id as usize % tiles_per_row) * size; + let tile_y = (tile_id as usize / tiles_per_row) * size; let pixel_x = tile_x + local_x; let pixel_y = tile_y + local_y; if pixel_x < self.width && pixel_y < self.height { - self.pixels[pixel_y * self.width + pixel_x] + self.pixel_indices[pixel_y * self.width + pixel_x] } else { - Color::BLACK + 0 // Fora do banco = Transparente } } + + /// Resolve um índice de pixel para uma Color real usando uma paleta. + pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color { + // Regra: Índice 0 é sempre transparente (usamos MAGENTA/COLOR_KEY como vácuo) + if pixel_index == 0 { + return Color::COLOR_KEY; + } + + let raw_color = self.palettes[palette_id as usize][pixel_index as usize]; + Color::from_raw(raw_color) + } } \ No newline at end of file diff --git a/crates/core/src/peripherals/gfx.rs b/crates/core/src/peripherals/gfx.rs index 57574194..73a09877 100644 --- a/crates/core/src/peripherals/gfx.rs +++ b/crates/core/src/peripherals/gfx.rs @@ -248,10 +248,16 @@ impl Gfx { 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; } + // 1. Pega o índice do pixel no banco + let px_index = bank.get_pixel_index(tile.id, fetch_x, fetch_y); - back[world_y as usize * screen_w + world_x as usize] = color.0; + // 2. Regra: Índice 0 é transparente + if px_index == 0 { continue; } + + // 3. Resolve a cor usando a paleta do tile + let color = bank.resolve_color(tile.palette_id, px_index); + + back[world_y as usize * screen_w + world_x as usize] = color.raw(); } } } @@ -275,40 +281,31 @@ impl Gfx { sprite: &Sprite, bank: &TileBank ) { + // ... (mesmo cálculo de bounds/clipping que já tínhamos) ... let size = bank.tile_size as usize; - let sprite_w = size as i32; - let sprite_h = size as i32; - - // 1. Clipping Rápido: Se o sprite estiver totalmente fora, nem processamos - if sprite.x <= -sprite_w || sprite.x >= screen_w as i32 || - sprite.y <= -sprite_h || sprite.y >= screen_h as i32 { - return; - } - - // 2. Calcular a área visível do sprite (Interseção com a tela) let start_x = sprite.x.max(0); let start_y = sprite.y.max(0); - let end_x = (sprite.x + sprite_w).min(screen_w as i32); - let end_y = (sprite.y + sprite_h).min(screen_h as i32); + let end_x = (sprite.x + size as i32).min(screen_w as i32); + let end_y = (sprite.y + size as i32).min(screen_h as i32); - // 3. Loop apenas sobre os pixels visíveis na tela for world_y in start_y..end_y { for world_x in start_x..end_x { - - // Calcular a posição local dentro do tile considerando o scroll/posição do sprite let local_x = (world_x - sprite.x) as usize; let local_y = (world_y - sprite.y) as usize; - // Lógica de Flip (Espelhamento) let fetch_x = if sprite.flip_x { size - 1 - local_x } else { local_x }; let fetch_y = if sprite.flip_y { size - 1 - local_y } else { local_y }; - let color = bank.get_pixel(sprite.tile.id, fetch_x, fetch_y); + // 1. Pega o índice + let px_index = bank.get_pixel_index(sprite.tile.id, fetch_x, fetch_y); - if color == Color::COLOR_KEY { continue; } + // 2. Transparência + if px_index == 0 { continue; } - let idx = (world_y as usize * screen_w) + world_x as usize; - back[idx] = color.0; + // 3. Resolve cor via paleta (do tile dentro do sprite) + let color = bank.resolve_color(sprite.tile.palette_id, px_index); + + back[world_y as usize * screen_w + world_x as usize] = color.raw(); } } } @@ -334,34 +331,44 @@ impl Gfx { } } - /// Retorna o uso estimado de memória das estruturas gráficas em bytes. - /// Isso ajuda a monitorar o "custo" do hardware simulado. + /// Retorna o uso real de memória das estruturas gráficas em bytes. + /// Reflete os buffers de índices, paletas e OAM conforme Capítulo 10.8. pub fn memory_usage_bytes(&self) -> usize { let mut total = 0; - // Buffers de tela (Front + Back) + // 1. Framebuffers (Front + Back) + // Cada um é Vec, ocupando 2 bytes por pixel. total += self.front.len() * 2; total += self.back.len() * 2; - // Layers (Tiles + Metadados) + // 2. Tile Layers (4 Game Layers) for layer in &self.layers { - total += layer.map.tiles.len() * std::mem::size_of::(); + // Tamanho da struct + os dados do mapa (Vec) total += std::mem::size_of::(); + total += layer.map.tiles.len() * std::mem::size_of::(); } - // HUD - total += self.hud.map.tiles.len() * std::mem::size_of::(); + // 3. HUD Layer total += std::mem::size_of::(); + total += self.hud.map.tiles.len() * std::mem::size_of::(); - // Tile Banks (Apenas bancos carregados) + // 4. Tile Banks (Assets e Paletas) for bank_opt in &self.banks { if let Some(bank) = bank_opt { - total += bank.pixels.len() * 2; // RGB565 = 2 bytes total += std::mem::size_of::(); + + // Buffer de Índices (pixel_indices: Vec) + // Cada pixel ocupa exatamente 1 byte. + total += bank.pixel_indices.len(); + + // Tabela de Paletas (palettes: [[u16; 16]; 256]) + // 256 paletas * 16 cores * 2 bytes cada. + total += 256 * 16 * 2; } } - // Sprites (OAM) + // 5. Sprites (OAM) + // Array fixo de 512 Sprites. total += self.sprites.len() * std::mem::size_of::(); total