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 { buffer: Box<[Color]>, raw_rgba: BytesMut, } impl RenderBuffer { 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, secondary: Vec, addr: u8, count: u8, state: OamState, oam_read_buffer: u8, edit_ver: usize, sprite_output_units: Vec, 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 for Color { fn into(self) -> Fill { iced::Color::from_rgb8(self.r, self.g, self.b).into() } } impl PPU { pub fn render_name_table(&self, mem: &Mapped, 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 = 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 { 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(&self, mem: &Mapped, 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 = 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 { 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 } } } }