use iced::{ Point, Size, advanced::graphics::geometry::Renderer, widget::canvas::{Fill, Frame}, }; use crate::{ hex_view::Memory, mem::{MemoryMap, Segment}, }; #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Color { pub r: u8, pub g: u8, pub b: u8, } impl Into for Color { fn into(self) -> Fill { iced::Color::from_rgb8(self.r, self.g, self.b).into() } } pub struct RenderBuffer { buffer: Box<[Color]>, } impl RenderBuffer { 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(crate) enum PPUMMRegisters { Palette, } pub struct OAM { mem: Vec, addr: u8, } enum BackgroundState { NameTableBytePre, NameTableByte, AttrTableBytePre, AttrTableByte, PatternTableTileLowPre, PatternTableTileLow, PatternTableTileHighPre, PatternTableTileHigh, } pub struct Background { /// Current vram address, 15 bits v: u16, /// Temp vram address, 15 bits t: u16, /// Fine X control, 3 bits x: u8, /// Whether this is the first or second write to PPUSCROLL /// When false, writes to x w: bool, /// When true, v is incremented by 32 after each read vram_column: bool, state: BackgroundState, cur_nametable: u8, cur_attr: u8, cur_high: u8, cur_low: u8, cur_shift_high: u8, cur_shift_low: u8, } pub struct Mask { grayscale: bool, background_on_left_edge: bool, sprites_on_left_edge: bool, enable_background: bool, enable_sprites: bool, em_red: bool, em_green: bool, 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) -> Color { debug_assert!(idx < 0x20, "Palette index out of range"); self.colors[(self.ram[idx as usize] & 0x3F) as usize] } } pub struct PPU { // registers: PPURegisters, 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, palette: Palette, 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]) -> 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: MemoryMap::new(vec![ Segment::rom("CHR ROM", 0x0000, rom), Segment::ram("Internal VRAM", 0x2000, 0x1000), Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1), Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette), Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 3), ]), background: Background { v: 0, t: 0, x: 0, w: false, vram_column: false, state: BackgroundState::NameTableBytePre, cur_high: 0, cur_low: 0, cur_shift_high: 0, cur_shift_low: 0, cur_nametable: 0, cur_attr: 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(&[]) }; } 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 => { 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.background.v = self .background .v .wrapping_add(if self.background.vram_column { 32 } else { 1 }); 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_0010 != 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 = true; } else { self.background.v = u16::from_le_bytes([self.background.v.to_le_bytes()[0], val & 0b0011_1111]); self.background.w = true; } // todo!("PPUADDR write") } 0x07 => { // TODO: ppu data } _ => panic!("No register at {:02X}", offset), } // TODO: use data in PPU } // 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.pixel == 257 { self.background.v = (self.background.v & 0b0111_1011_1110_0000) | (self.background.t & 0b0000_0100_0001_1111); } if self.pixel == 280 && self.scanline == 260 { self.background.v = (self.background.v & 0b0000_0100_0001_1111) | (self.background.t & 0b0111_1011_1110_0000); } if self.scanline < 240 || self.scanline == 261 { // TODO if self.pixel == 0 { // self.dbg_int = true; // idle cycle } else if self.pixel < 257 { // self.dbg_int = true; if self.scanline < 240 { // Determine background color let a = self.background.cur_shift_high & 0x80; let b = self.background.cur_shift_low & 0x80; let val = (a >> 6) | (b >> 7); debug_assert!(val < 4); // Write to screen self.render_buffer.write( self.scanline, self.pixel - 1, self.palette.color(val) // self.palette.colors[val as usize], ); // TODO: this should come from shift registers self.background.cur_shift_high <<= 1; self.background.cur_shift_low <<= 1; } self.background.state = match self.background.state { BackgroundState::NameTableByte => { // TODO: Fetch name table byte let addr = 0x2000 + self.pixel / 8 + self.scanline / 8; let val = self.memory.read(addr as u16).reg_map(|_, _| todo!()); self.background.cur_nametable = val; // self.background.v; BackgroundState::AttrTableBytePre } BackgroundState::AttrTableByte => { // TODO: Fetch attr table byte // let addr = 0x2000 + self.pixel / 8 + self.scanline / 8; // let val = self.memory.read(addr as u16).reg_map(|_, _| todo!()); // self.background.cur_attr = val; BackgroundState::PatternTableTileLowPre } BackgroundState::PatternTableTileLow => { // TODO: Fetch let addr = self.background.cur_nametable as u16 * 16 + (self.scanline % 8) as u16; let val = self.memory.read(addr).reg_map(|_, _| todo!()); self.background.cur_low = val; BackgroundState::PatternTableTileHighPre } BackgroundState::PatternTableTileHigh => { // TODO: Fetch let addr = self.background.cur_nametable as u16 * 16 + 8 + (self.scanline % 8) as u16; let val = self.memory.read(addr).reg_map(|_, _| todo!()); self.background.cur_high = val; self.background.cur_shift_low = self.background.cur_low; self.background.cur_shift_high = self.background.cur_high; BackgroundState::NameTableBytePre } BackgroundState::NameTableBytePre => BackgroundState::NameTableByte, BackgroundState::AttrTableBytePre => BackgroundState::AttrTableByte, BackgroundState::PatternTableTileLowPre => { BackgroundState::PatternTableTileLow } BackgroundState::PatternTableTileHighPre => { BackgroundState::PatternTableTileHigh } }; } else { // TODO: Sprite fetches } } } 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 peek_nmi(&self) -> bool { self.nmi_waiting } pub fn nmi_waiting(&mut self) -> bool { if self.nmi_waiting { self.nmi_waiting = false; return true; } else { return false; } } pub fn peek_irq(&self) -> bool { self.nmi_waiting } pub fn irq_waiting(&mut self) -> bool { false } pub fn render_name_table(&self, frame: &mut Frame) { for y in 0..280 / 8 { for x in 0..512 / 8 { let name = self.memory.peek(x + y * 512 / 8 + 0x2000).unwrap() as u16 * 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).rev() { frame.fill_rectangle( Point::new(x as f32 * 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 // let pat = self.memory.peek(); } } } pub fn render_pattern_tables(&self, frame: &mut Frame) { 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 render_palette(&self, frame: &mut Frame) { 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], ); } } } } #[cfg(test)] mod tests { use super::*; #[test] fn ppu_registers() { let mut ppu = PPU::with_chr_rom(&[0u8; 8192]); 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); } }