Files
nes-emu/src/ppu.rs

1164 lines
35 KiB
Rust

use std::fmt;
use iced::{
Point, Size,
advanced::graphics::geometry::Renderer,
widget::canvas::{Fill, Frame},
};
use crate::{
hex_view::Memory,
mem::{Mapper, MemoryMap, Segment},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Color {
pub r: u8,
pub g: u8,
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)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RenderBuffer<const W: usize, const H: usize> {
buffer: Box<[Color]>,
}
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(),
}
}
pub fn write(&mut self, line: usize, pixel: usize, color: Color) {
assert!(line < H && pixel < W);
self.buffer[line * W + pixel] = color;
}
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);
}
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)
);
}
}
}
// }
}
}
pub(crate) enum PPUMMRegisters {
Palette,
}
pub struct OAM {
mem: Vec<u8>,
addr: u8,
}
enum BackgroundState {
NameTableBytePre,
NameTableByte,
AttrTableBytePre,
AttrTableByte,
PatternTableTileLowPre,
PatternTableTileLow,
PatternTableTileHighPre,
PatternTableTileHigh,
}
pub struct Background {
/// Current vram address, 15 bits
pub v: u16,
/// Temp vram address, 15 bits
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,
/// When true, v is incremented by 32 after each read
pub vram_column: bool,
pub second_pattern: bool,
state: BackgroundState,
pub cur_nametable: u8,
pub cur_attr: u8,
pub next_attr: u8,
pub next_attr_2: u8,
pub cur_high: u8,
pub cur_low: u8,
pub cur_shift_high: u32,
pub cur_shift_low: u32,
}
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
];
pub struct Palette {
colors: &'static [Color; 0x40],
ram: [u8; 0x20],
}
impl Palette {
pub fn color(&self, idx: u8, palette: u8) -> Color {
debug_assert!(idx < 0x20, "Palette index out of range");
self.colors[(self.ram[idx as usize + palette as usize * 4] & 0x3F) as usize]
}
}
pub struct PPU {
// registers: PPURegisters,
pub frame_count: usize,
nmi_enabled: bool,
// nmi_waiting: bool,
pub even: bool,
pub scanline: usize,
pub pixel: usize,
pub mask: Mask,
pub vblank: bool,
pub(crate) memory: MemoryMap<PPUMMRegisters>,
palette: Palette,
pub background: Background,
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 {
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,
frame_count: 0,
nmi_enabled: false,
// nmi_waiting: false,
even: false,
scanline: 0,
pixel: 25,
render_buffer: RenderBuffer::empty(),
memory: mapper.ppu_map(rom),
background: Background {
v: 0,
t: 0,
x: 0,
w: false,
vram_column: false,
second_pattern: false,
state: BackgroundState::NameTableBytePre,
cur_high: 0,
cur_low: 0,
cur_shift_high: 0,
cur_shift_low: 0,
cur_nametable: 0,
cur_attr: 0,
next_attr: 0,
next_attr_2: 0,
},
oam: OAM {
mem: vec![0u8; 256],
addr: 0,
},
}
}
pub fn reset(&mut self) {
*self = Self {
memory: std::mem::replace(&mut self.memory, MemoryMap::new(vec![])),
..Self::with_chr_rom(&[], Mapper::from_flags(0))
};
}
pub fn rendering_enabled(&self) -> bool {
self.mask.enable_background || self.mask.enable_sprites
}
pub fn read_reg(&mut self, 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 };
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 => {
// println!("Updating v for ppudata read");
let val = self
.memory
.read(self.background.v)
.reg_map(|a, off| match a {
PPUMMRegisters::Palette => self.palette.ram[off as usize],
});
// if self.background
self.increment_v();
val
}
// 7 => self.registers.data,
_ => panic!("No register at {:02X}", offset),
}
}
pub fn write_reg(&mut self, offset: u16, val: u8) {
match offset {
0x00 => {
self.nmi_enabled = val & 0b1000_0000 != 0;
self.background.t =
(self.background.t & 0b0001_1000_0000_0000) | (((val & 0b11) as u16) << 10);
self.background.vram_column = val & 0b0000_0100 != 0;
self.background.second_pattern = val & 0b0001_0000 != 0;
// TODO: other control fields
}
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")
// TODO: ppu status
}
0x03 => self.oam.addr = val,
0x04 => {
self.oam.mem[self.oam.addr as usize] = val;
self.oam.addr = self.oam.addr.wrapping_add(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 => {
// TODO: ppu addr
if self.background.w {
self.background.v =
u16::from_le_bytes([val, self.background.v.to_le_bytes()[1]]);
self.background.w = false;
} else {
self.background.v =
u16::from_le_bytes([self.background.v.to_le_bytes()[0], val & 0b0011_1111]);
self.background.w = true;
}
// println!("Updating v for ppuaddr write: to {:04X}", self.background.v);
// self.dbg_int = true;
// todo!("PPUADDR write")
}
0x07 => {
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
self.memory
.write(self.background.v, val, |r, o, v| match r {
PPUMMRegisters::Palette => {
// println!("Writing {:02X} to {:02X}", v & 0x3F, o);
self.palette.ram[o as usize] = v & 0x3F;
}
});
self.increment_v();
// self.background.v += 1; // TODO: implement inc behavior
}
_ => 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;
}
// self.background.v = self
// .background
// .v
// .wrapping_add(if self.background.vram_column { 32 } else { 1 });
}
// pub fn write_oamdma(&mut self, val: u8) {
// // TODO: OAM high addr
// }
pub fn run_one_clock_cycle(&mut self) -> bool {
self.cycle += 1;
self.pixel += 1;
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;
}
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.pixel > 280 && self.pixel < 320 {
self.background.cur_shift_high <<= 1;
self.background.cur_shift_low <<= 1;
}
// TODO
if self.pixel == 0 {
// self.dbg_int = true;
// idle cycle
} else if self.pixel < 257 || self.pixel > 320 {
// self.dbg_int = true;
const POS: u32 = 1 << 15;
// Determine background color
let a = self.background.cur_shift_high & POS;
let b = self.background.cur_shift_low & POS;
let val = (a >> 14) | (b >> 15);
debug_assert!(val < 4);
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 = (self.background.cur_attr >> off) & 0x3;
if self.scanline < 240 && self.pixel < 257 {
// Write to screen
self.render_buffer.write(
self.scanline,
self.pixel - 1,
self.palette
.color(val as u8, if val != 0 { palette } else { 0 }), // self.palette.colors[val as usize],
); // TODO: this should come from shift registers
}
if self.pixel < 337 {
self.background.cur_shift_high <<= 1;
self.background.cur_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 = self.memory.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 = self.memory.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads
self.background.next_attr_2 = 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 = self.memory.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 = self.memory.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;
self.background.cur_attr = self.background.next_attr;
self.background.next_attr = self.background.next_attr_2;
// 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);
}
}
}
}
}
} else {
// TODO: Sprite fetches
}
}
}
if self.scanline == 261 && self.pixel == 1 {
self.vblank = false;
// TODO: clear sprite 0 & sprite overflow
}
if self.scanline == 241 && self.pixel == 1 {
self.vblank = true;
// self.nmi_waiting = self.nmi_enabled;
self.frame_count += 1;
self.background.state = BackgroundState::NameTableBytePre;
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
// if self.nmi_waiting {
// self.nmi_waiting = false;
// return true;
// } else {
// return false;
// }
}
pub fn peek_irq(&self) -> bool {
false
}
pub fn irq_waiting(&mut self) -> bool {
false
}
pub fn render_name_table<R: Renderer>(&self, 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 = self.memory.peek((off + col + row * 32) as u16).unwrap() as u16 * 16
+ if self.background.second_pattern {
0x1000
} else {
0
};
let attr = self
.memory
.peek((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 & 1) << 1) | ((row & 1) << 2))) & 0x3;
for y_off in 0..8 {
let low = self.memory.peek(name + y_off).unwrap();
let high = self.memory.peek(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, 0),
(true, false) => self.palette.color(1, palette),
(false, true) => self.palette.color(2, palette),
(true, true) => self.palette.color(3, 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 = self.memory.peek();
}
}
}
pub fn name_cursor_info(&self, 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 = self.memory.peek((off + col + row * 32) as u16).unwrap() as usize;
let attr = self
.memory
.peek((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, 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 = self.memory.peek(name + y_off).unwrap();
let high = self.memory.peek(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 = self.memory.peek(name + y_off + 0x1000).unwrap();
let high = self.memory.peek(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 = self.memory.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
}
}
pub fn mem(&self) -> &impl Memory {
&self.memory
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ppu_registers() {
let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_flags(0));
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(0, 0);
assert_eq!(ppu.background.v, 0);
ppu.write_reg(0, 0b11);
assert_eq!(
ppu.background.t, 0b0001100_00000000,
"Actual: {:016b}",
ppu.background.t
);
assert_eq!(ppu.background.w, false);
ppu.write_reg(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(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(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(2);
assert_eq!(ppu.background.w, false);
}
}