Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s
1447 lines
48 KiB
Rust
1447 lines
48 KiB
Rust
use std::fmt;
|
|
|
|
use bytes::{Bytes, BytesMut};
|
|
|
|
use crate::mem::{Mapped, PpuMem, Value};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub struct Color {
|
|
pub r: u8,
|
|
pub g: u8,
|
|
pub b: u8,
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
pub struct RenderBuffer<const W: usize, const H: usize> {
|
|
buffer: Box<[Color]>,
|
|
raw_rgba: BytesMut,
|
|
}
|
|
|
|
impl<const W: usize, const H: usize> RenderBuffer<W, H> {
|
|
pub fn empty() -> Self {
|
|
Self {
|
|
buffer: vec![Color { r: 0, g: 0, b: 0 }; W * H].into_boxed_slice(),
|
|
raw_rgba: BytesMut::from_iter(vec![0; W * H * 4]),
|
|
}
|
|
}
|
|
|
|
pub fn write(&mut self, line: usize, pixel: usize, color: Color) {
|
|
assert!(line < H && pixel < W);
|
|
self.buffer[line * W + pixel] = color;
|
|
let pos = (line * W + pixel) * 4;
|
|
self.raw_rgba[pos] = color.r;
|
|
self.raw_rgba[pos + 1] = color.g;
|
|
self.raw_rgba[pos + 2] = color.b;
|
|
self.raw_rgba[pos + 3] = 0xFF;
|
|
}
|
|
pub fn read(&self, line: usize, pixel: usize) -> Color {
|
|
assert!(line < H && pixel < W);
|
|
self.buffer[line * W + pixel]
|
|
}
|
|
|
|
pub fn clone_from(&mut self, other: &Self) {
|
|
self.buffer.copy_from_slice(&other.buffer);
|
|
// self.raw_rgba.fr
|
|
self.raw_rgba.copy_from_slice(&other.raw_rgba);
|
|
}
|
|
|
|
pub fn image(&self) -> Bytes {
|
|
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 {
|
|
for x in 0..W {
|
|
if self.read(y, x) != other.read(y, x) {
|
|
panic!(
|
|
"Rendered Buffers do not match\n Mismatch at ({x}, {y})\n Left: {}\n Right: {}",
|
|
self.read(y, x),
|
|
other.read(y, x)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum PPUMMRegisters {
|
|
Palette,
|
|
}
|
|
|
|
bitfield::bitfield! {
|
|
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub struct SpriteAttrs(u8);
|
|
impl Debug;
|
|
vflip, set_vflip: 7;
|
|
hflip, set_hflip: 6;
|
|
priority, set_priority: 5;
|
|
palette, set_palette: 1, 0;
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
struct SpriteOutputUnit {
|
|
high: u8,
|
|
low: u8,
|
|
x_pos: u8,
|
|
y_pos: u8,
|
|
tile: u8,
|
|
attrs: SpriteAttrs,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum OamState {
|
|
ReadY,
|
|
ReadTile,
|
|
ReadAttrs,
|
|
ReadX,
|
|
// OverflowScan,
|
|
Wait,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct OAM {
|
|
mem: Vec<u8>,
|
|
secondary: Vec<u8>,
|
|
addr: u8,
|
|
count: u8,
|
|
state: OamState,
|
|
oam_read_buffer: u8,
|
|
edit_ver: usize,
|
|
sprite_output_units: Vec<SpriteOutputUnit>,
|
|
large_sprites: bool,
|
|
pub overflow: bool,
|
|
sprite_offset_0x1000: bool,
|
|
}
|
|
|
|
impl OAM {
|
|
fn new() -> Self {
|
|
Self {
|
|
mem: vec![0u8; 256],
|
|
addr: 0,
|
|
count: 0,
|
|
edit_ver: 0,
|
|
secondary: vec![0xFF; 32],
|
|
sprite_output_units: vec![SpriteOutputUnit::default(); 8],
|
|
state: OamState::ReadY,
|
|
oam_read_buffer: 0,
|
|
overflow: false,
|
|
sprite_offset_0x1000: false,
|
|
large_sprites: false,
|
|
}
|
|
}
|
|
|
|
fn ppu_cycle(&mut self, pixel: usize, line: usize, mem: &mut PpuMem<'_>) {
|
|
if pixel < 64 {
|
|
} else if pixel == 64 {
|
|
self.secondary.fill(0xFF);
|
|
self.state = OamState::ReadY;
|
|
self.addr = 0;
|
|
self.count = 0;
|
|
} else if pixel <= 256 {
|
|
// Fetch/evalute sprites for next line
|
|
if pixel % 2 == 1 && !matches!(self.state, OamState::Wait) {
|
|
self.oam_read_buffer = self.mem[self.addr as usize];
|
|
if self.addr == 255 {
|
|
self.addr = 0;
|
|
} else {
|
|
self.addr += 1;
|
|
}
|
|
} else {
|
|
match self.state {
|
|
OamState::ReadY => {
|
|
let l = self.oam_read_buffer as usize;
|
|
if self.count < 8 {
|
|
if l <= line && line < l + if self.large_sprites { 16 } else { 8 } {
|
|
self.secondary[self.count as usize * 4] = self.oam_read_buffer;
|
|
self.state = OamState::ReadTile;
|
|
} else {
|
|
if self.addr < 0xFD {
|
|
self.addr += 3;
|
|
} else {
|
|
self.state = OamState::Wait; // Should be Overflow scan...
|
|
}
|
|
}
|
|
} else {
|
|
self.state = OamState::Wait; // Should be Overflow scan...
|
|
}
|
|
}
|
|
OamState::ReadTile => {
|
|
self.secondary[self.count as usize * 4 + 1] = self.oam_read_buffer;
|
|
self.state = OamState::ReadAttrs;
|
|
}
|
|
OamState::ReadAttrs => {
|
|
self.secondary[self.count as usize * 4 + 2] = self.oam_read_buffer;
|
|
self.state = OamState::ReadX;
|
|
}
|
|
OamState::ReadX => {
|
|
self.secondary[self.count as usize * 4 + 3] = self.oam_read_buffer;
|
|
self.state = OamState::ReadY;
|
|
self.count += 1;
|
|
if self.count == 8 {
|
|
self.state = OamState::Wait; // Should be Overflow scan...
|
|
}
|
|
}
|
|
// OamState::OverflowScan => todo!(),
|
|
OamState::Wait => (),
|
|
}
|
|
}
|
|
} else if pixel <= 320 {
|
|
if pixel == 257 {
|
|
// Reset output units
|
|
self.sprite_output_units.fill(SpriteOutputUnit::default());
|
|
}
|
|
let run = pixel - 257;
|
|
if run / 8 < self.count as usize {
|
|
if run % 8 == 0 {
|
|
self.sprite_output_units[run / 8].y_pos = self.secondary[(run / 8) * 4];
|
|
} else if run % 8 == 1 {
|
|
self.sprite_output_units[run / 8].tile = self.secondary[(run / 8) * 4 + 1];
|
|
} else if run % 8 == 2 {
|
|
self.sprite_output_units[run / 8].attrs.0 = self.secondary[(run / 8) * 4 + 2];
|
|
} else if run % 8 == 3 {
|
|
self.sprite_output_units[run / 8].x_pos = self.secondary[(run / 8) * 4 + 3];
|
|
} else if run % 8 == 4 {
|
|
// } else if run % 8 == 5 {
|
|
// let off = line - self.sprite_output_units[run / 8].y_pos as usize;
|
|
// let off = if self.sprite_output_units[run / 8].attrs.vflip() {
|
|
// 7 - off
|
|
// } else {
|
|
// off
|
|
// };
|
|
// let addr = if self.sprite_offset_0x1000 {
|
|
// 0x1000
|
|
// } else {
|
|
// 0x0000
|
|
// } + 16 * self.sprite_output_units[run / 8].tile as u16
|
|
// + off as u16;
|
|
// self.sprite_output_units[run / 8].low = mem.read(addr).reg_map(|_, _| todo!());
|
|
} else if run % 8 == 6 {
|
|
} else if run % 8 == 5 || run % 8 == 7 {
|
|
let off = line - self.sprite_output_units[run / 8].y_pos as usize;
|
|
let addr = if self.large_sprites {
|
|
let off = if self.sprite_output_units[run / 8].attrs.vflip() {
|
|
15 - off
|
|
} else {
|
|
off
|
|
};
|
|
(if self.sprite_output_units[run / 8].tile & 1 == 1 {
|
|
0x1000
|
|
} else {
|
|
0x0000
|
|
} + 16 * (self.sprite_output_units[run / 8].tile & !1) as u16
|
|
+ off as u16 + if off > 7 { 8 } else { 0 })
|
|
} else {
|
|
let off = if self.sprite_output_units[run / 8].attrs.vflip() {
|
|
7 - off
|
|
} else {
|
|
off
|
|
};
|
|
(if self.sprite_offset_0x1000 {
|
|
0x1000
|
|
} else {
|
|
0x0000
|
|
} + 16 * self.sprite_output_units[run / 8].tile as u16
|
|
+ off as u16)
|
|
};
|
|
if run % 8 == 5 {
|
|
self.sprite_output_units[run / 8].low =
|
|
mem.read(addr).reg_map(|_, _| todo!());
|
|
} else {
|
|
self.sprite_output_units[run / 8].high =
|
|
mem.read(addr + 8).reg_map(|_, _| todo!());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns Some((palette index, above background))
|
|
fn pixel(&mut self, pixel: usize, _line: usize) -> Option<(u8, bool, bool)> {
|
|
self.sprite_output_units
|
|
.iter()
|
|
.enumerate()
|
|
.filter_map(|(i, s)| {
|
|
if pixel >= s.x_pos as usize && pixel < s.x_pos as usize + 8 && s.x_pos < 0xFF {
|
|
let bit_pos = pixel - s.x_pos as usize;
|
|
let bit_pos = if s.attrs.hflip() {
|
|
bit_pos
|
|
} else {
|
|
7 - bit_pos
|
|
};
|
|
// println!("Shifting: 0b{:08b} by {}", s.high, 8 - bit_pos);
|
|
let hi = (s.high >> bit_pos) & 1;
|
|
// println!("Shifting: 0b{:08b} by {}", s.low, bit_pos);
|
|
let lo = (s.low >> bit_pos) & 1;
|
|
let idx = (hi << 1) | lo;
|
|
if idx != 0 {
|
|
Some((
|
|
idx + s.attrs.palette() * 4 + 0x10,
|
|
!s.attrs.priority(),
|
|
i == 0,
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.next()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Background {
|
|
/// Current vram address, 15 bits
|
|
/// yyy NN YYYYY XXXXX (0yyy NNYY YYYX XXXX)
|
|
pub v: u16,
|
|
/// Temp vram address, 15 bits
|
|
/// yyy NN YYYYY XXXXX
|
|
pub t: u16,
|
|
/// Fine X control, 3 bits
|
|
pub x: u8,
|
|
/// Whether this is the first or second write to PPUSCROLL
|
|
/// When false, writes to x
|
|
pub w: bool,
|
|
|
|
copy_v: u8,
|
|
|
|
/// When true, v is incremented by 32 after each read
|
|
pub vram_column: bool,
|
|
pub second_pattern: bool,
|
|
|
|
pub cur_nametable: u8,
|
|
pub next_attr: u8,
|
|
pub cur_high: u8,
|
|
pub cur_low: u8,
|
|
pub cur_shift_high: u32,
|
|
pub cur_shift_low: u32,
|
|
pub cur_attr_shift_high: u32,
|
|
pub cur_attr_shift_low: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Mask {
|
|
pub grayscale: bool,
|
|
pub background_on_left_edge: bool,
|
|
pub sprites_on_left_edge: bool,
|
|
pub enable_background: bool,
|
|
pub enable_sprites: bool,
|
|
pub em_red: bool,
|
|
pub em_green: bool,
|
|
pub em_blue: bool,
|
|
}
|
|
|
|
const COLORS: &'static [Color; 0b100_0000] = &[
|
|
Color {
|
|
r: 0x66,
|
|
g: 0x66,
|
|
b: 0x66,
|
|
}, // 00
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x2A,
|
|
b: 0x88,
|
|
}, // 01
|
|
Color {
|
|
r: 0x14,
|
|
g: 0x12,
|
|
b: 0xA7,
|
|
}, // 02
|
|
Color {
|
|
r: 0x3B,
|
|
g: 0x00,
|
|
b: 0xA4,
|
|
}, // 03
|
|
Color {
|
|
r: 0x5C,
|
|
g: 0x00,
|
|
b: 0x7E,
|
|
}, // 04
|
|
Color {
|
|
r: 0x6E,
|
|
g: 0x00,
|
|
b: 0x40,
|
|
}, // 05
|
|
Color {
|
|
r: 0x6C,
|
|
g: 0x06,
|
|
b: 0x00,
|
|
}, // 06
|
|
Color {
|
|
r: 0x56,
|
|
g: 0x1D,
|
|
b: 0x00,
|
|
}, // 07
|
|
Color {
|
|
r: 0x33,
|
|
g: 0x35,
|
|
b: 0x00,
|
|
}, // 08
|
|
Color {
|
|
r: 0x0B,
|
|
g: 0x48,
|
|
b: 0x00,
|
|
}, // 09
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x52,
|
|
b: 0x00,
|
|
}, // 0A
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x4F,
|
|
b: 0x08,
|
|
}, // 0B
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x40,
|
|
b: 0x4D,
|
|
}, // 0C
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 0D
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 0E
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 0F
|
|
Color {
|
|
r: 0xAD,
|
|
g: 0xAD,
|
|
b: 0xAD,
|
|
}, // 10
|
|
Color {
|
|
r: 0x15,
|
|
g: 0x5F,
|
|
b: 0xD9,
|
|
}, // 11
|
|
Color {
|
|
r: 0x42,
|
|
g: 0x40,
|
|
b: 0xFF,
|
|
}, // 12
|
|
Color {
|
|
r: 0x75,
|
|
g: 0x27,
|
|
b: 0xFE,
|
|
}, // 13
|
|
Color {
|
|
r: 0xA0,
|
|
g: 0x1A,
|
|
b: 0xCC,
|
|
}, // 14
|
|
Color {
|
|
r: 0xB7,
|
|
g: 0x1E,
|
|
b: 0x7B,
|
|
}, // 15
|
|
Color {
|
|
r: 0xB5,
|
|
g: 0x31,
|
|
b: 0x20,
|
|
}, // 16
|
|
Color {
|
|
r: 0x99,
|
|
g: 0x4E,
|
|
b: 0x00,
|
|
}, // 17
|
|
Color {
|
|
r: 0x6B,
|
|
g: 0x6D,
|
|
b: 0x00,
|
|
}, // 18
|
|
Color {
|
|
r: 0x38,
|
|
g: 0x87,
|
|
b: 0x00,
|
|
}, // 19
|
|
Color {
|
|
r: 0x0C,
|
|
g: 0x93,
|
|
b: 0x00,
|
|
}, // 1A
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x8F,
|
|
b: 0x32,
|
|
}, // 1B
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x7C,
|
|
b: 0x8D,
|
|
}, // 1C
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 1D
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 1E
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 1F
|
|
Color {
|
|
r: 0xFF,
|
|
g: 0xFE,
|
|
b: 0xFF,
|
|
}, // 20
|
|
Color {
|
|
r: 0x64,
|
|
g: 0xB0,
|
|
b: 0xFF,
|
|
}, // 21
|
|
Color {
|
|
r: 0x92,
|
|
g: 0x90,
|
|
b: 0xFF,
|
|
}, // 22
|
|
Color {
|
|
r: 0xC6,
|
|
g: 0x76,
|
|
b: 0xFF,
|
|
}, // 23
|
|
Color {
|
|
r: 0xF3,
|
|
g: 0x6A,
|
|
b: 0xFF,
|
|
}, // 24
|
|
Color {
|
|
r: 0xFE,
|
|
g: 0x6E,
|
|
b: 0xCC,
|
|
}, // 25
|
|
Color {
|
|
r: 0xFE,
|
|
g: 0x81,
|
|
b: 0x70,
|
|
}, // 26
|
|
Color {
|
|
r: 0xEA,
|
|
g: 0x9E,
|
|
b: 0x22,
|
|
}, // 27
|
|
Color {
|
|
r: 0xBC,
|
|
g: 0xBE,
|
|
b: 0x00,
|
|
}, // 28
|
|
Color {
|
|
r: 0x88,
|
|
g: 0xD8,
|
|
b: 0x00,
|
|
}, // 29
|
|
Color {
|
|
r: 0x5C,
|
|
g: 0xE4,
|
|
b: 0x30,
|
|
}, // 2A
|
|
Color {
|
|
r: 0x45,
|
|
g: 0xE0,
|
|
b: 0x82,
|
|
}, // 2B
|
|
Color {
|
|
r: 0x48,
|
|
g: 0xCD,
|
|
b: 0xDE,
|
|
}, // 2C
|
|
Color {
|
|
r: 0x4F,
|
|
g: 0x4F,
|
|
b: 0x4F,
|
|
}, // 2D
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 2E
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 2F
|
|
Color {
|
|
r: 0xFF,
|
|
g: 0xFE,
|
|
b: 0xFF,
|
|
}, // 30
|
|
Color {
|
|
r: 0xC0,
|
|
g: 0xDF,
|
|
b: 0xFF,
|
|
}, // 31
|
|
Color {
|
|
r: 0xD3,
|
|
g: 0xD2,
|
|
b: 0xFF,
|
|
}, // 32
|
|
Color {
|
|
r: 0xE8,
|
|
g: 0xC8,
|
|
b: 0xFF,
|
|
}, // 33
|
|
Color {
|
|
r: 0xFB,
|
|
g: 0xC2,
|
|
b: 0xFF,
|
|
}, // 34
|
|
Color {
|
|
r: 0xFE,
|
|
g: 0xC4,
|
|
b: 0xEA,
|
|
}, // 35
|
|
Color {
|
|
r: 0xFE,
|
|
g: 0xCC,
|
|
b: 0xC5,
|
|
}, // 36
|
|
Color {
|
|
r: 0xF7,
|
|
g: 0xD8,
|
|
b: 0xA5,
|
|
}, // 37
|
|
Color {
|
|
r: 0xE4,
|
|
g: 0xE5,
|
|
b: 0x94,
|
|
}, // 38
|
|
Color {
|
|
r: 0xCF,
|
|
g: 0xEF,
|
|
b: 0x96,
|
|
}, // 39
|
|
Color {
|
|
r: 0xBD,
|
|
g: 0xF4,
|
|
b: 0xAB,
|
|
}, // 3A
|
|
Color {
|
|
r: 0xB3,
|
|
g: 0xF3,
|
|
b: 0xCC,
|
|
}, // 3B
|
|
Color {
|
|
r: 0xB5,
|
|
g: 0xEB,
|
|
b: 0xF2,
|
|
}, // 3C
|
|
Color {
|
|
r: 0xB8,
|
|
g: 0xB8,
|
|
b: 0xB8,
|
|
}, // 3D
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 3E
|
|
Color {
|
|
r: 0x00,
|
|
g: 0x00,
|
|
b: 0x00,
|
|
}, // 3F
|
|
];
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Palette {
|
|
colors: &'static [Color; 0x40],
|
|
ram: [u8; 0x20],
|
|
}
|
|
|
|
impl Palette {
|
|
pub fn color(&self, idx: u8) -> Color {
|
|
debug_assert!(idx < 0x20, "Palette index out of range");
|
|
self.colors[(self.ram[idx as usize] & 0x3F) as usize]
|
|
}
|
|
|
|
pub fn ram(&self, offset: u8) -> u8 {
|
|
self.ram[offset as usize]
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct PPU {
|
|
// registers: PPURegisters,
|
|
pub frame_count: usize,
|
|
nmi_enabled: bool,
|
|
// nmi_waiting: bool,
|
|
pub even: bool,
|
|
pub scanline: usize,
|
|
pub pixel: usize,
|
|
|
|
vram_buffer: u8,
|
|
|
|
pub mask: Mask,
|
|
pub vblank: bool,
|
|
pub sprite_zero_hit: bool,
|
|
|
|
pub palette: Palette,
|
|
pub background: Background,
|
|
pub oam: OAM,
|
|
pub render_buffer: RenderBuffer<256, 240>,
|
|
pub dbg_int: bool,
|
|
pub cycle: usize,
|
|
}
|
|
|
|
bitfield::bitfield! {
|
|
pub struct PPUStatus(u8);
|
|
impl Debug;
|
|
vblank, set_vblank: 7;
|
|
sprite0, set_sprite0: 6;
|
|
sprite_overflow, set_sprite_overflow: 5;
|
|
}
|
|
|
|
impl std::fmt::Debug for PPU {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"PPU: f {}, s {}, p {}",
|
|
self.frame_count, self.scanline, self.pixel
|
|
)
|
|
}
|
|
}
|
|
|
|
impl PPU {
|
|
// pub fn with_chr_rom(rom: &[u8], mapper: Mapper) -> Self {
|
|
// todo!()
|
|
// }
|
|
pub fn init() -> Self {
|
|
Self {
|
|
cycle: 25,
|
|
dbg_int: false,
|
|
|
|
mask: Mask {
|
|
grayscale: false,
|
|
background_on_left_edge: false,
|
|
sprites_on_left_edge: false,
|
|
enable_background: false,
|
|
enable_sprites: false,
|
|
em_red: false,
|
|
em_green: false,
|
|
em_blue: false,
|
|
},
|
|
palette: Palette {
|
|
colors: COLORS,
|
|
ram: [
|
|
0x09, 0x01, 0x00, 0x01, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C,
|
|
0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
|
|
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|
],
|
|
},
|
|
vblank: false,
|
|
sprite_zero_hit: false,
|
|
frame_count: 0,
|
|
nmi_enabled: false,
|
|
even: false,
|
|
scanline: 0,
|
|
pixel: 25,
|
|
render_buffer: RenderBuffer::empty(),
|
|
background: Background {
|
|
v: 0,
|
|
t: 0,
|
|
x: 0,
|
|
w: false,
|
|
copy_v: 0,
|
|
vram_column: false,
|
|
second_pattern: false,
|
|
cur_high: 0,
|
|
cur_low: 0,
|
|
cur_shift_high: 0,
|
|
cur_shift_low: 0,
|
|
cur_attr_shift_high: 0,
|
|
cur_attr_shift_low: 0,
|
|
cur_nametable: 0,
|
|
next_attr: 0,
|
|
},
|
|
oam: OAM::new(),
|
|
vram_buffer: 0,
|
|
}
|
|
}
|
|
pub fn reset(&mut self) {
|
|
*self = Self::init();
|
|
}
|
|
|
|
pub fn rendering_enabled(&self) -> bool {
|
|
self.mask.enable_background || self.mask.enable_sprites
|
|
}
|
|
|
|
pub fn read_reg(&mut self, mem: &mut PpuMem<'_>, offset: u16) -> u8 {
|
|
match offset {
|
|
0 => panic!("ppuctrl is write-only"),
|
|
1 => panic!("ppumask is write-only"),
|
|
2 => {
|
|
let tmp = if self.vblank { 0b1000_0000 } else { 0 }
|
|
| if self.sprite_zero_hit { 0b0100_0000 } else { 0 }
|
|
| if self.oam.overflow { 0b0010_0000 } else { 0 };
|
|
self.vblank = false;
|
|
self.background.w = false;
|
|
tmp
|
|
}
|
|
3 => panic!("oamaddr is write-only"),
|
|
4 => self.oam.mem[self.oam.addr as usize],
|
|
5 => panic!("ppuscroll is write-only"),
|
|
6 => panic!("ppuaddr is write-only"),
|
|
7 => {
|
|
let val = match mem.read(self.background.v) {
|
|
Value::Value(v) => {
|
|
let val = self.vram_buffer;
|
|
self.vram_buffer = v;
|
|
val
|
|
}
|
|
Value::Register {
|
|
reg: PPUMMRegisters::Palette,
|
|
offset,
|
|
} => self.palette.ram[offset as usize],
|
|
};
|
|
self.increment_v();
|
|
val
|
|
}
|
|
// 7 => self.registers.data,
|
|
_ => panic!("No register at {:02X}", offset),
|
|
}
|
|
}
|
|
pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, mut val: u8) {
|
|
match offset {
|
|
0x00 => {
|
|
self.background.t =
|
|
(self.background.t & !0b0000_1100_0000_0000) | (((val & 0b11) as u16) << 10);
|
|
self.background.vram_column = val & 0b0000_0100 != 0;
|
|
self.oam.sprite_offset_0x1000 = val & 0b0000_1000 != 0;
|
|
self.background.second_pattern = val & 0b0001_0000 != 0;
|
|
self.oam.large_sprites = val & 0b0010_0000 != 0;
|
|
if val & 0b0100_0000 != 0 {
|
|
println!(
|
|
"WARNING: Bit 6 set in PPUCTRL - may cause damage on physical hardware"
|
|
);
|
|
}
|
|
self.nmi_enabled = val & 0b1000_0000 != 0;
|
|
}
|
|
0x01 => {
|
|
// self.dbg_int = true;
|
|
self.mask.grayscale = val & 0b0000_0001 != 0;
|
|
self.mask.background_on_left_edge = val & 0b0000_0010 != 0;
|
|
self.mask.sprites_on_left_edge = val & 0b0000_0100 != 0;
|
|
self.mask.enable_background = val & 0b0000_1000 != 0;
|
|
self.mask.enable_sprites = val & 0b0001_0000 != 0;
|
|
self.mask.em_red = val & 0b0010_0000 != 0;
|
|
self.mask.em_green = val & 0b0100_0000 != 0;
|
|
self.mask.em_blue = val & 0b1000_0000 != 0;
|
|
// todo!("Mask: {:02X}", val)
|
|
}
|
|
0x02 => {
|
|
todo!("Unable to write to PPU status")
|
|
}
|
|
0x03 => self.oam.addr = val,
|
|
0x04 => {
|
|
if self.oam.addr % 4 == 2 {
|
|
val &= 0b11100011;
|
|
}
|
|
self.oam.mem[self.oam.addr as usize] = val;
|
|
self.oam.addr = self.oam.addr.wrapping_add(1);
|
|
self.oam.edit_ver += 1;
|
|
}
|
|
0x05 => {
|
|
if self.background.w {
|
|
// Is y scroll
|
|
self.background.t = (self.background.t & 0b0000_1100_0001_1111)
|
|
| (((val as u16) << 2) & 0b0000_0011_1110_0000)
|
|
| (((val as u16) << 12) & 0b0111_0000_0000_0000);
|
|
self.background.w = false;
|
|
} else {
|
|
// Is x scroll
|
|
self.background.x = val & 0b111; // Lowest three bits
|
|
self.background.t =
|
|
(self.background.t & 0b0111_1111_1110_0000) | (val as u16 >> 3);
|
|
self.background.w = true;
|
|
}
|
|
}
|
|
0x06 => {
|
|
if self.background.w {
|
|
self.background.t =
|
|
u16::from_le_bytes([val, self.background.t.to_le_bytes()[1]]);
|
|
self.background.w = false;
|
|
self.background.copy_v = 2; // Set t to be copied to v in a pixel or so
|
|
} else {
|
|
self.background.t =
|
|
u16::from_le_bytes([self.background.t.to_le_bytes()[0], val & 0b0011_1111]);
|
|
self.background.w = true;
|
|
}
|
|
}
|
|
0x07 => {
|
|
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
|
|
mem.write(self.background.v, val, |r, mut o, v| match r {
|
|
PPUMMRegisters::Palette => {
|
|
// println!("Writing {:02X} to {:02X}", v & 0x3F, o);
|
|
if o % 4 == 0 {
|
|
o = o & !0b1_0000;
|
|
self.palette.ram[o as usize] = v & 0x3F;
|
|
self.palette.ram[(o | 0b1_0000) as usize] = v & 0x3F;
|
|
} else {
|
|
self.palette.ram[o as usize] = v & 0x3F;
|
|
}
|
|
}
|
|
});
|
|
self.increment_v();
|
|
}
|
|
_ => panic!("No register at {:02X}", offset),
|
|
}
|
|
}
|
|
/// Apply either row wise or column wise increment
|
|
fn increment_v(&mut self) {
|
|
if self.background.vram_column {
|
|
// if self.background.v
|
|
self.background.v += 32;
|
|
} else {
|
|
self.background.v += 1;
|
|
}
|
|
}
|
|
|
|
pub fn run_one_clock_cycle(&mut self, mem: &mut PpuMem<'_>) -> bool {
|
|
self.cycle += 1;
|
|
self.pixel += 1;
|
|
if self.background.copy_v > 0 {
|
|
self.background.copy_v -= 1;
|
|
if self.background.copy_v == 0 {
|
|
self.background.v = self.background.t;
|
|
}
|
|
}
|
|
if self.scanline == 261
|
|
&& (self.pixel == 341 || (self.pixel == 340 && self.even && self.rendering_enabled()))
|
|
{
|
|
self.scanline = 0;
|
|
self.pixel = 0;
|
|
self.even = !self.even;
|
|
self.frame_count += 1;
|
|
}
|
|
if self.pixel == 341 {
|
|
self.pixel = 0;
|
|
self.scanline += 1;
|
|
}
|
|
if self.mask.enable_background || self.mask.enable_sprites {
|
|
if self.scanline < 240 || self.scanline == 261 {
|
|
if self.pixel == 257 {
|
|
self.background.v = (self.background.v & 0b0111_1011_1110_0000)
|
|
| (self.background.t & 0b0000_0100_0001_1111);
|
|
}
|
|
if self.pixel >= 280 && self.pixel <= 304 && self.scanline == 261 {
|
|
self.background.v = (self.background.v & 0b0000_0100_0001_1111)
|
|
| (self.background.t & 0b0111_1011_1110_0000);
|
|
}
|
|
if self.scanline != 261 {
|
|
self.oam.ppu_cycle(self.pixel, self.scanline, mem);
|
|
}
|
|
if self.pixel == 0 {
|
|
if self.scanline == 1 {
|
|
// let h_scroll_offset = self.background.x as usize + ((self.background.t as usize & 0b11) << 3);
|
|
// dbg!(h_scroll_offset);
|
|
// dbg!((self.scanline, &self.oam.sprite_output_units[0]));
|
|
// dbg!(&self.oam.secondary);
|
|
}
|
|
// self.dbg_int = true;
|
|
// idle cycle
|
|
} else if self.pixel < 257 || self.pixel > 320 {
|
|
// self.dbg_int = true;
|
|
// const POS: u32 = 1 << 15;
|
|
let bg_color = if self.mask.enable_background {
|
|
let bit_pos = 15 - self.background.x;
|
|
// Determine background color
|
|
let a = (self.background.cur_shift_high >> bit_pos) & 1;
|
|
let b = (self.background.cur_shift_low >> bit_pos) & 1;
|
|
let val = (a << 1) | b;
|
|
debug_assert!(val < 4);
|
|
|
|
let a = (self.background.cur_attr_shift_high >> bit_pos) & 1;
|
|
let b = (self.background.cur_attr_shift_low >> bit_pos) & 1;
|
|
let palette = ((a << 1) | b) as u8;
|
|
debug_assert!(palette < 4);
|
|
val as u8 + if val != 0 { palette * 4 } else { 0 }
|
|
} else {
|
|
0
|
|
};
|
|
|
|
if self.scanline < 240 && self.pixel < 257 {
|
|
let color = if let Some((sp_color, above, is_sprite_zero)) = self
|
|
.mask
|
|
.enable_sprites
|
|
.then_some(())
|
|
.and(self.oam.pixel(self.pixel, self.scanline))
|
|
{
|
|
if is_sprite_zero && sp_color != 0 && bg_color != 0 {
|
|
self.sprite_zero_hit = true;
|
|
}
|
|
if sp_color == 0 || (bg_color != 0 && !above) {
|
|
bg_color
|
|
} else {
|
|
sp_color
|
|
}
|
|
} else {
|
|
bg_color
|
|
};
|
|
// Write to screen
|
|
self.render_buffer.write(
|
|
self.scanline,
|
|
self.pixel - 1,
|
|
self.palette.color(color),
|
|
);
|
|
}
|
|
if self.pixel < 337 {
|
|
self.background.cur_shift_high <<= 1;
|
|
self.background.cur_shift_low <<= 1;
|
|
self.background.cur_attr_shift_high <<= 1;
|
|
self.background.cur_attr_shift_low <<= 1;
|
|
}
|
|
|
|
if self.scanline < 240 || self.scanline == 261 {
|
|
if self.pixel <= 260 || self.pixel >= 321 {
|
|
if self.pixel % 8 == 2 {
|
|
// Name table fetch
|
|
// let addr = 0x2000 + self.pixel / 8 + self.scanline / 8;
|
|
let addr = 0x2000 | (self.background.v & 0x0FFF);
|
|
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
|
let val = mem.read(addr).reg_map(|_, _| 0);
|
|
self.background.cur_nametable = val;
|
|
} else if self.pixel % 8 == 4 {
|
|
// Attr table fetch
|
|
// let addr = 0x23C0 + self.pixel / 16 + self.scanline / 16;
|
|
let addr = 0x23C0
|
|
| (self.background.v & 0x0C00)
|
|
| ((self.background.v >> 4) & 0x38)
|
|
| ((self.background.v >> 2) & 0x07);
|
|
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
|
// assert_eq!(addr, addr_2);
|
|
let val = mem
|
|
.read(addr)
|
|
.reg_map(|_, offset| self.palette.ram(offset as u8));
|
|
self.background.next_attr = val;
|
|
} else if self.pixel % 8 == 6 {
|
|
// BG pattern low
|
|
let addr = self.background.cur_nametable as u16 * 16
|
|
+ ((self.background.v & 0x7000) >> 12)
|
|
// + (self.scanline % 8) as u16
|
|
+ if self.background.second_pattern {
|
|
0x1000
|
|
} else {
|
|
0
|
|
};
|
|
let val = mem.read(addr).reg_map(|_, _| 0);
|
|
self.background.cur_low = val;
|
|
} else if self.pixel % 8 == 0 && self.pixel > 0 {
|
|
let addr = self.background.cur_nametable as u16 * 16
|
|
+ 8
|
|
+ ((self.background.v & 0x7000) >> 12)
|
|
// (self.scanline % 8) as u16
|
|
+ if self.background.second_pattern {
|
|
0x1000
|
|
} else {
|
|
0
|
|
};
|
|
let val = mem.read(addr).reg_map(|_, _| todo!());
|
|
self.background.cur_high = val;
|
|
self.background.cur_shift_low |= self.background.cur_low as u32;
|
|
self.background.cur_shift_high |= self.background.cur_high as u32;
|
|
let h_off = (self.background.v >> 1) % 2;
|
|
let v_off = (self.background.v >> 6) % 2;
|
|
let off = h_off * 2 + v_off * 4;
|
|
let palette = (self.background.next_attr >> off) & 0b11;
|
|
self.background.cur_attr_shift_low |=
|
|
if palette & 0b01 == 0 { 0 } else { 0xFF };
|
|
self.background.cur_attr_shift_high |=
|
|
if palette & 0b10 == 0 { 0 } else { 0xFF };
|
|
// Inc horizontal
|
|
if self.background.v & 0x1F == 31 {
|
|
self.background.v = (self.background.v & !0x1F) ^ 0x400;
|
|
} else {
|
|
self.background.v += 1;
|
|
}
|
|
// Inc vertical
|
|
if self.pixel == 256 {
|
|
// && self.scanline % 4 == 0
|
|
if self.background.v & 0x7000 != 0x7000 {
|
|
self.background.v += 0x1000;
|
|
} else {
|
|
self.background.v &= !0x7000;
|
|
let mut y = (self.background.v & 0x03E0) >> 5;
|
|
if y == 29 {
|
|
y = 0;
|
|
self.background.v ^= 0x0800
|
|
} else if y == 31 {
|
|
y = 0;
|
|
} else {
|
|
y += 1;
|
|
}
|
|
self.background.v =
|
|
(self.background.v & !0x03E0) | (y << 5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if self.scanline == 261 && self.pixel == 1 {
|
|
self.vblank = false;
|
|
self.sprite_zero_hit = false;
|
|
self.oam.overflow = false;
|
|
}
|
|
if self.scanline == 241 && self.pixel == 1 {
|
|
self.vblank = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pub fn nmi_on_vblank(&self) -> bool {
|
|
self.nmi_enabled
|
|
}
|
|
pub fn peek_nmi(&self) -> bool {
|
|
self.vblank && self.nmi_enabled
|
|
}
|
|
pub fn nmi_waiting(&mut self) -> bool {
|
|
self.vblank && self.nmi_enabled
|
|
}
|
|
pub fn peek_irq(&self) -> bool {
|
|
false
|
|
}
|
|
pub fn irq_waiting(&mut self) -> bool {
|
|
false
|
|
}
|
|
pub fn peek_oam(&self, addr: u8) -> u8 {
|
|
self.oam.mem[addr as usize]
|
|
}
|
|
pub fn oam_edit_ver(&self) -> usize {
|
|
self.oam.edit_ver
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn ppu_registers() {
|
|
// let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_header(0));
|
|
let mut ppu = PPU::init();
|
|
// let mut mem = MemoryMap::new(vec![Segment::ram("")]);
|
|
let mut mem = Mapped::test_ram();
|
|
let mut mem = PpuMem::new(&mut mem);
|
|
assert_eq!(ppu.background.v, 0);
|
|
assert_eq!(ppu.background.t, 0);
|
|
assert_eq!(ppu.background.x, 0);
|
|
assert_eq!(ppu.background.w, false);
|
|
ppu.write_reg(&mut mem, 0, 0);
|
|
assert_eq!(ppu.background.v, 0);
|
|
ppu.write_reg(&mut mem, 0, 0b11);
|
|
assert_eq!(
|
|
ppu.background.t, 0b0001100_00000000,
|
|
"Actual: {:016b}",
|
|
ppu.background.t
|
|
);
|
|
assert_eq!(ppu.background.w, false);
|
|
|
|
ppu.write_reg(&mut mem, 5, 0x7D);
|
|
assert_eq!(
|
|
ppu.background.t, 0b0001100_00001111,
|
|
"Actual: {:016b}",
|
|
ppu.background.t
|
|
);
|
|
assert_eq!(ppu.background.x, 0b101);
|
|
assert_eq!(ppu.background.w, true);
|
|
ppu.write_reg(&mut mem, 5, 0x5E);
|
|
assert_eq!(
|
|
ppu.background.t, 0b1101101_01101111,
|
|
"Actual: {:016b}",
|
|
ppu.background.t
|
|
);
|
|
assert_eq!(ppu.background.x, 0b101);
|
|
assert_eq!(ppu.background.w, false);
|
|
|
|
ppu.write_reg(&mut mem, 5, 0x7D);
|
|
assert_eq!(
|
|
ppu.background.t, 0b1101101_01101111,
|
|
"Actual: {:016b}",
|
|
ppu.background.t
|
|
);
|
|
assert_eq!(ppu.background.x, 0b101);
|
|
assert_eq!(ppu.background.w, true);
|
|
ppu.read_reg(&mut mem, 2);
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|