vblank and registers

This commit is contained in:
Daniel Bauer
2020-01-13 09:20:16 +01:00
parent 99f73aa0c8
commit 6fe7c94edc
6 changed files with 370 additions and 214 deletions

View File

@@ -91,11 +91,13 @@ fn main() -> Result<(), Error> {
// Main loop // Main loop
let mut run = true; let mut run = false;
while let Some(event) = events.next(&mut window) { while let Some(event) = events.next(&mut window) {
if let Some(_) = event.render_args() { if let Some(_) = event.render_args() {
// Run enough clocks to render the next frame // 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(); texture.update(&mut texture_ctx, &nes.ppu.borrow().canvas_main).unwrap();
window.draw_2d(&event, |c, g, d| { window.draw_2d(&event, |c, g, d| {
@@ -124,18 +126,9 @@ fn main() -> Result<(), Error> {
Key::C => nes.clock(), // advance one clock Key::C => nes.clock(), // advance one clock
Key::S => { nes.clock_instruction() } Key::S => { nes.clock_instruction() }
Key::F => { nes.clock_frame() } Key::F => { nes.clock_frame() }
Key::L => { nes.clock_scanline() }
Key::R => nes.reset(), Key::R => nes.reset(),
Key::Space => run = !run, 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_cpu(glyphs, &nes.cpu, debug_offset);
render_disasm(glyphs, disasm, nes.cpu.regs.pc, render_disasm(glyphs, disasm, nes.cpu.regs.pc,
[debug_offset[0], debug_offset[1] + (8.0 * (FT_LINE_DISTANCE+FT_SIZE_PX))]); [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, // render_memory(glyphs, nes,
// [debug_offset[0] + 400.0, debug_offset[1]]); // [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<Resources, Factory>, cpu: &CPU, offset: [f32; 2]) { fn render_flags(glyphs: &mut GlyphBrush<Resources, Factory>, name: &str,
// draw flags names: Vec<&str>, values: Vec<bool>, offset: [f32; 2]) {
let mut position = (offset[0], offset[1] + FT_SIZE_PX); let mut position = (offset[0], offset[1] + FT_SIZE_PX);
glyphs.queue(Section { glyphs.queue(Section {
text: "Flags:", text: name,
scale: *FT_SCALE, scale: *FT_SCALE,
screen_position: position, screen_position: position,
color: FT_COLOR_WHITE, color: FT_COLOR_WHITE,
@@ -173,86 +167,34 @@ fn render_cpu(glyphs: &mut GlyphBrush<Resources, Factory>, cpu: &CPU, offset: [f
}); });
position.0 += 70.0; position.0 += 70.0;
let color = if cpu.is_flag_set(Flags::NEGATIVE) { FT_COLOR_GREEN } else { FT_COLOR_RED }; for (flag, value) in names.iter().zip(values.iter()) {
glyphs.queue(Section { let color = if *value { FT_COLOR_GREEN } else { FT_COLOR_RED };
text: "N", glyphs.queue(Section {
scale: *FT_SCALE, text: flag,
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),
scale: *FT_SCALE, scale: *FT_SCALE,
screen_position: position, screen_position: position,
color: color, color: color,
..Section::default() ..Section::default()
}); });
position.0 += 16.0;
}
}
fn render_cpu(glyphs: &mut GlyphBrush<Resources, Factory>, 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 = [ let cpu_register_texts = [
&format!("A: {0:#x} ({0})", cpu.regs.a), &format!("A: {0:#x} ({0})", cpu.regs.a),
@@ -276,6 +218,68 @@ fn render_cpu(glyphs: &mut GlyphBrush<Resources, Factory>, cpu: &CPU, offset: [f
} }
} }
fn render_ppu(glyphs: &mut GlyphBrush<Resources, Factory>, 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<Resources, Factory>, fn render_disasm(glyphs: &mut GlyphBrush<Resources, Factory>,
disasm: &Disasm, pc: Addr, offset: [f32; 2]) { disasm: &Disasm, pc: Addr, offset: [f32; 2]) {
let pc_position = disasm.addresses.iter().position(|&pos| pos == pc); let pc_position = disasm.addresses.iter().position(|&pos| pos == pc);
@@ -283,7 +287,7 @@ fn render_disasm(glyphs: &mut GlyphBrush<Resources, Factory>,
return; return;
} }
let pc_position = pc_position.unwrap(); 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 { let start = if (pc_position as i32 - lines_above_below as i32) < 0 {
0 0

View File

@@ -65,6 +65,9 @@ impl NES {
self.cpu.clock(&mut self.memory); self.cpu.clock(&mut self.memory);
} }
self.ppu.borrow_mut().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.clock();
} }
self.ppu.borrow_mut().frame_ready = false; 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();
}
}
} }

View File

@@ -187,7 +187,7 @@ mod tests {
#[test] #[test]
fn test_cartridge_new() { fn test_cartridge_new() {
let path = Path::new("test_roms/registers.nes"); let path = Path::new("test_roms/nestest.nes");
Cartridge::new(&path).unwrap(); Cartridge::new(&path).unwrap();
} }

View File

@@ -109,3 +109,13 @@ pub trait PPUMemory {
self.writeb_ppu(addr + 1, (data >> 8) as Byte); self.writeb_ppu(addr + 1, (data >> 8) as Byte);
} }
} }
pub trait PPUMemoryReader {
fn readb_ppu<T: PPUMemory>(&self, mem: &T, addr: Addr) -> Byte {
mem.readb_ppu(addr)
}
fn writeb_ppu<T: PPUMemory>(&mut self, mem: &mut T, addr: Addr, data: Byte) {
mem.writeb_ppu(addr, data)
}
}

View File

@@ -1,103 +1,68 @@
use crate::nes::memory::PPUMemory; use crate::nes::memory::{Memory,PPUMemoryReader,PPUMemory};
use phf::{Map,phf_map};
use crate::nes::types::*; use crate::nes::types::*;
use crate::nes::memory::Memory;
use image::{ImageBuffer, Rgba}; use image::{ImageBuffer, Rgba};
use rand::Rng; use rand::Rng;
use palette::PALETTE;
type Pixel = Rgba<u8>; pub mod palette;
type Sprite = ImageBuffer<Pixel, Vec<u8>>;
static PALETTE: Map<u8, Pixel> = phf_map! { pub type Pixel = Rgba<u8>;
// 0x00 pub type Sprite = ImageBuffer<Pixel, Vec<u8>>;
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]),
};
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 // 0x2000
ctrl: Word, pub ctrl: Byte,
// 0x2001 // 0x2001
mask: Word, pub mask: Byte,
// 0x2002 // 0x2002
status: Word, pub status: Byte,
// 0x2003 // 0x2003
oam_addr: Word, pub oam_addr: Byte,
// 0x2004 // 0x2004
oam_data: Word, pub oam_data: Byte,
// 0x2005 // 0x2005
scroll: Word, pub scroll: Byte,
// 0x2006 // 0x2006
addr: Word, pub addr: Byte,
// 0x2007 // 0x2007
data: Word, pub data: Byte,
// 0x2008 // 0x2008
dma: Word, // 0x4014 pub dma: Byte, // 0x4014
} }
impl Registers { impl Registers {
@@ -117,19 +82,20 @@ impl Registers {
} }
pub struct PPU { pub struct PPU {
registers: Registers, pub regs: Registers,
pub cycle: u16, pub cycle: u16,
pub scanline: u16, pub scanline: u16,
pub canvas_main: Sprite, pub canvas_main: Sprite,
pattern_table: [Sprite; 2], pattern_table: [Sprite; 2],
pub frame_ready: bool pub frame_ready: bool
} }
impl PPUMemoryReader for PPU {}
impl PPU { impl PPU {
pub fn new() -> Self { pub fn new() -> Self {
PPU { PPU {
registers: Registers::new(), regs: Registers::new(),
cycle: 0, cycle: 0,
scanline: 0, scanline: 0,
canvas_main: ImageBuffer::new(256, 240), canvas_main: ImageBuffer::new(256, 240),
@@ -139,37 +105,81 @@ impl PPU {
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.registers = Registers::new(); self.regs = Registers::new();
} }
pub fn clock<T: Memory>(&mut self, _mem: &mut T) { // Set a flag with the corresponding mask
if (self.cycle < self.canvas_main.width() as u16) && fn set_flag(&mut self, register: PPURegister, flag: Byte, val: bool) {
(self.scanline < self.canvas_main.height() as u16) { // TODO Likely not all of them should be rw and treated as flags
// random noise let reg = match register {
let mut rng = rand::thread_rng(); PPURegister::Control => &mut self.regs.ctrl,
let x = self.cycle; PPURegister::Mask => &mut self.regs.mask,
let y = self.scanline; 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,
};
let color = rng.gen::<bool>(); if val {
let px = if color { *reg |= flag;
PALETTE[&0x0f] } else {
} else { *reg &= !flag;
PALETTE[&0x20]
};
self.canvas_main.put_pixel(x as u32, y as u32, px);
} }
}
// increment cycle/scanline
self.cycle += 1; // PPU renders 262 scanlines with 341 clocks per line. One px per clock
if self.cycle > 340 { // 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<T: Memory>(&mut self, _mem: &mut T) {
if self.cycle == 340 {
self.cycle = 0; self.cycle = 0;
self.scanline += 1; if self.scanline == 261 {
if self.scanline >= 261 {
self.scanline = 0; self.scanline = 0;
self.frame_ready = true; 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::<bool>();
// 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 // Get the correct color for the pixel from the given palette
@@ -222,10 +232,10 @@ impl PPU {
impl Memory for PPU { impl Memory for PPU {
fn readb(&self, addr: Addr) -> Byte { 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 { match addr {
// PPU status // PPU status
0x2002 => { 0x00 }, 0x2002 => { return self.regs.status },
// OAM data (object attribute memory = list of 64 sprites) // OAM data (object attribute memory = list of 64 sprites)
0x2004 => { unimplemented!() }, 0x2004 => { unimplemented!() },
// ppu data // ppu data
@@ -235,23 +245,69 @@ impl Memory for PPU {
} }
fn writeb(&mut self, addr: Addr, data: Byte) { 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 { match addr {
// Control // Control
0x2000 => { unimplemented!() }, 0x2000 => { self.regs.ctrl = data },
// Mask // Mask
0x2001 => { unimplemented!() }, 0x2001 => { self.regs.mask = data },
// OAM address // OAM address
0x2003 => { unimplemented!() }, 0x2003 => { unimplemented!() },
// OAM data // OAM data
0x2004 => { unimplemented!() }, 0x2004 => { unimplemented!() },
// Scroll // Scroll
0x2005 => { unimplemented!() }, 0x2005 => { self.regs.scroll = data },
// Address // Address
0x2006 => { unimplemented!() }, 0x2006 => { self.regs.addr = data },
// ppu 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);
}
}

74
src/nes/ppu/palette.rs Normal file
View File

@@ -0,0 +1,74 @@
use image::Rgba;
use phf::{Map,phf_map};
use super::Pixel;
pub static PALETTE: Map<u8, Pixel> = 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]),
};