added sprite and draw

This commit is contained in:
bQUARKz 2026-01-13 07:04:14 +00:00
parent 13169698cf
commit c718d36fe2
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 154 additions and 30 deletions

View File

@ -28,6 +28,7 @@ impl Machine {
pub fn step_frame(&mut self, signals: &InputSignals) { pub fn step_frame(&mut self, signals: &InputSignals) {
self.begin_frame(signals); self.begin_frame(signals);
self.tick(); // futuro: executa cartucho/VM 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.end_frame(); // present/housekeeping
} }
@ -44,22 +45,6 @@ impl Machine {
/// Lógica do frame (demo hardcoded por enquanto). /// Lógica do frame (demo hardcoded por enquanto).
pub fn tick(&mut self) { pub fn tick(&mut self) {
// self.gfx.clear(Color::rgb(0x10, 0x10, 0x10));
//
// let (x, y) = self.demo_pos();
//
// let color = if self.pad.a.down || self.touch.f.down {
// Color::rgb(0x00, 0xFF, 0x00)
// } else {
// Color::rgb(0xFF, 0x40, 0x40)
// };
//
// // game
// self.gfx.fill_rect(x, y, 20, 20, color);
// // smoke
// self.gfx.fill_rect_blend(140, 0, 40, 180, Color::gray_scale(0x88), BlendMode::Half)
// Limpa a tela com um azul escuro "estilo console" // Limpa a tela com um azul escuro "estilo console"
self.gfx.clear(Color::rgb(0x20, 0x20, 0x40)); self.gfx.clear(Color::rgb(0x20, 0x20, 0x40));
@ -86,18 +71,18 @@ impl Machine {
// Coloca o Tile ID 1 no mapa do HUD (canto superior esquerdo) // 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() }); self.gfx.hud.map.set_tile(0, 0, crate::model::Tile { id: 1, ..Default::default() });
// Executa o pipeline gráfico for i in 0..10 {
self.gfx.render_all(); let s = &mut self.gfx.sprites[i];
s.active = true;
s.bank_id = 0;
s.tile.id = 1;
s.x = ((self.frame_index + (i as u64 * 20)) % 320) as i32;
s.y = (i as i32 * 15) + 20;
}
} }
/// Final do frame: troca buffers. /// Final do frame: troca buffers.
pub fn end_frame(&mut self) { pub fn end_frame(&mut self) {
self.gfx.present(); self.gfx.present();
} }
// fn demo_pos(&self) -> (i32, i32) {
// let x = (self.frame_index % 300) as i32;
// let y = (self.pad.a.hold_frames % 160) as i32;
// (x, y)
// }
} }

View File

@ -3,9 +3,11 @@ mod button;
mod tile; mod tile;
mod tile_layer; mod tile_layer;
mod tile_bank; mod tile_bank;
mod sprite;
pub use button::Button; pub use button::Button;
pub use color::Color; pub use color::Color;
pub use tile::Tile; pub use tile::Tile;
pub use tile_bank::{TileBank, TileSize}; pub use tile_bank::{TileBank, TileSize};
pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap};
pub use sprite::Sprite;

View File

@ -0,0 +1,13 @@
use crate::model::Tile;
#[derive(Clone, Copy, Debug, Default)]
pub struct Sprite {
pub tile: Tile,
pub x: i32,
pub y: i32,
pub bank_id: u8,
pub active: bool,
pub flip_x: bool,
pub flip_y: bool,
// No futuro: priority, palette override, etc.
}

View File

@ -1,4 +1,4 @@
use crate::model::{Color, HudTileLayer, ScrollableTileLayer, TileBank, TileMap, TileSize}; use crate::model::{Color, HudTileLayer, ScrollableTileLayer, Sprite, TileBank, TileMap, TileSize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum BlendMode { pub enum BlendMode {
@ -24,12 +24,22 @@ pub struct Gfx {
pub layers: [ScrollableTileLayer; 4], pub layers: [ScrollableTileLayer; 4],
pub hud: HudTileLayer, pub hud: HudTileLayer,
pub banks: [Option<TileBank>; 16], pub banks: [Option<TileBank>; 16],
pub sprites: [Sprite; 512], // Nossa "OAM"
} }
impl Gfx { impl Gfx {
pub fn new(w: usize, h: usize) -> Self { pub fn new(w: usize, h: usize) -> Self {
const EMPTY_BANK: Option<TileBank> = None; const EMPTY_BANK: Option<TileBank> = None;
const EMPTY_SPRITE: Sprite = Sprite {
tile: crate::model::Tile { id: 0, flip_x: false, flip_y: false, palette_id: 0 },
x: 0,
y: 0,
bank_id: 0,
active: false,
flip_x: false,
flip_y: false,
};
let len = w * h; let len = w * h;
Self { Self {
w, w,
@ -44,6 +54,7 @@ impl Gfx {
], ],
hud: HudTileLayer::new(64, 32), hud: HudTileLayer::new(64, 32),
banks: [EMPTY_BANK; 16], banks: [EMPTY_BANK; 16],
sprites: [EMPTY_SPRITE; 512],
} }
} }
@ -108,7 +119,9 @@ impl Gfx {
self.render_layer(i); self.render_layer(i);
} }
// 2. Sprites (Ainda vamos implementar) // 2. Sprites
self.render_sprites();
// 3. HUD (Sempre por cima) // 3. HUD (Sempre por cima)
self.render_hud(); self.render_hud();
@ -175,6 +188,103 @@ impl Gfx {
} }
} }
} }
fn render_sprites(&mut self) {
for sprite in self.sprites.iter() {
if !sprite.active { continue; }
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
);
}
}
fn draw_sprite_pixel_by_pixel(
back: &mut [u16],
screen_w: usize,
screen_h: usize,
sprite: &Sprite,
bank: &TileBank
) {
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);
// 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);
if color == Color::COLOR_KEY { continue; }
let idx = (world_y as usize * screen_w) + world_x as usize;
back[idx] = color.0;
}
}
}
/// Retorna o uso estimado de memória das estruturas gráficas em bytes.
/// Isso ajuda a monitorar o "custo" do hardware simulado.
pub fn memory_usage_bytes(&self) -> usize {
let mut total = 0;
// Buffers de tela (Front + Back)
total += self.front.len() * 2;
total += self.back.len() * 2;
// Layers (Tiles + Metadados)
for layer in &self.layers {
total += layer.map.tiles.len() * std::mem::size_of::<crate::model::Tile>();
total += std::mem::size_of::<ScrollableTileLayer>();
}
// HUD
total += self.hud.map.tiles.len() * std::mem::size_of::<crate::model::Tile>();
total += std::mem::size_of::<HudTileLayer>();
// Tile Banks (Apenas bancos carregados)
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::<TileBank>();
}
}
// Sprites (OAM)
total += self.sprites.len() * std::mem::size_of::<crate::model::Sprite>();
total
}
} }
/// Faz blend em RGB565 por canal com saturação. /// Faz blend em RGB565 por canal com saturação.

View File

@ -69,10 +69,10 @@ impl PrometeuApp {
} }
} }
impl ApplicationHandler for PrometeuApp { impl ApplicationHandler for PrometeuApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attrs = WindowAttributes::default() let attrs = WindowAttributes::default()
.with_title("PROMETEU - host_desktop") .with_title(format!("PROMETEU - host_desktop | GFX Mem: {:.2} KB | Frame: {}", 0.0, 0))
.with_inner_size(LogicalSize::new(960.0, 540.0)) .with_inner_size(LogicalSize::new(960.0, 540.0))
.with_min_inner_size(LogicalSize::new(320.0, 180.0)); .with_min_inner_size(LogicalSize::new(320.0, 180.0));
@ -188,6 +188,20 @@ impl ApplicationHandler for PrometeuApp {
// executa 1 frame do PROMETEU // executa 1 frame do PROMETEU
self.machine.step_frame(&self.input_signals); 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 {
if let Some(window) = self.window {
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: {}",
kb,
self.machine.frame_index);
window.set_title(&title);
}
}
// pede redraw // pede redraw
self.request_redraw(); self.request_redraw();
} else { } else {