From 9c2cbbd05a1de337b8314f0563e3c4cc8dae1d67 Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Thu, 21 Dec 2023 13:01:38 +1100 Subject: [PATCH] refactor + blarg tests --- frontend-common/src/lib.rs | 2 +- gb-vst/src/plugin.rs | 2 +- lib/src/connect/mod.rs | 14 ++++-- lib/src/lib.rs | 37 ++++++++-------- lib/src/processor/memory.rs | 26 ++++++----- lib/src/processor/memory/mmio/gpu.rs | 12 +++--- lib/src/processor/mod.rs | 4 +- lib/tests/blargg.rs | 64 ++++++++++++++++++++++++++++ lib/tests/common/mod.rs | 53 +++++++++++++++++++++++ 9 files changed, 175 insertions(+), 39 deletions(-) create mode 100644 lib/tests/blargg.rs create mode 100644 lib/tests/common/mod.rs diff --git a/frontend-common/src/lib.rs b/frontend-common/src/lib.rs index ea35b67..3e1b256 100644 --- a/frontend-common/src/lib.rs +++ b/frontend-common/src/lib.rs @@ -200,7 +200,7 @@ where let emulator_options = EmulatorOptions::new_with_config( configs.emu_config.clone(), configs.config_dir.clone(), - window, + Some(window), prepared.rom, output, ) diff --git a/gb-vst/src/plugin.rs b/gb-vst/src/plugin.rs index dceed83..3aab1df 100644 --- a/gb-vst/src/plugin.rs +++ b/gb-vst/src/plugin.rs @@ -315,7 +315,7 @@ impl Plugin for GameboyEmu { let options = EmulatorOptions::new_with_config( configs.emu_config.clone(), configs.config_dir.clone(), - emu_sender, + Some(emu_sender), rom, output, ) diff --git a/lib/src/connect/mod.rs b/lib/src/connect/mod.rs index 9cfdf2e..24ba652 100644 --- a/lib/src/connect/mod.rs +++ b/lib/src/connect/mod.rs @@ -231,7 +231,7 @@ where ColourFormat: From + Copy, C: PocketCamera + Send + 'static, { - pub(crate) window: Sender>, + pub(crate) window: Option>>, pub(crate) tile_window: Option>>, pub(crate) layer_window: Option>>, pub(crate) rom: Rom, @@ -241,6 +241,7 @@ where pub(crate) dmg_bootrom: Option, pub(crate) cgb_bootrom: Option, pub(crate) show_bootrom: bool, + pub(crate) no_output: bool, pub(crate) serial_target: SerialTarget, pub(crate) cgb_mode: bool, _spooky: PhantomData, @@ -252,7 +253,7 @@ where C: PocketCamera + Send + 'static, { pub fn new( - window: Sender>, + window: Option>>, rom: Rom, output: AudioOutput, ) -> Self { @@ -266,6 +267,7 @@ where dmg_bootrom: None, cgb_bootrom: None, show_bootrom: false, + no_output: false, serial_target: SerialTarget::None, cgb_mode: true, _spooky: PhantomData, @@ -276,7 +278,7 @@ where pub fn new_with_config( config: crate::config::Config, config_dir: PathBuf, - window: Sender>, + window: Option>>, rom: Rom, output: AudioOutput, ) -> Self { @@ -296,6 +298,7 @@ where .map(|v| config_dir.join(v)) .map(RomFile::Path), show_bootrom: config.show_bootrom, + no_output: false, serial_target: SerialTarget::None, cgb_mode: config.prefer_cgb, _spooky: Default::default(), @@ -322,6 +325,11 @@ where self } + pub fn with_no_output(mut self, no_output: bool) -> Self { + self.no_output = no_output; + self + } + pub fn with_stdout(mut self) -> Self { self.serial_target = SerialTarget::Stdout(StdoutType::Ascii); self diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6b76291..f4ef278 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -70,24 +70,24 @@ where .load_data() .expect("Error loading bootrom!"); - options - .window - .send(connect::RendererMessage::Prepare { - width: WIDTH, - height: HEIGHT, - }) - .expect("message error"); - options - .window - .send(connect::RendererMessage::SetTitle { - title: format!( - "{} on {} on {}", - rom.get_title(), - rom.mbc_type(), - if is_cgb_mode { "CGB" } else { "DMG" } - ), - }) - .expect("message error"); + if let Some(window) = &options.window { + window + .send(connect::RendererMessage::Prepare { + width: WIDTH, + height: HEIGHT, + }) + .expect("message error"); + window + .send(connect::RendererMessage::SetTitle { + title: format!( + "{} on {} on {}", + rom.get_title(), + rom.mbc_type(), + if is_cgb_mode { "CGB" } else { "DMG" } + ), + }) + .expect("message error"); + } Self::new( paused, @@ -107,6 +107,7 @@ where ), ), options.show_bootrom, + options.no_output, ), ) } diff --git a/lib/src/processor/memory.rs b/lib/src/processor/memory.rs index 790bfd7..60a64cd 100644 --- a/lib/src/processor/memory.rs +++ b/lib/src/processor/memory.rs @@ -115,7 +115,7 @@ where ColourFormat: From + Copy, C: PocketCamera + Send + 'static, { - window: Sender>, + window: Option>>, audio: AudioOutput, serial_target: SerialTarget, tile_window: Option>>, @@ -130,7 +130,7 @@ where C: PocketCamera + Send + 'static, { pub(crate) fn new( - window: Sender>, + window: Option>>, audio: AudioOutput, serial_target: SerialTarget, tile_window: Option>>, @@ -253,12 +253,13 @@ where self.rom.set(address, data); if self.rom.can_rumble() { // rumble - self.gpu - .window - .send(RendererMessage::Rumble { - rumble: self.rom.is_rumbling(), - }) - .expect("message error"); + if let Some(window) = &self.gpu.window { + window + .send(RendererMessage::Rumble { + rumble: self.rom.is_rumbling(), + }) + .expect("message error"); + } } } Address::Vram(address) => self.gpu.set_vram(address, data), @@ -462,14 +463,19 @@ where self.memory.interrupts.set_interrupt(Interrupt::Timer, true); } - self.memory.apu.tick(logical_steps, !self.is_skipping); + self.memory + .apu + .tick(logical_steps, !(self.is_skipping || self.no_output)); let serial_interrupt = self.memory.serial.tick(steps, self.memory.ime); self.memory .interrupts .set_interrupt(Interrupt::Serial, serial_interrupt); - let gpu_interrupts = self.memory.gpu.tick(logical_steps, !self.is_skipping); + let gpu_interrupts = self + .memory + .gpu + .tick(logical_steps, !(self.is_skipping || self.no_output)); self.memory .interrupts diff --git a/lib/src/processor/memory/mmio/gpu.rs b/lib/src/processor/memory/mmio/gpu.rs index 2a26f06..cf90e40 100644 --- a/lib/src/processor/memory/mmio/gpu.rs +++ b/lib/src/processor/memory/mmio/gpu.rs @@ -55,7 +55,7 @@ where pub buffer: Buffer, pub vram: Vram, pub oam: Oam, - pub window: Sender>, + pub window: Option>>, is_bg_zero: [bool; WIDTH], is_bg_priority: [bool; WIDTH], lcdc: Lcdc, @@ -84,7 +84,7 @@ where { pub fn new( cgb: bool, - window: Sender>, + window: Option>>, tile_window_renderer: Option>>, layer_window_renderer: Option>>, ) -> Self { @@ -513,9 +513,11 @@ where fn render_window(&mut self) { let mut buffer = Vec::new(); buffer.extend_from_slice(self.buffer.as_ref()); - self.window - .send(RendererMessage::display_message(buffer)) - .expect("message error"); + if let Some(window) = &self.window { + window + .send(RendererMessage::display_message(buffer)) + .expect("message error"); + } self.tile_window = self .tile_window diff --git a/lib/src/processor/mod.rs b/lib/src/processor/mod.rs index d51b369..da2b3bf 100644 --- a/lib/src/processor/mod.rs +++ b/lib/src/processor/mod.rs @@ -33,6 +33,7 @@ where should_halt_bug: bool, pub(super) cycle_count: usize, pub(crate) is_skipping: bool, + pub(crate) no_output: bool, pub(super) next_joypad_state: Option, } @@ -41,7 +42,7 @@ where ColourFormat: From + Copy, C: PocketCamera + Send + 'static, { - pub(crate) fn new(memory: Memory, run_bootrom: bool) -> Self { + pub(crate) fn new(memory: Memory, run_bootrom: bool, no_output: bool) -> Self { Self { memory, reg: Registers::init(), @@ -51,6 +52,7 @@ where should_halt_bug: false, cycle_count: 0, is_skipping: !run_bootrom, + no_output, next_joypad_state: None, } } diff --git a/lib/tests/blargg.rs b/lib/tests/blargg.rs new file mode 100644 index 0000000..10bf0b4 --- /dev/null +++ b/lib/tests/blargg.rs @@ -0,0 +1,64 @@ +use std::time::{Duration, Instant}; + +use common::emulator_setup; +use gb_emu_lib::connect::{EmulatorCoreTrait, RomFile}; + +mod common; + +#[test] +fn cpu_instrs() -> Result<(), String> { + run_blargg_test("cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests", include_bytes!( + "../../test-roms/blargg/cpu_instrs/cpu_instrs.gb" + ),None) +} + +#[test] +fn instr_timing() -> Result<(), String> { + run_blargg_test( + "instr_timing\n\n\nPassed", + include_bytes!("../../test-roms/blargg/instr_timing/instr_timing.gb"), + Some(vec!["Failed"]), + ) +} + +#[test] +fn mem_timing() -> Result<(), String> { + run_blargg_test( + "mem_timing\n\n\nPassed", + include_bytes!("../../test-roms/blargg/mem_timing/mem_timing.gb"), + None, + ) +} + +fn run_blargg_test( + correct_output: &str, + rom: &[u8; N], + extra_end: Option>, +) -> Result<(), String> { + let mut emu = emulator_setup(RomFile::Raw(rom.to_vec()))?; + + let mut end_strings = extra_end.unwrap_or_default(); + end_strings.append(&mut vec![".", "tests"]); + + let mut chars = String::new(); + + let began = Instant::now(); + let timeout = Duration::from_secs(60); + + 'outer: loop { + emu.core.run(100); + while let Ok(v) = emu.serial_rx.try_recv() { + chars.push(v as char); + if end_strings.iter().any(|end| chars.ends_with(end)) { + break 'outer; + } + } + if began.elapsed() > timeout { + return Err(format!("Test timed out: output was {chars}")); + } + } + + assert_eq!(correct_output, chars); + + Ok(()) +} diff --git a/lib/tests/common/mod.rs b/lib/tests/common/mod.rs new file mode 100644 index 0000000..2cdbfb0 --- /dev/null +++ b/lib/tests/common/mod.rs @@ -0,0 +1,53 @@ +use std::sync::mpsc::{channel, Receiver, Sender}; + +use async_ringbuf::AsyncHeapConsumer; +use gb_emu_lib::{ + connect::{AudioOutput, EmulatorMessage, EmulatorOptions, NoCamera, RomFile}, + EmulatorCore, +}; + +pub struct TestEmulator { + pub core: EmulatorCore<[u8; 4], NoCamera>, + pub sender: Sender>, + pub audio_rx: AsyncHeapConsumer<[f32; 2]>, + pub serial_rx: Receiver, +} + +pub fn emulator_setup(rom_file: RomFile) -> Result { + let (sender, receiver) = channel::>(); + let (rom, camera) = rom_file + .load(gb_emu_lib::connect::SramType::None, NoCamera::default()) + .map_err(|_e| String::from("Error reading ROM: {_e:?}"))?; + let (audio_output, audio_rx) = AudioOutput::new( + 48000., + 1, + gb_emu_lib::connect::DownsampleType::ZeroOrderHold, + ); + + // let (tx_to_gameboy, rx_from_test) = channel::(); + let (tx_to_test, serial_rx) = channel::(); + + let options = EmulatorOptions::new(None, rom, audio_output) + .with_no_output(true) + .with_serial_target(gb_emu_lib::connect::SerialTarget::Custom { + rx: None, + tx: Some(tx_to_test), + }) + .with_cgb_bootrom(Some(RomFile::Raw( + include_bytes!("../../../sameboy-bootroms/cgb_boot.bin").to_vec(), + ))) + .with_dmg_bootrom(Some(RomFile::Raw( + include_bytes!("../../../sameboy-bootroms/dmg_boot.bin").to_vec(), + ))); + + let core = EmulatorCore::init(true, receiver, options, camera); + sender + .send(EmulatorMessage::Start) + .map_err(|_e| String::from("Error sending message: {_e:?}"))?; + Ok(TestEmulator { + core, + sender, + audio_rx, + serial_rx, + }) +}