refactor + blarg tests
This commit is contained in:
parent
55af282e7e
commit
9c2cbbd05a
9 changed files with 175 additions and 39 deletions
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -231,7 +231,7 @@ where
|
|||
ColourFormat: From<Colour> + Copy,
|
||||
C: PocketCamera + Send + 'static,
|
||||
{
|
||||
pub(crate) window: Sender<RendererMessage<ColourFormat>>,
|
||||
pub(crate) window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
pub(crate) tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
pub(crate) layer_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
pub(crate) rom: Rom<C>,
|
||||
|
@ -241,6 +241,7 @@ where
|
|||
pub(crate) dmg_bootrom: Option<RomFile>,
|
||||
pub(crate) cgb_bootrom: Option<RomFile>,
|
||||
pub(crate) show_bootrom: bool,
|
||||
pub(crate) no_output: bool,
|
||||
pub(crate) serial_target: SerialTarget,
|
||||
pub(crate) cgb_mode: bool,
|
||||
_spooky: PhantomData<ColourFormat>,
|
||||
|
@ -252,7 +253,7 @@ where
|
|||
C: PocketCamera + Send + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
window: Sender<RendererMessage<ColourFormat>>,
|
||||
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
rom: Rom<C>,
|
||||
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<RendererMessage<ColourFormat>>,
|
||||
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
rom: Rom<C>,
|
||||
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
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ where
|
|||
ColourFormat: From<Colour> + Copy,
|
||||
C: PocketCamera + Send + 'static,
|
||||
{
|
||||
window: Sender<RendererMessage<ColourFormat>>,
|
||||
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
audio: AudioOutput,
|
||||
serial_target: SerialTarget,
|
||||
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
|
@ -130,7 +130,7 @@ where
|
|||
C: PocketCamera + Send + 'static,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
window: Sender<RendererMessage<ColourFormat>>,
|
||||
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
audio: AudioOutput,
|
||||
serial_target: SerialTarget,
|
||||
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
|
@ -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
|
||||
|
|
|
@ -55,7 +55,7 @@ where
|
|||
pub buffer: Buffer<ColourFormat, { WIDTH * HEIGHT }>,
|
||||
pub vram: Vram,
|
||||
pub oam: Oam,
|
||||
pub window: Sender<RendererMessage<ColourFormat>>,
|
||||
pub window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
is_bg_zero: [bool; WIDTH],
|
||||
is_bg_priority: [bool; WIDTH],
|
||||
lcdc: Lcdc,
|
||||
|
@ -84,7 +84,7 @@ where
|
|||
{
|
||||
pub fn new(
|
||||
cgb: bool,
|
||||
window: Sender<RendererMessage<ColourFormat>>,
|
||||
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
tile_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
layer_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||
) -> 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
|
||||
|
|
|
@ -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<JoypadState>,
|
||||
}
|
||||
|
||||
|
@ -41,7 +42,7 @@ where
|
|||
ColourFormat: From<Colour> + Copy,
|
||||
C: PocketCamera + Send + 'static,
|
||||
{
|
||||
pub(crate) fn new(memory: Memory<ColourFormat, C>, run_bootrom: bool) -> Self {
|
||||
pub(crate) fn new(memory: Memory<ColourFormat, C>, 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,
|
||||
}
|
||||
}
|
||||
|
|
64
lib/tests/blargg.rs
Normal file
64
lib/tests/blargg.rs
Normal file
|
@ -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<const N: usize>(
|
||||
correct_output: &str,
|
||||
rom: &[u8; N],
|
||||
extra_end: Option<Vec<&str>>,
|
||||
) -> 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(())
|
||||
}
|
53
lib/tests/common/mod.rs
Normal file
53
lib/tests/common/mod.rs
Normal file
|
@ -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<EmulatorMessage<[u8; 4]>>,
|
||||
pub audio_rx: AsyncHeapConsumer<[f32; 2]>,
|
||||
pub serial_rx: Receiver<u8>,
|
||||
}
|
||||
|
||||
pub fn emulator_setup(rom_file: RomFile) -> Result<TestEmulator, String> {
|
||||
let (sender, receiver) = channel::<EmulatorMessage<[u8; 4]>>();
|
||||
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::<u8>();
|
||||
let (tx_to_test, serial_rx) = channel::<u8>();
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
Loading…
Add table
Reference in a new issue