diff --git a/src/main.rs b/src/main.rs index 7b082d2..2e1a6df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,7 +77,7 @@ fn main() -> Result<(), Error> { // fps counter let mut fps = FPSCounter::new(); - + // main screen texture let mut texture_ctx = TextureContext { factory: window.factory.clone(), @@ -91,11 +91,13 @@ fn main() -> Result<(), Error> { // Main loop - let mut run = true; + let mut run = false; while let Some(event) = events.next(&mut window) { if let Some(_) = event.render_args() { // Run enough clocks to render the next frame - if run { nes.clock_frame(); } + // if run { nes.clock_frame(); } + if run { nes.clock_scanline(); } + texture.update(&mut texture_ctx, &nes.ppu.borrow().canvas_main).unwrap(); window.draw_2d(&event, |c, g, d| { @@ -124,18 +126,9 @@ fn main() -> Result<(), Error> { Key::C => nes.clock(), // advance one clock Key::S => { nes.clock_instruction() } Key::F => { nes.clock_frame() } + Key::L => { nes.clock_scanline() } Key::R => nes.reset(), Key::Space => run = !run, - Key::Up => { - event_settings.ups = (event_settings.ups as f64 * 1.1) as u64 + 1; - events = Events::new(event_settings); - debug!("UPS: {}", event_settings.ups); - } - Key::Down => { - event_settings.ups = (event_settings.ups as f64 * 0.91) as u64; - events = Events::new(event_settings); - debug!("UPS: {}", event_settings.ups); - } _ => { } } } @@ -153,6 +146,7 @@ fn render_debug(window: &mut PistonWindow, event: &Event, render_cpu(glyphs, &nes.cpu, debug_offset); render_disasm(glyphs, disasm, nes.cpu.regs.pc, [debug_offset[0], debug_offset[1] + (8.0 * (FT_LINE_DISTANCE+FT_SIZE_PX))]); + render_ppu(glyphs, &nes.ppu.borrow(), [debug_offset[0], debug_offset[1] + (25.0 * (FT_LINE_DISTANCE+FT_SIZE_PX))]) // render_memory(glyphs, nes, // [debug_offset[0] + 400.0, debug_offset[1]]); }); @@ -161,11 +155,11 @@ fn render_debug(window: &mut PistonWindow, event: &Event, } -fn render_cpu(glyphs: &mut GlyphBrush, cpu: &CPU, offset: [f32; 2]) { - // draw flags +fn render_flags(glyphs: &mut GlyphBrush, name: &str, + names: Vec<&str>, values: Vec, offset: [f32; 2]) { let mut position = (offset[0], offset[1] + FT_SIZE_PX); glyphs.queue(Section { - text: "Flags:", + text: name, scale: *FT_SCALE, screen_position: position, color: FT_COLOR_WHITE, @@ -173,86 +167,34 @@ fn render_cpu(glyphs: &mut GlyphBrush, cpu: &CPU, offset: [f }); position.0 += 70.0; - let color = if cpu.is_flag_set(Flags::NEGATIVE) { FT_COLOR_GREEN } else { FT_COLOR_RED }; - glyphs.queue(Section { - text: "N", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = if cpu.is_flag_set(Flags::OVERFLOW) { FT_COLOR_GREEN } else { FT_COLOR_RED }; - glyphs.queue(Section { - text: "V", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = FT_COLOR_WHITE; - glyphs.queue(Section { - text: "-", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = if cpu.is_flag_set(Flags::BREAK) { FT_COLOR_GREEN } else { FT_COLOR_RED }; - glyphs.queue(Section { - text: "B", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = if cpu.is_flag_set(Flags::DECIMAL) { FT_COLOR_GREEN } else { FT_COLOR_RED }; - glyphs.queue(Section { - text: "D", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = if cpu.is_flag_set(Flags::IRQ) { FT_COLOR_GREEN } else { FT_COLOR_RED }; - glyphs.queue(Section { - text: "I", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = if cpu.is_flag_set(Flags::ZERO) { FT_COLOR_GREEN } else { FT_COLOR_RED }; - glyphs.queue(Section { - text: "Z", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = if cpu.is_flag_set(Flags::CARRY) { FT_COLOR_GREEN } else { FT_COLOR_RED }; - glyphs.queue(Section { - text: "C", - scale: *FT_SCALE, - screen_position: position, - color: color, - ..Section::default() - }); - position.0 += 16.0; - let color = FT_COLOR_WHITE; - glyphs.queue(Section { - text: &format!("{:#06x}", cpu.regs.flags), + for (flag, value) in names.iter().zip(values.iter()) { + let color = if *value { FT_COLOR_GREEN } else { FT_COLOR_RED }; + glyphs.queue(Section { + text: flag, scale: *FT_SCALE, screen_position: position, color: color, ..Section::default() }); + position.0 += 16.0; + } +} + +fn render_cpu(glyphs: &mut GlyphBrush, cpu: &CPU, offset: [f32; 2]) { + render_flags(glyphs, + "Flags", + vec!["N", "V", "-", "B", "D", "I", "Z", "C"], + vec![ + cpu.is_flag_set(Flags::NEGATIVE), + cpu.is_flag_set(Flags::OVERFLOW), + cpu.is_flag_set(Flags::UNUSED), + cpu.is_flag_set(Flags::BREAK), + cpu.is_flag_set(Flags::DECIMAL), + cpu.is_flag_set(Flags::IRQ), + cpu.is_flag_set(Flags::ZERO), + cpu.is_flag_set(Flags::CARRY) + ], + offset); let cpu_register_texts = [ &format!("A: {0:#x} ({0})", cpu.regs.a), @@ -276,6 +218,68 @@ fn render_cpu(glyphs: &mut GlyphBrush, cpu: &CPU, offset: [f } } +fn render_ppu(glyphs: &mut GlyphBrush, ppu: &PPU, offset: [f32; 2]) { + // draw position + let mut position = [offset[0], offset[1] + FT_SIZE_PX]; + glyphs.queue(Section { + text: &format!("SLine/Cycle: {:03}/{:03}", ppu.scanline, ppu.cycle), + scale: *FT_SCALE, + screen_position: (position[0], position[1]), + color: FT_COLOR_WHITE, + ..Section::default() + }); + + // control register + position[1] += FT_LINE_DISTANCE + FT_SIZE_PX; + render_flags(glyphs, + "Ctrl", + vec!["V", "P", "H", "B", "S", "I", "N", "N"], + vec![ + (ppu.regs.ctrl & (1 << 7)) > 0, + (ppu.regs.ctrl & (1 << 6)) > 0, + (ppu.regs.ctrl & (1 << 5)) > 0, + (ppu.regs.ctrl & (1 << 4)) > 0, + (ppu.regs.ctrl & (1 << 3)) > 0, + (ppu.regs.ctrl & (1 << 2)) > 0, + (ppu.regs.ctrl & (1 << 1)) > 0, + ], + position); + + // mask register + position[1] += FT_LINE_DISTANCE + FT_SIZE_PX; + render_flags(glyphs, + "Mask", + vec!["B", "G", "R", "s", "b", "M", "m", "G"], + vec![ + (ppu.regs.mask & (1 << 7)) > 0, + (ppu.regs.mask & (1 << 6)) > 0, + (ppu.regs.mask & (1 << 5)) > 0, + (ppu.regs.mask & (1 << 4)) > 0, + (ppu.regs.mask & (1 << 3)) > 0, + (ppu.regs.mask & (1 << 2)) > 0, + (ppu.regs.mask & (1 << 1)) > 0, + ], + position); + + // status register + position[1] += FT_LINE_DISTANCE + FT_SIZE_PX; + render_flags(glyphs, + "Status", + vec!["V", "S", "O", "-", "-", "-", "-", "-"], + vec![ + (ppu.regs.status & (1 << 7)) > 0, + (ppu.regs.status & (1 << 6)) > 0, + (ppu.regs.status & (1 << 5)) > 0, + (ppu.regs.status & (1 << 4)) > 0, + (ppu.regs.status & (1 << 3)) > 0, + (ppu.regs.status & (1 << 2)) > 0, + (ppu.regs.status & (1 << 1)) > 0, + ], + position); + + +} + fn render_disasm(glyphs: &mut GlyphBrush, disasm: &Disasm, pc: Addr, offset: [f32; 2]) { let pc_position = disasm.addresses.iter().position(|&pos| pos == pc); @@ -283,7 +287,7 @@ fn render_disasm(glyphs: &mut GlyphBrush, return; } let pc_position = pc_position.unwrap(); - let lines_above_below: usize = 12; + let lines_above_below: usize = 7; let start = if (pc_position as i32 - lines_above_below as i32) < 0 { 0 diff --git a/src/nes.rs b/src/nes.rs index 08266e3..9b5037b 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -65,6 +65,9 @@ impl NES { self.cpu.clock(&mut self.memory); } self.ppu.borrow_mut().clock(&mut self.memory); + if self.clock_count % 100000 == 0 { + info!("clock {}", self.clock_count); + } } @@ -86,8 +89,17 @@ impl NES { self.clock(); } self.ppu.borrow_mut().frame_ready = false; - info!("clock {}", self.clock_count); } + + // clock until the next scanline is done + pub fn clock_scanline(&mut self) { + let current_line = self.ppu.borrow().scanline; + while self.ppu.borrow().scanline == current_line { + self.clock(); + } + } + + } diff --git a/src/nes/cartridge.rs b/src/nes/cartridge.rs index 458f246..71b4db2 100644 --- a/src/nes/cartridge.rs +++ b/src/nes/cartridge.rs @@ -187,7 +187,7 @@ mod tests { #[test] fn test_cartridge_new() { - let path = Path::new("test_roms/registers.nes"); + let path = Path::new("test_roms/nestest.nes"); Cartridge::new(&path).unwrap(); } diff --git a/src/nes/memory.rs b/src/nes/memory.rs index 96a97b8..0e6e295 100644 --- a/src/nes/memory.rs +++ b/src/nes/memory.rs @@ -108,4 +108,14 @@ pub trait PPUMemory { self.writeb_ppu(addr, data as Byte); self.writeb_ppu(addr + 1, (data >> 8) as Byte); } +} + +pub trait PPUMemoryReader { + fn readb_ppu(&self, mem: &T, addr: Addr) -> Byte { + mem.readb_ppu(addr) + } + + fn writeb_ppu(&mut self, mem: &mut T, addr: Addr, data: Byte) { + mem.writeb_ppu(addr, data) + } } \ No newline at end of file diff --git a/src/nes/ppu.rs b/src/nes/ppu.rs index 2b6244a..61a9339 100644 --- a/src/nes/ppu.rs +++ b/src/nes/ppu.rs @@ -1,103 +1,68 @@ -use crate::nes::memory::PPUMemory; -use phf::{Map,phf_map}; +use crate::nes::memory::{Memory,PPUMemoryReader,PPUMemory}; use crate::nes::types::*; -use crate::nes::memory::Memory; use image::{ImageBuffer, Rgba}; use rand::Rng; +use palette::PALETTE; -type Pixel = Rgba; -type Sprite = ImageBuffer>; +pub mod palette; -static PALETTE: Map = phf_map! { - // 0x00 - 0x00u8 => Rgba([84, 84, 84, 255]), - 0x01u8 => Rgba([0, 30, 116, 255]), - 0x02u8 => Rgba([8, 16, 144, 255]), - 0x03u8 => Rgba([48, 0, 136, 255]), - 0x04u8 => Rgba([68, 0, 100, 255]), - 0x05u8 => Rgba([92, 0, 48, 255]), - 0x06u8 => Rgba([84, 4, 0, 255]), - 0x07u8 => Rgba([60, 24, 0, 255]), - 0x08u8 => Rgba([32, 42, 0, 255]), - 0x09u8 => Rgba([8, 58, 0, 255]), - 0x0au8 => Rgba([0, 64, 0, 255]), - 0x0bu8 => Rgba([0, 60, 0, 255]), - 0x0cu8 => Rgba([0, 50, 60, 255]), - 0x0du8 => Rgba([0, 0, 0, 255]), - 0x0eu8 => Rgba([0, 0, 0, 255]), - 0x0fu8 => Rgba([0, 0, 0, 255]), - // 0x10 - 0x10u8 => Rgba([152, 150, 152, 255]), - 0x11u8 => Rgba([8, 76, 196, 255]), - 0x12u8 => Rgba([48, 50, 236, 255]), - 0x13u8 => Rgba([92, 30, 228, 255]), - 0x14u8 => Rgba([136, 20, 176, 255]), - 0x15u8 => Rgba([160, 20, 100, 255]), - 0x16u8 => Rgba([152, 34, 32, 255]), - 0x17u8 => Rgba([120, 60, 0, 255]), - 0x18u8 => Rgba([84, 90, 0, 255]), - 0x19u8 => Rgba([40, 114, 0, 255]), - 0x1au8 => Rgba([8, 124, 0, 255]), - 0x1bu8 => Rgba([0, 118, 40, 255]), - 0x1cu8 => Rgba([0, 102, 120, 255]), - 0x1du8 => Rgba([0, 0, 0, 255]), - 0x1eu8 => Rgba([0, 0, 0, 255]), - 0x1fu8 => Rgba([0, 0, 0, 255]), - // 0x20 - 0x20u8 => Rgba([236, 238, 236, 255]), - 0x21u8 => Rgba([76, 154, 236, 255]), - 0x22u8 => Rgba([120, 124, 236, 255]), - 0x23u8 => Rgba([176, 98, 236, 255]), - 0x24u8 => Rgba([228, 84, 236, 255]), - 0x25u8 => Rgba([236, 88, 180, 255]), - 0x26u8 => Rgba([236, 106, 100, 255]), - 0x27u8 => Rgba([212, 136, 32, 255]), - 0x28u8 => Rgba([160, 170, 0, 255]), - 0x29u8 => Rgba([116, 196, 0, 255]), - 0x2au8 => Rgba([76, 208, 32, 255]), - 0x2bu8 => Rgba([56, 204, 108, 255]), - 0x2cu8 => Rgba([56, 180, 204, 255]), - 0x2du8 => Rgba([60, 60, 60, 255]), - 0x2eu8 => Rgba([0, 0, 0, 255]), - 0x2fu8 => Rgba([0, 0, 0, 255]), - // 0x30 - 0x30u8 => Rgba([236, 238, 236, 255]), - 0x31u8 => Rgba([168, 204, 236, 255]), - 0x32u8 => Rgba([188, 188, 236, 255]), - 0x33u8 => Rgba([212, 178, 236, 255]), - 0x34u8 => Rgba([236, 174, 236, 255]), - 0x35u8 => Rgba([236, 174, 212, 255]), - 0x36u8 => Rgba([236, 180, 176, 255]), - 0x37u8 => Rgba([228, 196, 144, 255]), - 0x38u8 => Rgba([204, 210, 120, 255]), - 0x39u8 => Rgba([180, 222, 120, 255]), - 0x3au8 => Rgba([168, 226, 144, 255]), - 0x3bu8 => Rgba([152, 226, 180, 255]), - 0x3cu8 => Rgba([160, 214, 228, 255]), - 0x3du8 => Rgba([160, 162, 160, 255]), - 0xe3u8 => Rgba([0, 0, 0, 255]), - 0x3fu8 => Rgba([0, 0, 0, 255]), -}; +pub type Pixel = Rgba; +pub type Sprite = ImageBuffer>; -struct Registers { + +pub const CTRL_NAMETBL_X: Byte = 1 << 0; +pub const CTRL_NAMETBL_Y: Byte = 1 << 1; +pub const CTRL_INCR_MODE: Byte = 1 << 2; +pub const CTRL_PATTERN_SPRITE: Byte = 1 << 3; +pub const CTRL_PATTERN_BG: Byte = 1 << 4; +pub const CTRL_SPRITE_SIZE: Byte = 1 << 5; +pub const CTRL_SLAVE_MODE: Byte = 1 << 6; +pub const CTRL_ENABLE_NMI: Byte = 1 << 7; + +pub const STATUS_SPRITE_OVERFOL: Byte = 1 << 5; +pub const STATUS_SPRITE_ZERO_HIT: Byte = 1 << 6; +pub const STATUS_VERTICAL_BLANK: Byte = 1 << 7; + +pub const MASK_GRAYSCALE: Byte = 1 << 0; +pub const MASK_RENDER_BG_LEFT: Byte = 1 << 1; +pub const MASK_RENDER_SPRITES_LEFT: Byte = 1 << 2; +pub const MASK_RENDER_BG: Byte = 1 << 3; +pub const MASK_RENDER_SPRITES: Byte = 1 << 4; +pub const MASK_ENHANCE_RED: Byte = 1 << 5; +pub const MASK_ENHANCE_GREEN: Byte = 1 << 6; +pub const MASK_ENHANCE_BLUE: Byte = 1 << 7; + +enum PPURegister { + Control, + Mask, + Status, + OAMAddr, + OAMData, + Scroll, + Addr, + Data, + DMA +} + +pub struct Registers { // 0x2000 - ctrl: Word, + pub ctrl: Byte, // 0x2001 - mask: Word, + pub mask: Byte, // 0x2002 - status: Word, + pub status: Byte, // 0x2003 - oam_addr: Word, + pub oam_addr: Byte, // 0x2004 - oam_data: Word, + pub oam_data: Byte, // 0x2005 - scroll: Word, + pub scroll: Byte, // 0x2006 - addr: Word, + pub addr: Byte, // 0x2007 - data: Word, + pub data: Byte, // 0x2008 - dma: Word, // 0x4014 + pub dma: Byte, // 0x4014 } impl Registers { @@ -117,19 +82,20 @@ impl Registers { } pub struct PPU { - registers: Registers, + pub regs: Registers, pub cycle: u16, pub scanline: u16, pub canvas_main: Sprite, pattern_table: [Sprite; 2], pub frame_ready: bool - } +impl PPUMemoryReader for PPU {} + impl PPU { pub fn new() -> Self { PPU { - registers: Registers::new(), + regs: Registers::new(), cycle: 0, scanline: 0, canvas_main: ImageBuffer::new(256, 240), @@ -139,37 +105,81 @@ impl PPU { } pub fn reset(&mut self) { - self.registers = Registers::new(); + self.regs = Registers::new(); } - pub fn clock(&mut self, _mem: &mut T) { - if (self.cycle < self.canvas_main.width() as u16) && - (self.scanline < self.canvas_main.height() as u16) { - // random noise - let mut rng = rand::thread_rng(); - let x = self.cycle; - let y = self.scanline; - - let color = rng.gen::(); - let px = if color { - PALETTE[&0x0f] - } else { - PALETTE[&0x20] - }; - - self.canvas_main.put_pixel(x as u32, y as u32, px); + // Set a flag with the corresponding mask + fn set_flag(&mut self, register: PPURegister, flag: Byte, val: bool) { + // TODO Likely not all of them should be rw and treated as flags + let reg = match register { + PPURegister::Control => &mut self.regs.ctrl, + PPURegister::Mask => &mut self.regs.mask, + PPURegister::Status => &mut self.regs.status, + PPURegister::OAMAddr => &mut self.regs.oam_addr, + PPURegister::OAMData => &mut self.regs.oam_data, + PPURegister::Scroll => &mut self.regs.scroll, + PPURegister::Addr => &mut self.regs.addr, + PPURegister::Data => &mut self.regs.data, + PPURegister::DMA => &mut self.regs.dma, + }; + + if val { + *reg |= flag; + } else { + *reg &= !flag; } + } - // increment cycle/scanline - self.cycle += 1; - if self.cycle > 340 { + + // PPU renders 262 scanlines with 341 clocks per line. One px per clock + // Scanline -1,261: Dummy scanline + // Scanline 0-239: Visible scanlines: + // Cycle 0: idle. + // Cycle 1-256: Fetch tile data + // Cycle 257-320: Fetch tile data of sprites for next scanline + // Cycle 321-336: Fetch first two tiles of next scanline + // Cycle 337-340: "Unknown" data fetch + // Scanline 240: PPU idle + // Scanline 241-260: Vblack. Flag is set during second clock of 241 together + // with NMI + pub fn clock(&mut self, _mem: &mut T) { + if self.cycle == 340 { self.cycle = 0; - self.scanline += 1; - if self.scanline >= 261 { + if self.scanline == 261 { self.scanline = 0; self.frame_ready = true; - } + } else { + self.scanline += 1; + } + } else { + self.cycle += 1; + }; + + // set/clear vblank flag + if self.scanline == 241 && self.cycle == 1 { + self.set_flag(PPURegister::Status, STATUS_VERTICAL_BLANK, true); + + } else if self.scanline == 261 && self.cycle == 1 { + self.set_flag(PPURegister::Status, STATUS_VERTICAL_BLANK, false); } + + // if (self.cycle < self.canvas_main.width() as u16) && + // (self.scanline < self.canvas_main.height() as u16) { + // // random noise + // let mut rng = rand::thread_rng(); + // let x = self.cycle; + // let y = self.scanline; + + // let color = rng.gen::(); + // let px = if color { + // PALETTE[&0x0f] + // } else { + // PALETTE[&0x20] + // }; + + // self.canvas_main.put_pixel(x as u32, y as u32, px); + // } + } // Get the correct color for the pixel from the given palette @@ -222,10 +232,10 @@ impl PPU { impl Memory for PPU { fn readb(&self, addr: Addr) -> Byte { - // Only some of the PPU registers can actually by read + // Only some of the PPU regs can actually by read match addr { // PPU status - 0x2002 => { 0x00 }, + 0x2002 => { return self.regs.status }, // OAM data (object attribute memory = list of 64 sprites) 0x2004 => { unimplemented!() }, // ppu data @@ -235,23 +245,69 @@ impl Memory for PPU { } fn writeb(&mut self, addr: Addr, data: Byte) { - // Only some of the PPU registers can be written to + // Only some of the PPU regs can be written to match addr { // Control - 0x2000 => { unimplemented!() }, + 0x2000 => { self.regs.ctrl = data }, // Mask - 0x2001 => { unimplemented!() }, + 0x2001 => { self.regs.mask = data }, // OAM address 0x2003 => { unimplemented!() }, // OAM data 0x2004 => { unimplemented!() }, // Scroll - 0x2005 => { unimplemented!() }, + 0x2005 => { self.regs.scroll = data }, // Address - 0x2006 => { unimplemented!() }, + 0x2006 => { self.regs.addr = data }, // ppu data - 0x2007 => { unimplemented!() }, - _ => { } + 0x2007 => { self.regs.data = data }, + _ => { unreachable!() } } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_set_flags() { + let mut ppu = PPU::new(); + + // set unset flag + assert_eq!(ppu.regs.status, 0b00000000); + ppu.set_flag(PPURegister::Status, STATUS_VERTICAL_BLANK, true); + assert_eq!(ppu.regs.status, 0b10000000, + "register={:#010b}; should be 0b10000000", ppu.regs.status); + ppu.set_flag(PPURegister::Status, STATUS_VERTICAL_BLANK, false); + assert_eq!(ppu.regs.status, 0b00000000, + "register={:#010b}; should be 0b00000000", ppu.regs.status); + + + // set same flag twice + ppu.set_flag(PPURegister::Status, STATUS_VERTICAL_BLANK, true); + assert_eq!(ppu.regs.status, 0b10000000, + "register={:#010b}; should be 0b10000000", ppu.regs.status); + ppu.set_flag(PPURegister::Status, STATUS_VERTICAL_BLANK, true); + assert_eq!(ppu.regs.status, 0b10000000, + "register={:#010b}; should be 0b10000000", ppu.regs.status); + + + // set other flag + ppu.set_flag(PPURegister::Status, STATUS_SPRITE_ZERO_HIT, true); + assert_eq!(ppu.regs.status, 0b11000000, + "register={:#010b}; should be 0b11000000", ppu.regs.status); + + ppu.set_flag(PPURegister::Status, STATUS_SPRITE_ZERO_HIT, false); + assert_eq!(ppu.regs.status, 0b10000000, + "register={:#010b}; should be 0b10000000", ppu.regs.status); + + + // set other register + ppu.set_flag(PPURegister::Mask, MASK_GRAYSCALE, true); + assert_eq!(ppu.regs.status, 0b10000000, + "register={:#010b}; should be 0b10000000", ppu.regs.status); + assert_eq!(ppu.regs.mask, 0b0000001, + "register={:#010b}; should be 0b0000001", ppu.regs.status); + } +} diff --git a/src/nes/ppu/palette.rs b/src/nes/ppu/palette.rs new file mode 100644 index 0000000..7db6066 --- /dev/null +++ b/src/nes/ppu/palette.rs @@ -0,0 +1,74 @@ +use image::Rgba; +use phf::{Map,phf_map}; +use super::Pixel; + +pub static PALETTE: Map = phf_map! { + // 0x00 + 0x00u8 => Rgba([84, 84, 84, 255]), + 0x01u8 => Rgba([0, 30, 116, 255]), + 0x02u8 => Rgba([8, 16, 144, 255]), + 0x03u8 => Rgba([48, 0, 136, 255]), + 0x04u8 => Rgba([68, 0, 100, 255]), + 0x05u8 => Rgba([92, 0, 48, 255]), + 0x06u8 => Rgba([84, 4, 0, 255]), + 0x07u8 => Rgba([60, 24, 0, 255]), + 0x08u8 => Rgba([32, 42, 0, 255]), + 0x09u8 => Rgba([8, 58, 0, 255]), + 0x0au8 => Rgba([0, 64, 0, 255]), + 0x0bu8 => Rgba([0, 60, 0, 255]), + 0x0cu8 => Rgba([0, 50, 60, 255]), + 0x0du8 => Rgba([0, 0, 0, 255]), + 0x0eu8 => Rgba([0, 0, 0, 255]), + 0x0fu8 => Rgba([0, 0, 0, 255]), + // 0x10 + 0x10u8 => Rgba([152, 150, 152, 255]), + 0x11u8 => Rgba([8, 76, 196, 255]), + 0x12u8 => Rgba([48, 50, 236, 255]), + 0x13u8 => Rgba([92, 30, 228, 255]), + 0x14u8 => Rgba([136, 20, 176, 255]), + 0x15u8 => Rgba([160, 20, 100, 255]), + 0x16u8 => Rgba([152, 34, 32, 255]), + 0x17u8 => Rgba([120, 60, 0, 255]), + 0x18u8 => Rgba([84, 90, 0, 255]), + 0x19u8 => Rgba([40, 114, 0, 255]), + 0x1au8 => Rgba([8, 124, 0, 255]), + 0x1bu8 => Rgba([0, 118, 40, 255]), + 0x1cu8 => Rgba([0, 102, 120, 255]), + 0x1du8 => Rgba([0, 0, 0, 255]), + 0x1eu8 => Rgba([0, 0, 0, 255]), + 0x1fu8 => Rgba([0, 0, 0, 255]), + // 0x20 + 0x20u8 => Rgba([236, 238, 236, 255]), + 0x21u8 => Rgba([76, 154, 236, 255]), + 0x22u8 => Rgba([120, 124, 236, 255]), + 0x23u8 => Rgba([176, 98, 236, 255]), + 0x24u8 => Rgba([228, 84, 236, 255]), + 0x25u8 => Rgba([236, 88, 180, 255]), + 0x26u8 => Rgba([236, 106, 100, 255]), + 0x27u8 => Rgba([212, 136, 32, 255]), + 0x28u8 => Rgba([160, 170, 0, 255]), + 0x29u8 => Rgba([116, 196, 0, 255]), + 0x2au8 => Rgba([76, 208, 32, 255]), + 0x2bu8 => Rgba([56, 204, 108, 255]), + 0x2cu8 => Rgba([56, 180, 204, 255]), + 0x2du8 => Rgba([60, 60, 60, 255]), + 0x2eu8 => Rgba([0, 0, 0, 255]), + 0x2fu8 => Rgba([0, 0, 0, 255]), + // 0x30 + 0x30u8 => Rgba([236, 238, 236, 255]), + 0x31u8 => Rgba([168, 204, 236, 255]), + 0x32u8 => Rgba([188, 188, 236, 255]), + 0x33u8 => Rgba([212, 178, 236, 255]), + 0x34u8 => Rgba([236, 174, 236, 255]), + 0x35u8 => Rgba([236, 174, 212, 255]), + 0x36u8 => Rgba([236, 180, 176, 255]), + 0x37u8 => Rgba([228, 196, 144, 255]), + 0x38u8 => Rgba([204, 210, 120, 255]), + 0x39u8 => Rgba([180, 222, 120, 255]), + 0x3au8 => Rgba([168, 226, 144, 255]), + 0x3bu8 => Rgba([152, 226, 180, 255]), + 0x3cu8 => Rgba([160, 214, 228, 255]), + 0x3du8 => Rgba([160, 162, 160, 255]), + 0xe3u8 => Rgba([0, 0, 0, 255]), + 0x3fu8 => Rgba([0, 0, 0, 255]), +};