Split WASM and native versions, and move iced support code to native

This commit is contained in:
2026-03-27 00:27:34 -05:00
parent 3010469c8a
commit b433148843
23 changed files with 2330 additions and 1012 deletions

View File

@@ -1,11 +1,6 @@
use std::fmt;
use bytes::{Bytes, BytesMut};
use iced::{
Point, Size,
advanced::graphics::geometry::Renderer,
widget::canvas::{Fill, Frame},
};
use crate::mem::{Mapped, PpuMem, Value};
@@ -16,12 +11,6 @@ pub struct Color {
pub b: u8,
}
impl Into<Fill> for Color {
fn into(self) -> Fill {
iced::Color::from_rgb8(self.r, self.g, self.b).into()
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
@@ -66,6 +55,10 @@ impl<const W: usize, const H: usize> RenderBuffer<W, H> {
Bytes::copy_from_slice(&self.raw_rgba)
}
pub fn raw_image(&self) -> &[u8] {
&self.raw_rgba
}
pub fn assert_eq(&self, other: &Self) {
// if self.buffer != other.buffer {
for y in 0..H {
@@ -1126,238 +1119,6 @@ impl PPU {
pub fn oam_edit_ver(&self) -> usize {
self.oam.edit_ver
}
pub fn render_name_table<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
for y in 0..60 {
for x in 0..64 {
let row = y % 30;
let col = x % 32;
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as u16 * 16
+ if self.background.second_pattern {
0x1000
} else {
0
};
let attr = mem
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
.unwrap();
// attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
// let h_off = ((self.pixel - 1) / 16) % 2;
// let v_off = (self.scanline / 16) % 2;
// let off = v_off * 4 + h_off * 2;
let palette = (attr >> (((col & 2) << 0) | ((row & 2) << 1))) & 0x3;
for y_off in 0..8 {
let low = mem.peek_ppu(name + y_off).unwrap();
let high = mem.peek_ppu(name + y_off + 8).unwrap();
for bit in 0..8 {
frame.fill_rectangle(
Point::new(
x as f32 * 8. + 8. - bit as f32,
y as f32 * 8. + y_off as f32,
),
Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => self.palette.color(0),
(true, false) => self.palette.color(1 + 4 * palette),
(false, true) => self.palette.color(2 + 4 * palette),
(true, true) => self.palette.color(3 + 4 * palette),
// (false, false) => Color { r: 0, g: 0, b: 0 },
// (true, false) => Color {
// r: 64,
// g: 64,
// b: 64,
// },
// (false, true) => Color {
// r: 128,
// g: 128,
// b: 128,
// },
// (true, true) => Color {
// r: 255,
// g: 255,
// b: 255,
// },
},
);
}
}
// for
// let pat = mem.peek();
}
}
}
pub fn name_cursor_info(&self, mem: &Mapped, cursor: Point) -> Option<String> {
let x = (cursor.x / 8.) as usize;
let y = (cursor.y / 8.) as usize;
if x < 64 && y < 60 {
let row = y % 30;
let col = x % 32;
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as usize;
let attr = mem
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
.unwrap();
Some(format!(
"Row, Column: {}, {}
X, Y: {}, {}
Tilemap address: ${:04X}
Tile Index: ${:02X}
Tile Address (PPU): ${:04X}
Tile Address (CHR): ${:04X}
Palette Index {}
Palette Address ${:04X}
Attribute Address ${:04X}
Attribute data: ${:02X}
",
row,
col,
col * 8,
row * 8,
off + col + row * 32,
name,
name * 16
+ if self.background.second_pattern {
0x1000
} else {
0
},
name * 16
+ if self.background.second_pattern {
0x1000
} else {
0
},
(attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3,
((attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3) as usize * 4 + 0x3F00,
col / 4 + row / 4 * 8 + 0x3C0 + off,
attr,
))
} else {
None
}
}
pub fn render_pattern_tables<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
for y in 0..16 {
for x in 0..16 {
let name = (y * 16 + x) * 16;
for y_off in 0..8 {
let low = mem.peek_ppu(name + y_off).unwrap();
let high = mem.peek_ppu(name + y_off + 8).unwrap();
for bit in 0..8 {
frame.fill_rectangle(
Point::new(
x as f32 * 8. + 8. - bit as f32,
y as f32 * 8. + y_off as f32,
),
Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => Color { r: 0, g: 0, b: 0 },
(true, false) => Color {
r: 64,
g: 64,
b: 64,
},
(false, true) => Color {
r: 128,
g: 128,
b: 128,
},
(true, true) => Color {
r: 255,
g: 255,
b: 255,
},
},
);
}
}
}
}
for y in 0..16 {
for x in 0..16 {
let name = (y * 16 + x) * 16;
for y_off in 0..8 {
let low = mem.peek_ppu(name + y_off + 0x1000).unwrap();
let high = mem.peek_ppu(name + y_off + 8 + 0x1000).unwrap();
for bit in 0..8 {
frame.fill_rectangle(
Point::new(
x as f32 * 8. + 8. - bit as f32,
y as f32 * 8. + y_off as f32 + 130.,
),
Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => Color { r: 0, g: 0, b: 0 },
(true, false) => Color {
r: 64,
g: 64,
b: 64,
},
(false, true) => Color {
r: 128,
g: 128,
b: 128,
},
(true, true) => Color {
r: 255,
g: 255,
b: 255,
},
},
);
}
}
// for
// let pat = mem.peek();
}
}
}
pub fn pattern_cursor_info(&self, cursor: Point) -> Option<String> {
let x = (cursor.x / 8.) as usize;
let y = (cursor.y / 8.) as usize;
if x < 16 && y < 32 {
Some(format!(
"Tile address (PPU): {:04X}\nTile address (CHR): {:04X}\nIndex: {:02X}",
(y * 16 + x) * 16,
(y * 16 + x) * 16,
((y % 16) * 16 + x),
))
} else {
None
}
}
pub fn render_palette<R: Renderer>(&self, frame: &mut Frame<R>) {
for y in 0..8 {
for x in 0..4 {
frame.fill_rectangle(
Point::new(x as f32 * 10., y as f32 * 10.),
Size::new(10., 10.),
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
);
}
}
}
pub fn palette_cursor_info(&self, cursor: Point) -> Option<String> {
let x = (cursor.x / 10.) as usize;
let y = (cursor.y / 10.) as usize;
if x < 4 && y < 8 {
Some(format!(
"Index: {:02X}\nValue: {:02X}\nColor code: {}",
x + y * 4,
self.palette.ram[x + y * 4] & 0x3F,
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
))
} else {
None
}
}
}
#[cfg(test)]
@@ -1414,3 +1175,253 @@ mod tests {
assert_eq!(ppu.background.w, false);
}
}
#[cfg(feature = "iced")]
mod ppu_iced {
use super::*;
use iced::{
Point, Size,
advanced::graphics::geometry::Renderer,
widget::canvas::{Fill, Frame},
};
impl Into<Fill> for Color {
fn into(self) -> Fill {
iced::Color::from_rgb8(self.r, self.g, self.b).into()
}
}
impl PPU {
pub fn render_name_table<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
for y in 0..60 {
for x in 0..64 {
let row = y % 30;
let col = x % 32;
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as u16 * 16
+ if self.background.second_pattern {
0x1000
} else {
0
};
let attr = mem
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
.unwrap();
// attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
// let h_off = ((self.pixel - 1) / 16) % 2;
// let v_off = (self.scanline / 16) % 2;
// let off = v_off * 4 + h_off * 2;
let palette = (attr >> (((col & 2) << 0) | ((row & 2) << 1))) & 0x3;
for y_off in 0..8 {
let low = mem.peek_ppu(name + y_off).unwrap();
let high = mem.peek_ppu(name + y_off + 8).unwrap();
for bit in 0..8 {
frame.fill_rectangle(
Point::new(
x as f32 * 8. + 8. - bit as f32,
y as f32 * 8. + y_off as f32,
),
Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => self.palette.color(0),
(true, false) => self.palette.color(1 + 4 * palette),
(false, true) => self.palette.color(2 + 4 * palette),
(true, true) => self.palette.color(3 + 4 * palette),
// (false, false) => Color { r: 0, g: 0, b: 0 },
// (true, false) => Color {
// r: 64,
// g: 64,
// b: 64,
// },
// (false, true) => Color {
// r: 128,
// g: 128,
// b: 128,
// },
// (true, true) => Color {
// r: 255,
// g: 255,
// b: 255,
// },
},
);
}
}
// for
// let pat = mem.peek();
}
}
}
pub fn name_cursor_info(&self, mem: &Mapped, cursor: Point) -> Option<String> {
let x = (cursor.x / 8.) as usize;
let y = (cursor.y / 8.) as usize;
if x < 64 && y < 60 {
let row = y % 30;
let col = x % 32;
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as usize;
let attr = mem
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
.unwrap();
Some(format!(
"Row, Column: {}, {}
X, Y: {}, {}
Tilemap address: ${:04X}
Tile Index: ${:02X}
Tile Address (PPU): ${:04X}
Tile Address (CHR): ${:04X}
Palette Index {}
Palette Address ${:04X}
Attribute Address ${:04X}
Attribute data: ${:02X}
",
row,
col,
col * 8,
row * 8,
off + col + row * 32,
name,
name * 16
+ if self.background.second_pattern {
0x1000
} else {
0
},
name * 16
+ if self.background.second_pattern {
0x1000
} else {
0
},
(attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3,
((attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3) as usize * 4 + 0x3F00,
col / 4 + row / 4 * 8 + 0x3C0 + off,
attr,
))
} else {
None
}
}
pub fn render_pattern_tables<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
for y in 0..16 {
for x in 0..16 {
let name = (y * 16 + x) * 16;
for y_off in 0..8 {
let low = mem.peek_ppu(name + y_off).unwrap();
let high = mem.peek_ppu(name + y_off + 8).unwrap();
for bit in 0..8 {
frame.fill_rectangle(
Point::new(
x as f32 * 8. + 8. - bit as f32,
y as f32 * 8. + y_off as f32,
),
Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => Color { r: 0, g: 0, b: 0 },
(true, false) => Color {
r: 64,
g: 64,
b: 64,
},
(false, true) => Color {
r: 128,
g: 128,
b: 128,
},
(true, true) => Color {
r: 255,
g: 255,
b: 255,
},
},
);
}
}
}
}
for y in 0..16 {
for x in 0..16 {
let name = (y * 16 + x) * 16;
for y_off in 0..8 {
let low = mem.peek_ppu(name + y_off + 0x1000).unwrap();
let high = mem.peek_ppu(name + y_off + 8 + 0x1000).unwrap();
for bit in 0..8 {
frame.fill_rectangle(
Point::new(
x as f32 * 8. + 8. - bit as f32,
y as f32 * 8. + y_off as f32 + 130.,
),
Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => Color { r: 0, g: 0, b: 0 },
(true, false) => Color {
r: 64,
g: 64,
b: 64,
},
(false, true) => Color {
r: 128,
g: 128,
b: 128,
},
(true, true) => Color {
r: 255,
g: 255,
b: 255,
},
},
);
}
}
// for
// let pat = mem.peek();
}
}
}
pub fn pattern_cursor_info(&self, cursor: Point) -> Option<String> {
let x = (cursor.x / 8.) as usize;
let y = (cursor.y / 8.) as usize;
if x < 16 && y < 32 {
Some(format!(
"Tile address (PPU): {:04X}\nTile address (CHR): {:04X}\nIndex: {:02X}",
(y * 16 + x) * 16,
(y * 16 + x) * 16,
((y % 16) * 16 + x),
))
} else {
None
}
}
pub fn render_palette<R: Renderer>(&self, frame: &mut Frame<R>) {
for y in 0..8 {
for x in 0..4 {
frame.fill_rectangle(
Point::new(x as f32 * 10., y as f32 * 10.),
Size::new(10., 10.),
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
);
}
}
}
pub fn palette_cursor_info(&self, cursor: Point) -> Option<String> {
let x = (cursor.x / 10.) as usize;
let y = (cursor.y / 10.) as usize;
if x < 4 && y < 8 {
Some(format!(
"Index: {:02X}\nValue: {:02X}\nColor code: {}",
x + y * 4,
self.palette.ram[x + y * 4] & 0x3F,
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
))
} else {
None
}
}
}
}