added sprite and draw
This commit is contained in:
parent
13169698cf
commit
c718d36fe2
@ -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)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
13
crates/core/src/model/sprite.rs
Normal file
13
crates/core/src/model/sprite.rs
Normal 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.
|
||||||
|
}
|
||||||
@ -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.
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user