diff --git a/deemgee/src/gameboy.rs b/deemgee/src/gameboy.rs index 258c1d5..8a59079 100644 --- a/deemgee/src/gameboy.rs +++ b/deemgee/src/gameboy.rs @@ -3,7 +3,7 @@ mod interrupts; mod joypad; pub mod mapper; mod memory; -mod ppu; +pub mod ppu; mod serial; mod sound; mod timer; @@ -25,18 +25,16 @@ use self::{ pub struct DmaState { pub base: u8, pub remaining_cycles: u8, - pub remaining_delay: u8, } impl DmaState { pub fn new() -> Self { - Self { base: 0, remaining_cycles: 0, remaining_delay: 0 } + Self { base: 0, remaining_cycles: 0 } } pub fn init_request(&mut self, base: u8) { self.base = base; self.remaining_cycles = 0xA0; - self.remaining_delay = 2; } } @@ -135,6 +133,8 @@ pub struct Gameboy { pub stop: bool, pub pc_history: RingBuffer, + + pub tick_count: u8, } impl Gameboy { @@ -147,7 +147,7 @@ impl Gameboy { joypad: Joypad::new(), serial: Serial::new(), dma: DmaState::new(), - ppu: Ppu::new(), + ppu: Ppu::new(bootrom.is_some()), registers: match bootrom.is_some() { true => Registers::default(), false => Registers::post_rom(), @@ -164,6 +164,7 @@ impl Gameboy { used_halt_bug: false, stop: false, pc_history: RingBuffer::new(), + tick_count: 0, } } @@ -188,6 +189,9 @@ impl Gameboy { } pub fn load_cartridge(&mut self, bytes: Vec) { + if bytes.len() < 0x150 { + panic!("Bad cartridge (len < 0x150)"); + } match bytes[0x147] { 0 => self.cartridge = Some(Box::new(NoMBC::new(bytes))), 1 => self.cartridge = Some(Box::new(MBC1::new(bytes))), @@ -201,173 +205,217 @@ impl Gameboy { log::info!("\n-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}\n-- Interrupts --\nIME: {}\nIE VBlank: {}\nIE LCD Stat: {}\nIE Timer: {}\nIE Serial: {}\nIE Joypad: {}\nIF VBlank: {}\nIF LCD Stat: {}\nIF Timer: {}\nIF Serial: {}\nIF Joypad: {}\n", self.registers.get_af(), self.registers.get_bc(), self.registers.get_de(), self.registers.get_hl(), self.registers.get_sp(), self.registers.pc, self.registers.get_zero(), self.registers.get_subtract(), self.registers.get_half_carry(), self.registers.get_carry(), self.interrupts.ime, self.interrupts.read_ie_vblank(), self.interrupts.read_ie_lcd_stat(), self.interrupts.read_ie_timer(), self.interrupts.read_ie_serial(), self.interrupts.read_ie_joypad(), self.interrupts.read_if_vblank(), self.interrupts.read_if_lcd_stat(), self.interrupts.read_if_timer(), self.interrupts.read_if_serial(), self.interrupts.read_if_joypad()); } + pub fn tick_4(&mut self) -> (bool, Option) { + let mut request_redraw = false; + let mut debug_time = None; + for _ in 0..4 { + let (t_request_redraw, t_debug_time) = self.tick(); + request_redraw |= t_request_redraw; + if t_debug_time.is_some() { + assert!(debug_time.is_none()); + debug_time = t_debug_time; + } + } + + (request_redraw, debug_time) + } + pub fn tick(&mut self) -> (bool, Option) { - if self.breakpoints[self.registers.pc as usize] && !self.single_step { - self.single_step = true; - log::info!("Breakpoint hit @ {:#X}", self.registers.pc); - } + if self.tick_count == 0 { + if self.breakpoints[self.registers.pc as usize] && !self.single_step { + self.single_step = true; + log::info!("Breakpoint hit @ {:#X}", self.registers.pc); + } - let mut diff = None; + let mut diff = None; - if self.trigger_bp || (self.single_step && self.registers.cycle == 0) { - let entered_step = chrono::Utc::now(); - self.trigger_bp = false; - self.single_step = true; - let mut input = String::new(); - let mut exit = true; - match std::io::stdin().read_line(&mut input) { - Ok(_) => { - let lower = input.trim_end().to_lowercase(); - let (lhs, rhs) = lower.split_once(' ').unwrap_or_else(|| (lower.as_str(), "")); - match lhs { - "read" => match u16::from_str_radix(rhs, 16) { - Ok(address) => { - let res = self.internal_cpu_read_u8(address); - log::info!("{:#X}: {:#X} ({:#b})", address, res, res); - } - Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), - }, - "regs" => self.log_state(), - "op" => { - self.log_next_opcode(); - } - "bp" => match u16::from_str_radix(rhs, 16) { - Ok(address) => { - let bp = &mut self.breakpoints[address as usize]; - *bp = !*bp; - match *bp { - true => log::info!("Set breakpoint @ {:#X}", address), - false => log::info!("Cleared breakpoint @ {:#X}", address), + if self.trigger_bp || (self.single_step && self.registers.cycle == 0) { + let entered_step = chrono::Utc::now(); + self.trigger_bp = false; + self.single_step = true; + let mut input = String::new(); + let mut exit = true; + match std::io::stdin().read_line(&mut input) { + Ok(_) => { + let lower = input.trim_end().to_lowercase(); + let (lhs, rhs) = + lower.split_once(' ').unwrap_or_else(|| (lower.as_str(), "")); + match lhs { + "read" => match u16::from_str_radix(rhs, 16) { + Ok(address) => { + let res = self.internal_cpu_read_u8(address); + log::info!("{:#X}: {:#X} ({:#b})", address, res, res); } + Err(_) => { + log::error!("Failed to parse input as hex u16 (f.ex 420C)") + } + }, + "regs" => self.log_state(), + "op" => { + self.log_next_opcode(); } - Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), - }, - "bpr" => match u16::from_str_radix(rhs, 16) { - Ok(address) => { - let bp = &mut self.mem_read_breakpoints[address as usize]; - *bp = !*bp; - match *bp { - true => log::info!("Set breakpoint on read @ {:#X}", address), - false => { - log::info!("Cleared breakpoint on read @ {:#X}", address) + "bp" => match u16::from_str_radix(rhs, 16) { + Ok(address) => { + let bp = &mut self.breakpoints[address as usize]; + *bp = !*bp; + match *bp { + true => log::info!("Set breakpoint @ {:#X}", address), + false => log::info!("Cleared breakpoint @ {:#X}", address), } } - } - Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), - }, - "bpw" => match u16::from_str_radix(rhs, 16) { - Ok(address) => { - let bp = &mut self.mem_write_breakpoints[address as usize]; - *bp = !*bp; - match *bp { - true => log::info!("Set breakpoint on write @ {:#X}", address), - false => { - log::info!("Cleared breakpoint on write @ {:#X}", address) + Err(_) => { + log::error!("Failed to parse input as hex u16 (f.ex 420C)") + } + }, + "bpr" => match u16::from_str_radix(rhs, 16) { + Ok(address) => { + let bp = &mut self.mem_read_breakpoints[address as usize]; + *bp = !*bp; + match *bp { + true => { + log::info!("Set breakpoint on read @ {:#X}", address) + } + false => { + log::info!( + "Cleared breakpoint on read @ {:#X}", + address + ) + } } } - } - Err(_) => log::error!("Failed to parse input as hex u16 (f.ex 420C)"), - }, - "c" => { - self.single_step = false; - log::info!("Continuing"); - exit = false; - } - "timer" => { - println!("-- Timer Info --\n{:#?}\n-- End of Timer Info --", self.timer) - } - "p" | "pause" => { - self.single_step = true; - log::info!("Single step activated"); - exit = false; - } - "pch" => { - println!("-- Start of PC History (new to old) --"); - for (idx, pc) in self.pc_history.to_vec().iter().rev().enumerate() { - println!("{}: {:#04X}", idx + 1, pc); - } - println!("-- End of PC History --"); - } - "s" | "step" | "" => { - self.log_next_opcode(); - exit = false; - } - "ls" => { - self.log_state(); - exit = false; - } - "dumpbgtiles" => { - self.ppu.dump_bg_tiles(); - } - "dumpfb" => { - println!("Written to: {}", self.ppu.dump_fb()); - } - "dumpoam" => { - for x in 0..self.ppu.oam.len() { - if x % 0x10 == 0 { - print!("\n{:X}: ", 0xFE00 + x) + Err(_) => { + log::error!("Failed to parse input as hex u16 (f.ex 420C)") } - - let mem_val = self.ppu.oam[x]; - print!("{:02X} ", mem_val); - } - println!(); - } - "dumpvram" => { - for x in 0..0x200 { - if x % 0x10 == 0 { - print!("\n{:X}: ", 0x8000 + x) + }, + "bpw" => match u16::from_str_radix(rhs, 16) { + Ok(address) => { + let bp = &mut self.mem_write_breakpoints[address as usize]; + *bp = !*bp; + match *bp { + true => { + log::info!("Set breakpoint on write @ {:#X}", address) + } + false => { + log::info!( + "Cleared breakpoint on write @ {:#X}", + address + ) + } + } } - - let mem_val = self.ppu.vram[x]; - print!("{:02X} ", mem_val); - } - println!(); - } - "dumptilemap" => { - let base = match (self.ppu.lcdc >> 3) & 0b1 == 1 { - true => 0x1C00, - false => 0x1800, - }; - - for x in 0..0x400 { - if x % 0x10 == 0 { - print!("\n{:X}: ", 0x8000 + base + x) + Err(_) => { + log::error!("Failed to parse input as hex u16 (f.ex 420C)") } - - let mem_val = self.ppu.vram[base + x]; - print!("{:02X} ", mem_val); + }, + "c" => { + self.single_step = false; + log::info!("Continuing"); + exit = false; } - println!(); + "timer" => { + println!( + "-- Timer Info --\n{:#?}\n-- End of Timer Info --", + self.timer + ) + } + "p" | "pause" => { + self.single_step = true; + log::info!("Single step activated"); + exit = false; + } + "pch" => { + println!("-- Start of PC History (new to old) --"); + for (idx, pc) in self.pc_history.to_vec().iter().rev().enumerate() { + println!("{}: {:#04X}", idx + 1, pc); + } + println!("-- End of PC History --"); + } + "s" | "step" | "" => { + self.log_next_opcode(); + exit = false; + } + "ls" => { + self.log_state(); + exit = false; + } + "dumpbgtiles" => { + self.ppu.dump_bg_tiles(); + } + "dumpfb" => { + println!("Written to: {}", self.ppu.dump_fb()); + } + "dumpoam" => { + for x in 0..self.ppu.oam.len() { + if x % 0x10 == 0 { + print!("\n{:X}: ", 0xFE00 + x) + } + + let mem_val = self.ppu.oam[x]; + print!("{:02X} ", mem_val); + } + println!(); + } + "dumpvram" => { + for x in 0..0x200 { + if x % 0x10 == 0 { + print!("\n{:X}: ", 0x8000 + x) + } + + let mem_val = self.ppu.vram[x]; + print!("{:02X} ", mem_val); + } + println!(); + } + "dumptilemap" => { + let base = match (self.ppu.lcdc >> 3) & 0b1 == 1 { + true => 0x1C00, + false => 0x1800, + }; + + for x in 0..0x400 { + if x % 0x10 == 0 { + print!("\n{:X}: ", 0x8000 + base + x) + } + + let mem_val = self.ppu.vram[base + x]; + print!("{:02X} ", mem_val); + } + println!(); + } + _ => {} } - _ => {} } + Err(stdin_err) => panic!("Failed to lock stdin: {:?}", stdin_err), } - Err(stdin_err) => panic!("Failed to lock stdin: {:?}", stdin_err), + + diff = Some((chrono::Utc::now() - entered_step).num_milliseconds()); + if exit { + return (false, diff); + } + } + if self.timer.tick() { + self.interrupts.write_if_timer(true); } - diff = Some((chrono::Utc::now() - entered_step).num_milliseconds()); - if exit { - return (false, diff); + cpu::tick_cpu(self); + let redraw_requested = self.ppu.tick(&mut self.interrupts); + self.tick_dma(); + if self.serial.tick() { + self.interrupts.write_if_serial(true); } + self.tick_count += 1; + (redraw_requested, diff) + } else { + let redraw_requested = self.ppu.tick(&mut self.interrupts); + self.tick_count += 1; + self.tick_count %= 4; + (redraw_requested, None) } - if self.timer.tick() { - self.interrupts.write_if_timer(true); - } - - cpu::tick_cpu(self); - let redraw_requested = self.ppu.tick(&mut self.interrupts); - self.tick_dma(); - if self.serial.tick() { - self.interrupts.write_if_serial(true); - } - (redraw_requested, diff) } fn tick_dma(&mut self) { - if self.dma.remaining_delay > 0 { - self.dma.remaining_delay -= 1; - } else if self.dma.remaining_cycles > 0 { + self.ppu.dma_occuring = self.dma.remaining_cycles > 0; + if self.dma.remaining_cycles > 0 { let offset = 0xA0 - self.dma.remaining_cycles; let value = if self.dma.base <= 0x7F { @@ -376,13 +424,14 @@ impl Gameboy { None => 0xFF, } } else if self.dma.base <= 0x9F { - self.ppu.dma_read_vram(offset) + let address = (((self.dma.base as usize) << 8) | offset as usize) - 0x8000; + self.ppu.vram[address] } else if self.dma.base <= 0xDF { - let address = (self.dma.base as u16) << 8 | offset as u16; - self.memory.wram[address as usize - 0xC000] + let address = ((self.dma.base as usize) << 8 | offset as usize) - 0xC000; + self.memory.wram[address] } else if self.dma.base <= 0xFD { - let address = (self.dma.base as u16) << 8 | offset as u16; - self.memory.wram[address as usize - 0xE000] + let address = ((self.dma.base as usize) << 8 | offset as usize) - 0xE000; + self.memory.wram[address] } else { 0xFF }; @@ -430,13 +479,15 @@ impl Gameboy { 0xFF27..=0xFF2F => 0xFF, 0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30], 0xFF40 => self.ppu.lcdc, - 0xFF41 => self.ppu.stat, + 0xFF41 => self.ppu.get_stat(), 0xFF42 => self.ppu.scy, 0xFF43 => self.ppu.scx, 0xFF44 => self.ppu.ly, 0xFF45 => self.ppu.lyc, 0xFF46 => self.dma.base, - 0xFF47..=0xFF49 => 0xFF, + 0xFF47 => self.ppu.bgp.value(), + 0xFF48 => self.ppu.obp[0].value(), + 0xFF49 => self.ppu.obp[1].value(), 0xFF4A => self.ppu.wy, 0xFF4B => self.ppu.wx, 0xFF4C..=0xFF4E => 0xFF, // Unused @@ -489,18 +540,29 @@ impl Gameboy { 0xFF26 => self.sound.nr52 = value, 0xFF27..=0xFF2F => {} 0xFF30..=0xFF3F => self.sound.wave_pattern_ram[address as usize - 0xFF30] = value, - 0xFF40 => self.ppu.lcdc = value, - 0xFF41 => self.ppu.cpu_write_stat(value), + 0xFF40 => { + let old_value = self.ppu.lcdc; + self.ppu.lcdc = value; + + if value >> 7 == 0 && old_value >> 7 == 1 { + self.ppu.stop(); + } else if value >> 7 == 1 && old_value >> 7 == 0 { + self.ppu.start(&mut self.interrupts); + } + } + 0xFF41 => self.ppu.set_stat(&mut self.interrupts, value), 0xFF42 => self.ppu.scy = value, 0xFF43 => self.ppu.scx = value, 0xFF44 => {} // LY is read only - 0xFF45 => self.ppu.lyc = value, + 0xFF45 => self.ppu.set_lyc(&mut self.interrupts, value), 0xFF46 => { if self.dma.remaining_cycles == 0 { self.dma.init_request(value); } } - 0xFF47..=0xFF49 => {} + 0xFF47 => self.ppu.bgp.write_bgp(value), + 0xFF48 => self.ppu.obp[0].write_obp(value), + 0xFF49 => self.ppu.obp[1].write_obp(value), 0xFF4A => self.ppu.wy = value, 0xFF4B => self.ppu.wx = value, 0xFF4C..=0xFF4E => {} // Unused diff --git a/deemgee/src/gameboy/cpu.rs b/deemgee/src/gameboy/cpu.rs index 82e7072..c41f492 100644 --- a/deemgee/src/gameboy/cpu.rs +++ b/deemgee/src/gameboy/cpu.rs @@ -188,7 +188,8 @@ pub fn tick_cpu(state: &mut Gameboy) { } if state.registers.cycle == 0 && state.interrupts.ei_queued { - state.interrupts.cycle_passed = true; + state.interrupts.ime = state.interrupts.ei_queued; + state.interrupts.ei_queued = false; } if state.registers.cycle == 0 && state.halt_bug { @@ -527,12 +528,6 @@ pub fn tick_cpu(state: &mut Gameboy) { state.registers.pc = state.registers.pc.overflowing_add(1).0; } - if state.interrupts.cycle_passed && state.interrupts.ei_queued { - state.interrupts.cycle_passed = false; - state.interrupts.ei_queued = false; - state.interrupts.ime = true; - } - if result == CycleResult::Finished { match state.registers.opcode_bytecount { Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0, diff --git a/deemgee/src/gameboy/joypad.rs b/deemgee/src/gameboy/joypad.rs index 7347bef..a7fa653 100644 --- a/deemgee/src/gameboy/joypad.rs +++ b/deemgee/src/gameboy/joypad.rs @@ -2,6 +2,7 @@ pub enum JoypadMode { Action, Direction, + Both, } macro_rules! joypad_input { @@ -17,6 +18,7 @@ macro_rules! joypad_input { }; } +#[derive(Debug)] pub struct Joypad { mode: JoypadMode, pub down: bool, @@ -33,7 +35,7 @@ pub struct Joypad { impl Joypad { pub fn new() -> Self { Self { - mode: JoypadMode::Action, + mode: JoypadMode::Direction, down: false, up: false, left: false, @@ -62,6 +64,7 @@ impl Joypad { | ((!self.left as u8) << 1) | (!self.right as u8) } + JoypadMode::Both => 0x3F, } } @@ -77,10 +80,10 @@ impl Joypad { pub fn cpu_write(&mut self, content: u8) { if (content >> 5) & 0b1 == 0 { self.mode = JoypadMode::Action; - } - - if (content >> 4) & 0b1 == 0 { + } else if (content >> 4) & 0b1 == 0 { self.mode = JoypadMode::Direction; + } else { + self.mode = JoypadMode::Both; } } } diff --git a/deemgee/src/gameboy/mapper/mbc1.rs b/deemgee/src/gameboy/mapper/mbc1.rs index cfdd3fd..3bfa739 100644 --- a/deemgee/src/gameboy/mapper/mbc1.rs +++ b/deemgee/src/gameboy/mapper/mbc1.rs @@ -63,7 +63,7 @@ impl MBC1 { } fn is_large_rom(&self) -> bool { - self.rom_bank_count > 4 + self.rom_bank_count >= 64 } #[allow(unused)] diff --git a/deemgee/src/gameboy/ppu.rs b/deemgee/src/gameboy/ppu.rs index 7d04791..847d6c4 100644 --- a/deemgee/src/gameboy/ppu.rs +++ b/deemgee/src/gameboy/ppu.rs @@ -1,15 +1,72 @@ -use std::ops::{Index, IndexMut}; +use super::interrupts::Interrupts; -use bmp::{Image, Pixel}; +pub const FB_HEIGHT: u32 = 144; +pub const FB_WIDTH: u32 = 160; -use crate::{ - gameboy::Interrupts, - window::{FB_HEIGHT, FB_WIDTH}, -}; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Palette { + id3: Color, + id2: Color, + id1: Color, + id0: Color, +} + +impl Palette { + pub fn color_from_2bit(&self, value: u8) -> Color { + match value & 0b11 { + 0 => self.id0, + 1 => self.id1, + 2 => self.id2, + 3 => self.id3, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum LineDrawingState { + /// (Cycles left, SCX, SCY) + BackgroundScrolling(usize, u8, u8), + /// (original SCX, original SCY, drawn pixel count, window drawn, draw only + /// sprites) + BackgroundAndObjectFifo(u8, u8, u8, bool, bool), + Finished, +} + +impl Palette { + pub fn new_bgp() -> Self { + Self { id0: Color::White, id1: Color::LGray, id2: Color::DGray, id3: Color::Black } + } + + pub fn new_obp() -> Self { + Self { id0: Color::Transparent, id1: Color::LGray, id2: Color::DGray, id3: Color::Black } + } + + pub fn write_bgp(&mut self, value: u8) { + self.id0 = Color::from_bg_2bit(value); + self.id1 = Color::from_bg_2bit(value >> 2); + self.id2 = Color::from_bg_2bit(value >> 4); + self.id3 = Color::from_bg_2bit(value >> 6); + } + + pub fn write_obp(&mut self, value: u8) { + self.id0 = Color::from_obj_2bit(value); + self.id1 = Color::from_obj_2bit(value >> 2); + self.id2 = Color::from_obj_2bit(value >> 4); + self.id3 = Color::from_obj_2bit(value >> 6); + } + + pub fn value(&self) -> u8 { + (self.id0.to_2bit()) + | (self.id1.to_2bit() << 2) + | (self.id2.to_2bit() << 4) + | (self.id3.to_2bit() << 6) + } +} pub struct WrappedBuffer([u8; SIZE]); -impl Index for WrappedBuffer { +impl std::ops::Index for WrappedBuffer { type Output = u8; fn index(&self, index: usize) -> &Self::Output { @@ -17,7 +74,7 @@ impl Index for WrappedBuffer { } } -impl IndexMut for WrappedBuffer { +impl std::ops::IndexMut for WrappedBuffer { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index % SIZE] } @@ -29,7 +86,7 @@ impl WrappedBuffer { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PPUMode { /// Mode 0 HBlank, @@ -51,19 +108,35 @@ pub enum Color { } impl Color { + const WHITE: [u8; 4] = [0xe0, 0xf8, 0xd0, 0xFF]; + const LGRAY: [u8; 4] = [0x88, 0xc0, 0x70, 0xFF]; + const DGRAY: [u8; 4] = [0x34, 0x68, 0x56, 0xFF]; + const BLACK: [u8; 4] = [0x08, 0x18, 0x20, 0xFF]; + const TRANSPARENT: [u8; 4] = [0x00, 0x00, 0x00, 0x00]; pub fn rgba(self) -> &'static [u8; 4] { match self { - Color::White => &[0x7F, 0x86, 0x0F, 0xFF], - Color::LGray => &[0x57, 0x7c, 0x44, 0xFF], - Color::DGray => &[0x36, 0x5d, 0x48, 0xFF], - Color::Black => &[0x2a, 0x45, 0x3b, 0xFF], - Color::Transparent => &[0x00, 0x00, 0x00, 0x00], + Color::White => &Self::WHITE, + Color::LGray => &Self::LGRAY, + Color::DGray => &Self::DGRAY, + Color::Black => &Self::BLACK, + Color::Transparent => &Self::TRANSPARENT, } } - pub fn parse_bgp_color(low: u8, high: u8) -> Self { - let color = ((high & 0b1) << 1) | low & 0b1; - match color & 0b11 { + #[allow(unused)] + pub fn from_rgba(rgba: [u8; 4]) -> Option { + match rgba { + Self::WHITE => Some(Self::White), + Self::LGRAY => Some(Self::LGray), + Self::DGRAY => Some(Self::DGray), + Self::BLACK => Some(Self::Black), + Self::TRANSPARENT => Some(Self::Transparent), + _ => None, + } + } + + pub fn from_bg_2bit(value: u8) -> Self { + match value & 0b11 { 0 => Self::White, 1 => Self::LGray, 2 => Self::DGray, @@ -72,9 +145,8 @@ impl Color { } } - pub fn parse_obp_color(low: u8, high: u8) -> Self { - let color = ((high & 0b1) << 1) | low & 0b1; - match color & 0b11 { + pub fn from_obj_2bit(value: u8) -> Self { + match value & 0b11 { 0 => Self::Transparent, 1 => Self::LGray, 2 => Self::DGray, @@ -83,10 +155,31 @@ impl Color { } } - pub fn parse_bgp(mut bgp_low: u8, mut bgp_high: u8) -> [Self; 8] { + pub fn to_2bit(&self) -> u8 { + match self { + Color::White => 0, + Color::LGray => 1, + Color::DGray => 2, + Color::Black => 3, + Color::Transparent => 0, + } + } + + pub fn parse_bgp_color(low: u8, high: u8, palette: &Palette) -> (u8, Self) { + let color = ((high & 0b1) << 1) | low & 0b1; + match color & 0b11 { + 0 => (color & 0b11, palette.id0), + 1 => (color & 0b11, palette.id1), + 2 => (color & 0b11, palette.id2), + 3 => (color & 0b11, palette.id3), + _ => unreachable!(), + } + } + + pub fn parse_bgp(mut bgp_low: u8, mut bgp_high: u8, palette: &Palette) -> [Self; 8] { let mut out = [Self::White; 8]; for color in &mut out { - *color = Self::parse_bgp_color(bgp_low, bgp_high); + *color = Self::parse_bgp_color(bgp_low, bgp_high, palette).1; bgp_low >>= 1; bgp_high >>= 1; } @@ -95,7 +188,7 @@ impl Color { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct OAMEntry { pub y: u8, pub x: u8, @@ -103,6 +196,12 @@ pub struct OAMEntry { pub flags: u8, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum SpriteHeight { + Eight = 8, + Sixteen = 16, +} + impl OAMEntry { pub fn parse(entry: [u8; 4]) -> Self { Self { y: entry[0], x: entry[1], tile_idx: entry[2], flags: entry[3] } @@ -120,7 +219,6 @@ impl OAMEntry { (self.flags >> 5) & 0b1 == 1 } - #[allow(unused)] pub fn palette_number(&self) -> bool { (self.flags >> 4) & 0b1 == 1 } @@ -135,42 +233,96 @@ impl PPUMode { PPUMode::TransferringData => 3, } } +} - pub fn from_mode_flag(value: u8) -> Self { - match value & 0b11 { - 0 => Self::HBlank, - 1 => Self::VBlank, - 2 => Self::SearchingOAM, - 3 => Self::TransferringData, - _ => unreachable!(), - } +#[derive(Debug, Clone, Copy, Default)] +pub struct StatFlags { + pub lyc_int: bool, + pub mode2_int: bool, + pub mode1_int: bool, + pub mode0_int: bool, +} + +impl StatFlags { + pub fn flag_bits(&self) -> u8 { + ((self.lyc_int as u8) << 6) + | ((self.mode2_int as u8) << 5) + | ((self.mode1_int as u8) << 4) + | ((self.mode0_int as u8) << 3) + } + + pub fn from_bits(&mut self, stat: u8) { + self.lyc_int = (stat >> 6) & 1 == 1; + self.mode2_int = (stat >> 5) & 1 == 1; + self.mode1_int = (stat >> 4) & 1 == 1; + self.mode0_int = (stat >> 3) & 1 == 1; } } pub struct Ppu { pub lcdc: u8, - pub stat: u8, + stat_flags: StatFlags, + mode: PPUMode, pub scy: u8, pub scx: u8, pub ly: u8, pub lyc: u8, pub wy: u8, pub wx: u8, + pub vram: [u8; 0x2000], pub oam: [u8; 0xA0], - pub cycle_counter: u16, - pub bgp: u8, - pub obp: u8, + + pub bgp: Palette, + pub obp: [Palette; 2], pub framebuffer: WrappedBuffer<{ 160 * 144 * 4 }>, pub sprite_framebuffer: WrappedBuffer<{ 160 * 144 * 4 }>, + + ly_lyc: bool, + + // Internals + current_dot: u16, + dot_target: u16, + + sprite_buffer: [Option; 10], + sprite_count: usize, + + pub dma_occuring: bool, + + current_draw_state: Option, + wy_match: bool, + + first_frame: bool, + first_line: bool, + total_dots: u16, + + is_irq_high: bool, } impl Ppu { - pub fn new() -> Self { + pub fn stop(&mut self) { + self.current_dot = 0; + self.dot_target = 0; + self.sprite_buffer = [None; 10]; + self.sprite_count = 0; + self.current_draw_state = None; + self.wy_match = false; + self.mode = PPUMode::HBlank; + self.first_frame = true; + self.first_line = true; + self.total_dots = 0; + } + + pub fn start(&mut self, interrupts: &mut Interrupts) { + self.set_scanline(interrupts, 0); + } + + pub fn new(bootrom_ran: bool) -> Self { Self { - lcdc: 0b1000_0000, - stat: 0b0000_0010, + lcdc: (!bootrom_ran).then_some(0b1000_0000).unwrap_or_default(), + stat_flags: StatFlags::default(), + mode: PPUMode::HBlank, scy: 0, scx: 0, ly: 0, @@ -179,235 +331,540 @@ impl Ppu { wx: 0, vram: [0; 0x2000], oam: [0; 0xA0], - cycle_counter: 0, framebuffer: WrappedBuffer::empty(), sprite_framebuffer: WrappedBuffer::empty(), - bgp: 0, - obp: 0, + bgp: Palette::new_bgp(), + obp: [Palette::new_obp(), Palette::new_obp()], + + ly_lyc: true, + + current_dot: 0, + dot_target: 0, + sprite_buffer: [None; 10], + sprite_count: 0, + dma_occuring: false, + current_draw_state: None, + wy_match: false, + first_frame: true, + total_dots: 0, + first_line: true, + is_irq_high: false, } } - pub fn dump_fb(&self) -> String { - let mut image = Image::new(FB_WIDTH, FB_HEIGHT); + pub fn handle_stat_irq(&mut self, interrupts: &mut Interrupts) { + if self.enabled() { + let old_irq_high = self.is_irq_high; - for y in 0..FB_HEIGHT { - for x in 0..FB_WIDTH { - let base = ((y as usize * FB_WIDTH as usize) + x as usize) * 4; - image.set_pixel( - x, - y, - Pixel::new( - self.framebuffer[base], - self.framebuffer[base + 1], - self.framebuffer[base + 2], - ), - ); - } - } + let stat_int = match self.mode { + PPUMode::HBlank => self.stat_flags.mode0_int, + PPUMode::VBlank => self.stat_flags.mode1_int, + PPUMode::SearchingOAM => self.stat_flags.mode2_int, + PPUMode::TransferringData => false, + }; - let now = chrono::Utc::now(); - std::fs::create_dir_all("./bmp").unwrap(); - let file_name = format!("./bmp/fb-{}.bmp", now.timestamp()); - image.save(file_name.as_str()).unwrap(); - file_name - } + let vblank_causing_oam_int_bug = + self.mode == PPUMode::VBlank && self.ly == 144 && self.stat_flags.mode2_int; - pub fn dump_bg_tiles(&self) { - let mut image = Image::new(16 * 8, 16 * 9); + let ly_eq_lyc_int = self.ly == self.lyc && self.stat_flags.lyc_int; - for tile_y in 0..16 { - for tile_x in 0..16 { - let tiledata = self.read_bg_win_tile(tile_y * 16 + tile_x); - for row in 0..8usize { - let base = row * 2; + self.is_irq_high = stat_int || ly_eq_lyc_int || vblank_causing_oam_int_bug; - let pixels = Color::parse_bgp(tiledata[base], tiledata[base + 1]); - - for (x, color) in pixels.iter().enumerate() { - let pixel = color.rgba(); - image.set_pixel( - (tile_x as u32 * 8) + x as u32, - tile_y as u32 * 9 + row as u32, - Pixel::new(pixel[0], pixel[1], pixel[2]), - ); - } - } - - for x in 0..8 { - let color = Pixel::new(255 / (x + 1), 255 / (x + 1), 255 / (x + 1)); - image.set_pixel((tile_x as u32 * 8) + x as u32, 9 * tile_y as u32, color); - } - } - } - - let now = chrono::Utc::now(); - std::fs::create_dir_all("./bmp").unwrap(); - let file_name = format!("./bmp/bg-data-{}.bmp", now.timestamp()); - image.save(file_name.as_str()).unwrap(); - } - - fn set_scanline(&mut self, interrupts: &mut Interrupts, scanline: u8) { - self.ly = scanline; - - self.stat &= !(1 << 2); - if self.ly == self.lyc { - self.stat |= 1 << 2; - - if (self.stat >> 6) & 0b1 == 1 { + if !old_irq_high && self.is_irq_high { interrupts.write_if_lcd_stat(true); } - } else { - self.stat &= !(1 << 2); } } - fn draw_line(&mut self) { - let scrolled_y = self.ly.overflowing_add(self.scy).0 as usize; - for pixel_idx in 0..FB_WIDTH as u8 { - let scrolled_x = pixel_idx.overflowing_add(self.scx).0 as usize; - - // BG - let tilemap_idx = scrolled_x / 8 + ((scrolled_y as usize / 8) * 32); - let tilemap_value = self.read_tile_map()[tilemap_idx]; - let color = Self::parse_tile_color( - self.read_bg_win_tile(tilemap_value), - scrolled_x % 8, - scrolled_y % 8, - ); - let dest_idx_base = ((self.ly as usize * FB_WIDTH as usize) + pixel_idx as usize) * 4; - for (idx, byte) in color.rgba().iter().enumerate() { - self.framebuffer[dest_idx_base + idx] = *byte; + pub fn set_lyc(&mut self, interrupts: &mut Interrupts, value: u8) { + if self.lyc != value { + self.lyc = value; + if self.enabled() { + self.handle_stat_irq(interrupts); + self.ly_lyc = self.ly == self.lyc; } } - - // Sprite - let mut found_sprites = 0; - let mut sprite_line = [0u8; 256 * 4]; - let sprite_height = if (self.lcdc >> 2) & 0b1 == 1 { 16 } else { 8 }; - - for x in 0..40 { - if found_sprites >= 10 { - break; - } - - let oam_offset = x * 4; - let entry = OAMEntry::parse([ - self.oam[oam_offset], - self.oam[oam_offset + 1], - self.oam[oam_offset + 2], - self.oam[oam_offset + 3], - ]); - - let mut base = entry.y.overflowing_sub(sprite_height).0; - let mut in_range = None; - - for x in 0..sprite_height { - if base as usize == scrolled_y { - in_range = Some(x); - found_sprites += 1; - break; - } - base = base.overflowing_add(1).0; - } - - if let Some(mut tile_y_idx) = in_range { - let is_second_tile = tile_y_idx >= 8; - - if is_second_tile { - tile_y_idx -= 8; - } - - if entry.y_flip() { - tile_y_idx = 7 - tile_y_idx; - } - - let tile_idx = - if is_second_tile { entry.tile_idx | 1 } else { entry.tile_idx & 0xFE }; - - for x in 0..8 { - let fb_x = entry.x.overflowing_sub(7 - x).0; - - let sprite_line_base = fb_x as usize * 4; - - let tile_x_idx = if entry.x_flip() { 7 - x } else { x }; - - let color = Self::parse_sprite_tile_color( - self.read_obj_tile(tile_idx), - tile_x_idx as usize, - tile_y_idx as usize, - ); - - let bg_fb_idx = (((self.ly as usize + self.scy as usize) * FB_WIDTH as usize) - + fb_x as usize) * 4; - let ok_to_draw = if entry.covered_by_bg_window() { - let rgba = [ - self.framebuffer[bg_fb_idx], - self.framebuffer[bg_fb_idx + 1], - self.framebuffer[bg_fb_idx + 2], - self.framebuffer[bg_fb_idx + 3], - ]; - &rgba != Color::Black.rgba() - } else { - true - }; - - if ok_to_draw { - for (idx, byte) in color.rgba().iter().enumerate() { - sprite_line[sprite_line_base + idx] = *byte; - } - } - } - } - } - - for x in (self.scx as usize)..(self.scx as usize + FB_WIDTH as usize) { - let x = x % FB_WIDTH as usize; - - let base = x * 4; - - self.sprite_framebuffer[base] = sprite_line[x]; - self.sprite_framebuffer[base + 1] = sprite_line[x + 1]; - self.sprite_framebuffer[base + 2] = sprite_line[x + 2]; - self.sprite_framebuffer[base + 3] = sprite_line[x + 3]; - } } - fn parse_sprite_tile_color(tile: &[u8], x: usize, y: usize) -> Color { - assert!(x < 8); - assert!(y < 8); - let bitshift = 7 - x; - Color::parse_obp_color(tile[y * 2] >> bitshift, tile[(y * 2) + 1] >> bitshift) - } - - fn parse_tile_color(tile: &[u8], x: usize, y: usize) -> Color { - assert!(x < 8); - assert!(y < 8); - let bitshift = 7 - x; - Color::parse_bgp_color(tile[y * 2] >> bitshift, tile[(y * 2) + 1] >> bitshift) - } - - fn set_mode(&mut self, interrupts: &mut Interrupts, mode: PPUMode) { - log::trace!("PPU switching mode to {:?} @ {}", mode, self.cycle_counter); - self.stat &= !0b11; - self.stat |= mode.mode_flag(); - self.cycle_counter = 0; - - let offset = match mode { - PPUMode::HBlank => 3, - PPUMode::VBlank => 4, - PPUMode::SearchingOAM => 5, - _ => return, + pub fn get_stat(&self) -> u8 { + let ly_eq_lyc = match self.enabled() { + true => self.ly == self.lyc, + false => self.ly_lyc, }; + (1 << 7) | self.stat_flags.flag_bits() | ((ly_eq_lyc as u8) << 2) | self.mode().mode_flag() + } - if (self.stat >> offset) & 0b1 == 1 { - interrupts.write_if_lcd_stat(true); + pub fn set_stat(&mut self, interrupts: &mut Interrupts, value: u8) { + self.stat_flags.from_bits(value); + self.handle_stat_irq(interrupts); + } + + pub fn sprite_height(&self) -> SpriteHeight { + match (self.lcdc >> 2) == 1 { + false => SpriteHeight::Eight, + true => SpriteHeight::Sixteen, } + } + + pub fn mode(&self) -> PPUMode { + self.mode + } + + pub fn read_window_tile_map(&self) -> &[u8] { + match (self.lcdc >> 6) & 0b1 == 1 { + true => &self.vram[0x1C00..=0x1FFF], + false => &self.vram[0x1800..=0x1BFF], + } + } + + pub fn read_tile_map(&self) -> &[u8] { + match (self.lcdc >> 3) & 0b1 == 1 { + true => &self.vram[0x1C00..=0x1FFF], + false => &self.vram[0x1800..=0x1BFF], + } + } + + pub fn read_obj_tile_colour_id(&self, tile_idx: u8, x: usize, y: usize) -> u8 { + assert!(x < 8); + assert!(y < 8); + let bitshift = 7 - x; + let offset = (tile_idx as usize * 16) + (y * 2); + let low = self.vram[offset] >> bitshift; + let high = self.vram[offset + 1] >> bitshift; + ((high & 0b1) << 1) | low & 0b1 + } + + fn internal_read_oam(&mut self, offset: usize) -> u8 { + match self.dma_occuring { + true => 0xFF, + false => self.oam[offset as usize], + } + } + + pub fn dma_write_oam(&mut self, offset: u8, value: u8) { + assert!(self.dma_occuring); + self.oam[offset as usize] = value; + } + + pub fn cpu_read_oam(&self, address: u16) -> u8 { + let decoded_address = address - 0xFE00; + if self.enabled() && !self.first_frame { + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank => self.oam[decoded_address as usize], + PPUMode::SearchingOAM | PPUMode::TransferringData => 0xFF, + } + } else { + self.oam[decoded_address as usize] + } + } + + pub fn cpu_write_oam(&mut self, address: u16, value: u8) { + let decoded_address = address - 0xFE00; + if self.enabled() && !self.first_frame { + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank => self.oam[decoded_address as usize] = value, + PPUMode::SearchingOAM | PPUMode::TransferringData => {} + } + } else { + self.oam[decoded_address as usize] = value + } + } + + pub fn cpu_read_vram(&self, address: u16) -> u8 { + let decoded_address = address - 0x8000; + if self.enabled() && !self.first_frame { + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank | PPUMode::SearchingOAM => { + self.vram[decoded_address as usize] + } + PPUMode::TransferringData => 0xFF, + } + } else { + self.vram[decoded_address as usize] + } + } + + pub fn cpu_write_vram(&mut self, address: u16, value: u8) { + let decoded_address = address - 0x8000; + if self.enabled() && !self.first_frame { + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank | PPUMode::SearchingOAM => { + self.vram[decoded_address as usize] = value + } + PPUMode::TransferringData => {} + } + } else { + self.vram[decoded_address as usize] = value + } + } + + pub fn enabled(&self) -> bool { + (self.lcdc >> 7) == 1 + } + + pub fn set_mode(&mut self, interrupts: &mut Interrupts, mode: PPUMode) { + if mode == PPUMode::HBlank { + assert_eq!(self.mode(), PPUMode::TransferringData); + assert!(self.current_dot >= 172); + assert!(self.current_dot <= 289); + self.dot_target = 376 - self.dot_target; + assert!(self.dot_target >= 87); + assert!(self.dot_target <= 204); + } else if mode == PPUMode::TransferringData { + if !self.first_frame { + assert_eq!(self.mode(), PPUMode::SearchingOAM); + } else if self.ly == 0 { + assert_eq!(self.mode(), PPUMode::HBlank); + } + self.current_draw_state = None; + self.dot_target = 160 + 12; + } + + self.mode = mode; + self.current_dot = 0; + + self.handle_stat_irq(interrupts); if mode == PPUMode::VBlank { interrupts.write_if_vblank(true); } } + fn set_scanline(&mut self, interrupts: &mut Interrupts, scanline: u8) { + self.ly = scanline; + self.handle_stat_irq(interrupts); + self.ly_lyc = self.ly == self.lyc; + } + + pub fn tick(&mut self, interrupts: &mut Interrupts) -> bool { + if self.enabled() { + match self.mode() { + PPUMode::SearchingOAM => { + if self.current_dot == 0 { + if self.ly == 0 { + self.wy_match = self.wy == self.ly; + } + self.sprite_buffer = [None; 10]; + self.sprite_count = 0; + } + + if self.current_dot % 2 == 0 { + let oam_item_idx: usize = (self.current_dot as usize / 2) * 4; + + let oam_entry = OAMEntry::parse([ + self.internal_read_oam(oam_item_idx), + self.internal_read_oam(oam_item_idx + 1), + self.internal_read_oam(oam_item_idx + 2), + self.internal_read_oam(oam_item_idx + 3), + ]); + + let sprite_height = self.sprite_height(); + + let real_oam_y = + oam_entry.y.wrapping_sub(16).wrapping_add(sprite_height as u8); + + if oam_entry.x > 0 + && self.ly.wrapping_add(self.scy) < real_oam_y + && self.ly.wrapping_add(self.scy) >= oam_entry.y.wrapping_sub(16) + && self.sprite_count < 10 + { + self.sprite_buffer[self.sprite_count] = Some(oam_entry); + self.sprite_count += 1; + } + } + + self.current_dot += 1; + self.total_dots += 1; + + if self.current_dot == 80 { + self.set_mode(interrupts, PPUMode::TransferringData); + assert_eq!(self.total_dots, 80); + } else { + assert!(self.current_dot < 80); + } + + false + } + PPUMode::TransferringData => { + if !self.first_frame && self.current_dot == 0 { + assert_eq!(self.total_dots, 80); + } + // assert!(self.current_dot < self.dot_target); + + if self.current_dot >= 12 { + match self.current_draw_state { + Some(LineDrawingState::Finished) => {} + _ => { + self.draw_pixel(); + } + } + } + + self.current_dot += 1; + self.total_dots += 1; + + // if self.current_dot == self.dot_target - 1 { + // self.handle_stat_irq(interrupts, true); + // } + + if self.current_dot == self.dot_target { + // assert_eq!(self.total_dots, match self.first_frame && self.first_line { + // true => self.dot_target, + // false => 80 + self.dot_target + // }); + assert_eq!(self.current_draw_state, Some(LineDrawingState::Finished)); + self.set_mode(interrupts, PPUMode::HBlank); + } + + false + } + PPUMode::HBlank => { + if self.first_line && self.current_dot == 16 && self.dot_target == 0 { + // Special case for resets + // self.handle_stat_irq(interrupts, false); + self.set_mode(interrupts, PPUMode::TransferringData); + } else if self.dot_target != 0 && self.current_dot == self.dot_target { + self.set_scanline(interrupts, self.ly + 1); + + assert_eq!( + self.total_dots, + match self.first_frame && self.first_line { + true => 456 - (80 - 16), + false => 456, + } + ); + self.total_dots = 0; + self.first_line = false; + + let next_mode = match self.ly > 143 { + true => PPUMode::VBlank, + false => PPUMode::SearchingOAM, + }; + + self.set_mode(interrupts, next_mode); + } else { + self.current_dot += 1; + self.total_dots += 1; + if !self.first_line { + assert_ne!(self.dot_target, 0); + } + } + + false + } + PPUMode::VBlank => { + if self.current_dot != 0 && self.current_dot % 456 == 0 { + if self.ly >= 153 { + self.set_scanline(interrupts, 0); + self.set_mode(interrupts, PPUMode::SearchingOAM); + self.first_frame = false; + true + } else { + self.set_scanline(interrupts, self.ly + 1); + self.current_dot += 1; + false + } + } else { + assert!(self.current_dot < 4560); + self.current_dot += 1; + false + } + } + } + } else { + false + } + } + + fn parse_tile_color(tile: &[u8], x: usize, y: usize, palette: &Palette) -> (u8, Color) { + assert!(x < 8); + assert!(y < 8); + let bitshift = 7 - x; + Color::parse_bgp_color(tile[y * 2] >> bitshift, tile[(y * 2) + 1] >> bitshift, palette) + } + + fn clear_line_sprite_fb(&mut self, real_line_number: usize) { + assert!(real_line_number < FB_HEIGHT as usize); + for value in 0..256 { + let idx = ((real_line_number * FB_WIDTH as usize) + value) * 4; + self.sprite_framebuffer[idx] = 0; + self.sprite_framebuffer[idx + 1] = 0; + self.sprite_framebuffer[idx + 2] = 0; + self.sprite_framebuffer[idx + 3] = 0; + } + } + + fn draw_pixel(&mut self) { + let state = match self.current_draw_state.take() { + Some(state) => state, + None => { + let scrolling_delay = self.scx % 8; + self.dot_target += scrolling_delay as u16; + if scrolling_delay != 0 { + LineDrawingState::BackgroundScrolling( + scrolling_delay as usize, + self.scx, + self.scy, + ) + } else { + self.clear_line_sprite_fb(self.ly as usize); + LineDrawingState::BackgroundAndObjectFifo( + self.scx, + self.scy, + 0, + false, + self.lcdc & 0b1 == 0, + ) + } + } + }; + + match state { + LineDrawingState::BackgroundScrolling(mut remaining_cycles, scx, scy) => { + assert_ne!(remaining_cycles, 0); + + remaining_cycles -= 1; + self.current_draw_state = + Some(LineDrawingState::BackgroundScrolling(remaining_cycles, scx, scy)); + if remaining_cycles == 0 { + self.current_draw_state = Some(LineDrawingState::BackgroundAndObjectFifo( + scx, + scy, + 0, + false, + self.lcdc & 0b1 == 0, + )); + self.clear_line_sprite_fb(self.ly as usize); + } + } + LineDrawingState::BackgroundAndObjectFifo( + scx, + scy, + mut drawn_pixels, + mut window_drawn, + draw_only_sprites, + ) => { + let wx_match = (drawn_pixels as usize + 7) >= self.wx as usize; + let scrolled_y = self.ly.wrapping_add(scy) as usize; + let scrolled_x = drawn_pixels.wrapping_add(scx) as usize; + + let (bg_color_id, bg_color) = match draw_only_sprites { + true => (0, Color::White), + false => { + let tilemap_idx = scrolled_x / 8 + ((scrolled_y / 8) * 32); + let tilemap_value = self.read_tile_map()[tilemap_idx]; + let (mut bg_color_id, mut bg_color) = Self::parse_tile_color( + self.read_bg_win_tile(tilemap_value), + scrolled_x % 8, + scrolled_y % 8, + &self.bgp, + ); + + if self.window_enabled() && wx_match && self.wy_match { + window_drawn = true; + let tilemap_idx = + drawn_pixels as usize / 8 + ((self.ly as usize / 8) * 32); + let tilemap_value = self.read_window_tile_map()[tilemap_idx]; + (bg_color_id, bg_color) = Self::parse_tile_color( + self.read_bg_win_tile(tilemap_value), + drawn_pixels as usize % 8, + self.ly as usize % 8, + &self.bgp, + ); + } + + (bg_color_id, bg_color) + } + }; + + let framebuffer_offset = + ((self.ly as usize * FB_WIDTH as usize) + drawn_pixels as usize) * 4; + for (idx, byte) in bg_color.rgba().iter().enumerate() { + self.framebuffer[framebuffer_offset + idx] = *byte; + } + + let mut sprite_buffer = Vec::new(); + for sprite_idx in 0..self.sprite_count { + // WARNING: Sprites are not scrolled, they have an absolute position! + let sprite = self.sprite_buffer[sprite_idx] + .as_ref() + .expect("within the sprite count there should be non `None`s"); + + let x_valid = + drawn_pixels < sprite.x && drawn_pixels.wrapping_add(8) >= sprite.x; + let y_valid = self.ly < sprite.y && self.ly.wrapping_add(16) >= sprite.y; + + if x_valid && y_valid { + sprite_buffer.push(*sprite); + } + } + + sprite_buffer.sort_by(|l, r| l.x.cmp(&r.x)); + + // TODO: Adjust mode length based on sprites + for sprite in &sprite_buffer { + let mut sprite_x_idx = + drawn_pixels.wrapping_sub(sprite.x.wrapping_sub(8)) as usize; + let mut sprite_y_idx = self.ly.wrapping_sub(sprite.y.wrapping_sub(16)) as usize; + + let tile_idx = match self.sprite_height() { + SpriteHeight::Eight => sprite.tile_idx, + SpriteHeight::Sixteen => match sprite_y_idx >= 8 { + true => sprite.tile_idx | 1, + false => sprite.tile_idx & 0xFE, + }, + }; + + if sprite_y_idx >= 8 { + sprite_y_idx -= 8; + assert!(sprite_y_idx < 8); + } + + if sprite.y_flip() { + sprite_y_idx = 7 - sprite_y_idx; + } + + if sprite.x_flip() { + sprite_x_idx = 7 - sprite_x_idx; + } + + let color_id = + self.read_obj_tile_colour_id(tile_idx, sprite_x_idx, sprite_y_idx); + let palette = &self.obp[sprite.palette_number() as usize]; + let sprite_color = palette.color_from_2bit(color_id); + + if color_id != 0 && !(sprite.covered_by_bg_window() && bg_color_id != 0) { + self.sprite_framebuffer[framebuffer_offset + 0] = sprite_color.rgba()[0]; + self.sprite_framebuffer[framebuffer_offset + 1] = sprite_color.rgba()[1]; + self.sprite_framebuffer[framebuffer_offset + 2] = sprite_color.rgba()[2]; + self.sprite_framebuffer[framebuffer_offset + 3] = sprite_color.rgba()[3]; + } + } + + drawn_pixels += 1; + if drawn_pixels == FB_WIDTH as u8 { + if window_drawn { + self.dot_target += 6; + } + self.current_draw_state = Some(LineDrawingState::Finished); + } else { + self.current_draw_state = Some(LineDrawingState::BackgroundAndObjectFifo( + scx, + scy, + drawn_pixels, + window_drawn, + draw_only_sprites, + )); + } + } + LineDrawingState::Finished => unreachable!(), + } + } + + pub fn window_enabled(&self) -> bool { + ((self.lcdc >> 5) & 0b1) == 1 + } + pub fn write_fb(&self) -> Vec { let mut out = self.framebuffer.0.to_vec(); @@ -432,115 +889,63 @@ impl Ppu { out } - /// One line should be 456 ticks (but we effectively emulate 228 ticks) - pub fn tick(&mut self, interrupts: &mut Interrupts) -> bool { - let res = match self.mode() { - PPUMode::HBlank => { - if self.cycle_counter == (102 / 2) { - self.set_scanline(interrupts, self.ly + 1); + pub fn dump_fb(&self) -> String { + let mut image = bmp::Image::new(FB_WIDTH, FB_HEIGHT); - let next_mode = match self.ly > 143 { - true => PPUMode::VBlank, - false => PPUMode::SearchingOAM, - }; - self.set_mode(interrupts, next_mode); - } - false + for y in 0..FB_HEIGHT { + for x in 0..FB_WIDTH { + let base = ((y as usize * FB_WIDTH as usize) + x as usize) * 4; + image.set_pixel( + x, + y, + bmp::Pixel::new( + self.framebuffer[base], + self.framebuffer[base + 1], + self.framebuffer[base + 2], + ), + ); } - PPUMode::VBlank => { - if self.cycle_counter % (228 / 2) == 0 { - if self.ly >= 153 { - self.set_scanline(interrupts, 0); - self.set_mode(interrupts, PPUMode::SearchingOAM); - true - } else { - self.set_scanline(interrupts, self.ly + 1); - false + } + + let now = chrono::Utc::now(); + std::fs::create_dir_all("./bmp").unwrap(); + let file_name = format!("./bmp/fb-{}.bmp", now.timestamp()); + image.save(file_name.as_str()).unwrap(); + file_name + } + + pub fn dump_bg_tiles(&self) { + let mut image = bmp::Image::new(16 * 8, 16 * 9); + + for tile_y in 0..16 { + for tile_x in 0..16 { + let tiledata = self.read_bg_win_tile(tile_y * 16 + tile_x); + for row in 0..8usize { + let base = row * 2; + + let pixels = Color::parse_bgp(tiledata[base], tiledata[base + 1], &self.bgp); + + for (x, color) in pixels.iter().enumerate() { + let pixel = color.rgba(); + image.set_pixel( + (tile_x as u32 * 8) + x as u32, + tile_y as u32 * 9 + row as u32, + bmp::Pixel::new(pixel[0], pixel[1], pixel[2]), + ); } - } else { - false + } + + for x in 0..8 { + let color = bmp::Pixel::new(255 / (x + 1), 255 / (x + 1), 255 / (x + 1)); + image.set_pixel((tile_x as u32 * 8) + x as u32, 9 * tile_y as u32, color); } } - PPUMode::SearchingOAM => { - if self.cycle_counter == (40 / 2) { - self.set_mode(interrupts, PPUMode::TransferringData); - } - false - } - PPUMode::TransferringData => { - if self.cycle_counter == (86 / 2) { - self.draw_line(); - self.set_mode(interrupts, PPUMode::HBlank); - } - false - } - }; - - self.cycle_counter += 1; - - res - } - - pub fn mode(&self) -> PPUMode { - PPUMode::from_mode_flag(self.stat) - } - - pub fn cpu_read_oam(&self, address: u16) -> u8 { - let decoded_address = address - 0xFE00; - match self.mode() { - PPUMode::SearchingOAM - | PPUMode::TransferringData - | PPUMode::HBlank - | PPUMode::VBlank => self.oam[decoded_address as usize], - //_ => 0xFF, } - } - pub fn dma_write_oam(&mut self, offset: u8, value: u8) { - self.oam[offset as usize] = value; - } - - pub fn cpu_write_oam(&mut self, address: u16, value: u8) { - let decoded_address = address - 0xFE00; - match self.mode() { - PPUMode::SearchingOAM - | PPUMode::TransferringData - | PPUMode::HBlank - | PPUMode::VBlank => self.oam[decoded_address as usize] = value, - // _ => {} - } - } - - pub fn dma_read_vram(&mut self, offset: u8) -> u8 { - self.vram[offset as usize] - } - - pub fn cpu_read_vram(&self, address: u16) -> u8 { - let decoded_address = address - 0x8000; - match self.mode() { - PPUMode::TransferringData - | PPUMode::HBlank - | PPUMode::VBlank - | PPUMode::SearchingOAM => self.vram[decoded_address as usize], /* PPUMode::TransferringData => 0xFF, */ - } - } - - pub fn cpu_write_vram(&mut self, address: u16, value: u8) { - let decoded_address = address - 0x8000; - match self.mode() { - PPUMode::TransferringData - | PPUMode::HBlank - | PPUMode::VBlank - | PPUMode::SearchingOAM => self.vram[decoded_address as usize] = value, //_ => {} - } - } - - pub fn cpu_write_stat(&mut self, value: u8) { - self.stat = value & 0b0111_1000; - } - - pub fn read_obj_tile(&self, idx: u8) -> &[u8] { - &self.vram[idx as usize * 16..((idx as usize + 1) * 16)] + let now = chrono::Utc::now(); + std::fs::create_dir_all("./bmp").unwrap(); + let file_name = format!("./bmp/bg-data-{}.bmp", now.timestamp()); + image.save(file_name.as_str()).unwrap(); } pub fn read_bg_win_tile(&self, idx: u8) -> &[u8] { @@ -554,11 +959,4 @@ impl Ppu { [0x800 + (adjusted_obj as usize * 16)..0x800 + ((adjusted_obj as usize + 1) * 16)] } } - - pub fn read_tile_map(&self) -> &[u8] { - match (self.lcdc >> 3) & 0b1 == 1 { - true => &self.vram[0x1C00..=0x1FFF], - false => &self.vram[0x1800..=0x1BFF], - } - } } diff --git a/deemgee/src/lib.rs b/deemgee/src/lib.rs index b12534b..a868e86 100644 --- a/deemgee/src/lib.rs +++ b/deemgee/src/lib.rs @@ -15,7 +15,7 @@ pub fn setup_test_emulator( gameboy.cartridge = Some(Box::new(cartridge)); - gameboy.tick(); // Prefetch instruction + gameboy.tick_4(); // Prefetch instruction assert!(gameboy.registers.mem_read_hold.is_some()); // Assert prefetch happened and opcode is now sitting in the memory bus assert_eq!(gameboy.registers.cycle, 0); // Assert tick really did just prefetch instruction and not run the opcode at // all diff --git a/deemgee/src/main.rs b/deemgee/src/main.rs index 93abe91..9c9d65f 100644 --- a/deemgee/src/main.rs +++ b/deemgee/src/main.rs @@ -171,7 +171,7 @@ pub fn run_gameboy( } } - let (redraw_needed, time_spent_debugging) = gameboy.tick(); + let (redraw_needed, time_spent_debugging) = gameboy.tick_4(); if let Some(diff) = time_spent_debugging { goal = goal + Duration::milliseconds(diff); diff --git a/deemgee/src/window.rs b/deemgee/src/window.rs index 7ba6a8a..a750b69 100644 --- a/deemgee/src/window.rs +++ b/deemgee/src/window.rs @@ -50,9 +50,6 @@ pub enum GameboyEvent { Framebuffer(Vec), } -pub const FB_HEIGHT: u32 = 144; -pub const FB_WIDTH: u32 = 160; - #[derive(Debug, Default)] struct Keymap { pub down: bool, @@ -108,7 +105,12 @@ pub fn run_window( let mut pixels = { let window_size = window.inner_size(); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); - Pixels::new(FB_WIDTH, FB_HEIGHT, surface_texture).unwrap() + Pixels::new( + crate::gameboy::ppu::FB_WIDTH, + crate::gameboy::ppu::FB_HEIGHT, + surface_texture, + ) + .unwrap() }; let mut redraw_happened = true; diff --git a/deemgee/tests/flow_opcodes.rs b/deemgee/tests/flow_opcodes.rs index ab99de0..fb6ff5b 100644 --- a/deemgee/tests/flow_opcodes.rs +++ b/deemgee/tests/flow_opcodes.rs @@ -9,11 +9,11 @@ macro_rules! conditional_jump_relative_testgen { emulator.registers.[](false); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x103); } @@ -23,9 +23,9 @@ macro_rules! conditional_jump_relative_testgen { emulator.registers.[](true); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x102); } @@ -35,11 +35,11 @@ macro_rules! conditional_jump_relative_testgen { emulator.registers.[](true); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x103); } @@ -49,9 +49,9 @@ macro_rules! conditional_jump_relative_testgen { emulator.registers.[](false); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x102); } } @@ -65,11 +65,11 @@ conditional_jump_relative_testgen!(carry, 0x30, 0x38); fn test_jr_i8() { let mut emulator = setup_test_emulator([0x18, 0x1]); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x103); } @@ -77,13 +77,13 @@ fn test_jr_i8() { fn test_jp_u16() { let mut emulator = setup_test_emulator([0xC3, 0xFE, 0xCA]); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); } @@ -93,7 +93,7 @@ fn test_jp_hl() { emulator.registers.set_hl(0xCAFE); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); } @@ -106,13 +106,13 @@ macro_rules! conditional_jump_testgen { emulator.registers.[](false); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); } @@ -122,11 +122,11 @@ macro_rules! conditional_jump_testgen { emulator.registers.[](true); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x103); } @@ -136,13 +136,13 @@ macro_rules! conditional_jump_testgen { emulator.registers.[](true); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); } @@ -152,11 +152,11 @@ macro_rules! conditional_jump_testgen { emulator.registers.[](false); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x103); } } @@ -172,19 +172,19 @@ fn test_call_u16() { let orignal_sp = emulator.registers.sp; - emulator.tick(); // <-- Read first u8 + emulator.tick_4(); // <-- Read first u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Read second u8 + emulator.tick_4(); // <-- Read second u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Push next instruction PC hi to stack + emulator.tick_4(); // <-- Push next instruction PC hi to stack assert_eq!(emulator.registers.sp, orignal_sp - 1); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Push next instruction PC lo to stack + emulator.tick_4(); // <-- Push next instruction PC lo to stack assert_eq!(emulator.registers.sp, orignal_sp - 2); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01); @@ -202,19 +202,19 @@ macro_rules! conditional_call_testgen { emulator.registers.[](false); - emulator.tick(); // <-- Read first u8 + emulator.tick_4(); // <-- Read first u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Read second u8 + emulator.tick_4(); // <-- Read second u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Check flag + emulator.tick_4(); // <-- Check flag assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Push next instruction PC hi to stack + emulator.tick_4(); // <-- Push next instruction PC hi to stack assert_eq!(emulator.registers.sp, orignal_sp - 1); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Push next instruction PC lo to stack + emulator.tick_4(); // <-- Push next instruction PC lo to stack assert_eq!(emulator.registers.sp, orignal_sp - 2); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01); @@ -229,11 +229,11 @@ macro_rules! conditional_call_testgen { emulator.registers.[](true); - emulator.tick(); // <-- Read first u8 + emulator.tick_4(); // <-- Read first u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Read second u8 + emulator.tick_4(); // <-- Read second u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Check flag + emulator.tick_4(); // <-- Check flag assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.sp, orignal_sp); @@ -247,19 +247,19 @@ macro_rules! conditional_call_testgen { emulator.registers.[](true); - emulator.tick(); // <-- Read first u8 + emulator.tick_4(); // <-- Read first u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Read second u8 + emulator.tick_4(); // <-- Read second u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Check flag + emulator.tick_4(); // <-- Check flag assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Push next instruction PC hi to stack + emulator.tick_4(); // <-- Push next instruction PC hi to stack assert_eq!(emulator.registers.sp, orignal_sp - 1); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Push next instruction PC lo to stack + emulator.tick_4(); // <-- Push next instruction PC lo to stack assert_eq!(emulator.registers.sp, orignal_sp - 2); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.debug_read_u8(orignal_sp - 1), 0x01); @@ -274,11 +274,11 @@ macro_rules! conditional_call_testgen { emulator.registers.[](false); - emulator.tick(); // <-- Read first u8 + emulator.tick_4(); // <-- Read first u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Read second u8 + emulator.tick_4(); // <-- Read second u8 assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); // <-- Check flag + emulator.tick_4(); // <-- Check flag assert_eq!(emulator.registers.pc, 0x103); assert_eq!(emulator.registers.sp, orignal_sp); @@ -301,15 +301,15 @@ fn test_ret() { let orignal_sp = emulator.registers.sp; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 1); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 2); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); } @@ -326,15 +326,15 @@ fn test_reti() { let orignal_sp = emulator.registers.sp; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 1); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 2); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); assert_eq!(emulator.interrupts.ime, true); } @@ -355,17 +355,17 @@ macro_rules! conditional_ret_testgen { let orignal_sp = emulator.registers.sp; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 1); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 2); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); } @@ -382,9 +382,9 @@ macro_rules! conditional_ret_testgen { let orignal_sp = emulator.registers.sp; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x101); assert_eq!(emulator.registers.sp, orignal_sp); } @@ -402,17 +402,17 @@ macro_rules! conditional_ret_testgen { let orignal_sp = emulator.registers.sp; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 1); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); assert_eq!(emulator.registers.sp, orignal_sp + 2); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0xCAFE); } @@ -429,9 +429,9 @@ macro_rules! conditional_ret_testgen { let orignal_sp = emulator.registers.sp; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, 0x101); assert_eq!(emulator.registers.sp, orignal_sp); } @@ -451,16 +451,16 @@ macro_rules! rst_testgen { let original_sp = emulator.registers.sp; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, original_sp); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, original_sp - 1); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, original_sp - 2); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.pc, $addr); assert_eq!(emulator.debug_read_u8(original_sp - 1), 0x01); diff --git a/deemgee/tests/load_store_move_opcodes.rs b/deemgee/tests/load_store_move_opcodes.rs index 03a01de..3d9095f 100644 --- a/deemgee/tests/load_store_move_opcodes.rs +++ b/deemgee/tests/load_store_move_opcodes.rs @@ -10,15 +10,15 @@ macro_rules! ld_reg_imm_u16_testgen { emulator.registers.$hireg = 0x00; emulator.registers.$loreg = 0x00; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.$hireg, 0x00); assert_eq!(emulator.registers.$loreg, 0x00); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.$hireg, 0x00); assert_eq!(emulator.registers.$loreg, 0xFE); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.$hireg, 0xCA); assert_eq!(emulator.registers.$loreg, 0xFE); assert_eq!(emulator.registers.pc, 0x103); @@ -37,13 +37,13 @@ fn test_ld_reg_sp_imm_u16() { emulator.registers.sp = 0x0000; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, 0x0000); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, 0x00FE); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, 0xCAFE); assert_eq!(emulator.registers.pc, 0x103); } @@ -55,10 +55,10 @@ fn test_ld_sp_hl() { emulator.registers.sp = 0x0000; emulator.registers.set_hl(0xCAFE); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, 0x00FE); assert_eq!(emulator.registers.pc, 0x100); - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers.sp, 0xCAFE); assert_eq!(emulator.registers.pc, 0x101); } diff --git a/deemgee/tests/misc_opcodes.rs b/deemgee/tests/misc_opcodes.rs index 23d66f6..b9477d3 100644 --- a/deemgee/tests/misc_opcodes.rs +++ b/deemgee/tests/misc_opcodes.rs @@ -10,7 +10,7 @@ fn test_nop() { state }; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers, expected_register_state); } @@ -26,7 +26,7 @@ fn test_di() { emulator.interrupts.ime = true; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers, expected_register_state); assert!(!emulator.interrupts.ime); } @@ -43,11 +43,11 @@ fn test_ei() { emulator.interrupts.ime = false; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers, expected_register_state); assert!(!emulator.interrupts.ime); - emulator.tick(); // <-- Execute the NOP that comes after as the `EI` instruction only takes - // effect a cycle after + emulator.tick_4(); // <-- Execute the NOP that comes after as the `EI` instruction only takes + // effect a cycle after assert!(emulator.interrupts.ime); } @@ -63,7 +63,7 @@ fn test_halt() { emulator.interrupts.ime = true; - emulator.tick(); + emulator.tick_4(); assert_eq!(emulator.registers, expected_register_state); assert!(emulator.halt); } diff --git a/tests.md b/tests.md new file mode 100644 index 0000000..c756af7 --- /dev/null +++ b/tests.md @@ -0,0 +1,9 @@ +# Passing Tests + +* blargg/cpu_instrs.gb +* blargg/instr_timing.gb +* blargg/mem_timing.gb +* mts/acceptance/ppu/intr_1_2_timing-GS.gb (VBlank intr -> OAM intr timing) +* mts/acceptance/ppu/intr_2_0_timing.gb (VBlank intr -> HBlank intr timing) (I think this shouldn't pass, i believe i'm triggering the HBlank IRQ one CPU cycle late) +* mts/acceptance/ppu/stat_lyc_onoff.gb (LY==LYC handling with PPU being enabled and disabled) +* mts/acceptance/ppu/stat_irq_blocking.gb (LCD status IRQ blocking) \ No newline at end of file