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 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 { 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 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, 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, 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(&self, frame: &mut Frame) { 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 { 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(&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 pattern_cursor_info(&self, cursor: Point) -> Option { 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(&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], ); } } } pub fn palette_cursor_info(&self, cursor: Point) -> Option { 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); } }