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(
|
let emulator_options = EmulatorOptions::new_with_config(
|
||||||
configs.emu_config.clone(),
|
configs.emu_config.clone(),
|
||||||
configs.config_dir.clone(),
|
configs.config_dir.clone(),
|
||||||
window,
|
Some(window),
|
||||||
prepared.rom,
|
prepared.rom,
|
||||||
output,
|
output,
|
||||||
)
|
)
|
||||||
|
|
|
@ -315,7 +315,7 @@ impl Plugin for GameboyEmu {
|
||||||
let options = EmulatorOptions::new_with_config(
|
let options = EmulatorOptions::new_with_config(
|
||||||
configs.emu_config.clone(),
|
configs.emu_config.clone(),
|
||||||
configs.config_dir.clone(),
|
configs.config_dir.clone(),
|
||||||
emu_sender,
|
Some(emu_sender),
|
||||||
rom,
|
rom,
|
||||||
output,
|
output,
|
||||||
)
|
)
|
||||||
|
|
|
@ -231,7 +231,7 @@ where
|
||||||
ColourFormat: From<Colour> + Copy,
|
ColourFormat: From<Colour> + Copy,
|
||||||
C: PocketCamera + Send + 'static,
|
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) tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
pub(crate) layer_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
pub(crate) layer_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
pub(crate) rom: Rom<C>,
|
pub(crate) rom: Rom<C>,
|
||||||
|
@ -241,6 +241,7 @@ where
|
||||||
pub(crate) dmg_bootrom: Option<RomFile>,
|
pub(crate) dmg_bootrom: Option<RomFile>,
|
||||||
pub(crate) cgb_bootrom: Option<RomFile>,
|
pub(crate) cgb_bootrom: Option<RomFile>,
|
||||||
pub(crate) show_bootrom: bool,
|
pub(crate) show_bootrom: bool,
|
||||||
|
pub(crate) no_output: bool,
|
||||||
pub(crate) serial_target: SerialTarget,
|
pub(crate) serial_target: SerialTarget,
|
||||||
pub(crate) cgb_mode: bool,
|
pub(crate) cgb_mode: bool,
|
||||||
_spooky: PhantomData<ColourFormat>,
|
_spooky: PhantomData<ColourFormat>,
|
||||||
|
@ -252,7 +253,7 @@ where
|
||||||
C: PocketCamera + Send + 'static,
|
C: PocketCamera + Send + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
window: Sender<RendererMessage<ColourFormat>>,
|
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
rom: Rom<C>,
|
rom: Rom<C>,
|
||||||
output: AudioOutput,
|
output: AudioOutput,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -266,6 +267,7 @@ where
|
||||||
dmg_bootrom: None,
|
dmg_bootrom: None,
|
||||||
cgb_bootrom: None,
|
cgb_bootrom: None,
|
||||||
show_bootrom: false,
|
show_bootrom: false,
|
||||||
|
no_output: false,
|
||||||
serial_target: SerialTarget::None,
|
serial_target: SerialTarget::None,
|
||||||
cgb_mode: true,
|
cgb_mode: true,
|
||||||
_spooky: PhantomData,
|
_spooky: PhantomData,
|
||||||
|
@ -276,7 +278,7 @@ where
|
||||||
pub fn new_with_config(
|
pub fn new_with_config(
|
||||||
config: crate::config::Config,
|
config: crate::config::Config,
|
||||||
config_dir: PathBuf,
|
config_dir: PathBuf,
|
||||||
window: Sender<RendererMessage<ColourFormat>>,
|
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
rom: Rom<C>,
|
rom: Rom<C>,
|
||||||
output: AudioOutput,
|
output: AudioOutput,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -296,6 +298,7 @@ where
|
||||||
.map(|v| config_dir.join(v))
|
.map(|v| config_dir.join(v))
|
||||||
.map(RomFile::Path),
|
.map(RomFile::Path),
|
||||||
show_bootrom: config.show_bootrom,
|
show_bootrom: config.show_bootrom,
|
||||||
|
no_output: false,
|
||||||
serial_target: SerialTarget::None,
|
serial_target: SerialTarget::None,
|
||||||
cgb_mode: config.prefer_cgb,
|
cgb_mode: config.prefer_cgb,
|
||||||
_spooky: Default::default(),
|
_spooky: Default::default(),
|
||||||
|
@ -322,6 +325,11 @@ where
|
||||||
self
|
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 {
|
pub fn with_stdout(mut self) -> Self {
|
||||||
self.serial_target = SerialTarget::Stdout(StdoutType::Ascii);
|
self.serial_target = SerialTarget::Stdout(StdoutType::Ascii);
|
||||||
self
|
self
|
||||||
|
|
|
@ -70,15 +70,14 @@ where
|
||||||
.load_data()
|
.load_data()
|
||||||
.expect("Error loading bootrom!");
|
.expect("Error loading bootrom!");
|
||||||
|
|
||||||
options
|
if let Some(window) = &options.window {
|
||||||
.window
|
window
|
||||||
.send(connect::RendererMessage::Prepare {
|
.send(connect::RendererMessage::Prepare {
|
||||||
width: WIDTH,
|
width: WIDTH,
|
||||||
height: HEIGHT,
|
height: HEIGHT,
|
||||||
})
|
})
|
||||||
.expect("message error");
|
.expect("message error");
|
||||||
options
|
window
|
||||||
.window
|
|
||||||
.send(connect::RendererMessage::SetTitle {
|
.send(connect::RendererMessage::SetTitle {
|
||||||
title: format!(
|
title: format!(
|
||||||
"{} on {} on {}",
|
"{} on {} on {}",
|
||||||
|
@ -88,6 +87,7 @@ where
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
.expect("message error");
|
.expect("message error");
|
||||||
|
}
|
||||||
|
|
||||||
Self::new(
|
Self::new(
|
||||||
paused,
|
paused,
|
||||||
|
@ -107,6 +107,7 @@ where
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
options.show_bootrom,
|
options.show_bootrom,
|
||||||
|
options.no_output,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ where
|
||||||
ColourFormat: From<Colour> + Copy,
|
ColourFormat: From<Colour> + Copy,
|
||||||
C: PocketCamera + Send + 'static,
|
C: PocketCamera + Send + 'static,
|
||||||
{
|
{
|
||||||
window: Sender<RendererMessage<ColourFormat>>,
|
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
audio: AudioOutput,
|
audio: AudioOutput,
|
||||||
serial_target: SerialTarget,
|
serial_target: SerialTarget,
|
||||||
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
|
@ -130,7 +130,7 @@ where
|
||||||
C: PocketCamera + Send + 'static,
|
C: PocketCamera + Send + 'static,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
window: Sender<RendererMessage<ColourFormat>>,
|
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
audio: AudioOutput,
|
audio: AudioOutput,
|
||||||
serial_target: SerialTarget,
|
serial_target: SerialTarget,
|
||||||
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
|
@ -253,14 +253,15 @@ where
|
||||||
self.rom.set(address, data);
|
self.rom.set(address, data);
|
||||||
if self.rom.can_rumble() {
|
if self.rom.can_rumble() {
|
||||||
// rumble
|
// rumble
|
||||||
self.gpu
|
if let Some(window) = &self.gpu.window {
|
||||||
.window
|
window
|
||||||
.send(RendererMessage::Rumble {
|
.send(RendererMessage::Rumble {
|
||||||
rumble: self.rom.is_rumbling(),
|
rumble: self.rom.is_rumbling(),
|
||||||
})
|
})
|
||||||
.expect("message error");
|
.expect("message error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Address::Vram(address) => self.gpu.set_vram(address, data),
|
Address::Vram(address) => self.gpu.set_vram(address, data),
|
||||||
Address::CartRam(address) => self.rom.set_ram(address, data),
|
Address::CartRam(address) => self.rom.set_ram(address, data),
|
||||||
Address::WorkRam(address) => self.ram.bank_0[address.get_local() as usize] = data,
|
Address::WorkRam(address) => self.ram.bank_0[address.get_local() as usize] = data,
|
||||||
|
@ -462,14 +463,19 @@ where
|
||||||
self.memory.interrupts.set_interrupt(Interrupt::Timer, true);
|
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);
|
let serial_interrupt = self.memory.serial.tick(steps, self.memory.ime);
|
||||||
self.memory
|
self.memory
|
||||||
.interrupts
|
.interrupts
|
||||||
.set_interrupt(Interrupt::Serial, serial_interrupt);
|
.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
|
self.memory
|
||||||
.interrupts
|
.interrupts
|
||||||
|
|
|
@ -55,7 +55,7 @@ where
|
||||||
pub buffer: Buffer<ColourFormat, { WIDTH * HEIGHT }>,
|
pub buffer: Buffer<ColourFormat, { WIDTH * HEIGHT }>,
|
||||||
pub vram: Vram,
|
pub vram: Vram,
|
||||||
pub oam: Oam,
|
pub oam: Oam,
|
||||||
pub window: Sender<RendererMessage<ColourFormat>>,
|
pub window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
is_bg_zero: [bool; WIDTH],
|
is_bg_zero: [bool; WIDTH],
|
||||||
is_bg_priority: [bool; WIDTH],
|
is_bg_priority: [bool; WIDTH],
|
||||||
lcdc: Lcdc,
|
lcdc: Lcdc,
|
||||||
|
@ -84,7 +84,7 @@ where
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cgb: bool,
|
cgb: bool,
|
||||||
window: Sender<RendererMessage<ColourFormat>>,
|
window: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
tile_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
|
tile_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
layer_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
|
layer_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -513,9 +513,11 @@ where
|
||||||
fn render_window(&mut self) {
|
fn render_window(&mut self) {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
buffer.extend_from_slice(self.buffer.as_ref());
|
buffer.extend_from_slice(self.buffer.as_ref());
|
||||||
self.window
|
if let Some(window) = &self.window {
|
||||||
|
window
|
||||||
.send(RendererMessage::display_message(buffer))
|
.send(RendererMessage::display_message(buffer))
|
||||||
.expect("message error");
|
.expect("message error");
|
||||||
|
}
|
||||||
|
|
||||||
self.tile_window = self
|
self.tile_window = self
|
||||||
.tile_window
|
.tile_window
|
||||||
|
|
|
@ -33,6 +33,7 @@ where
|
||||||
should_halt_bug: bool,
|
should_halt_bug: bool,
|
||||||
pub(super) cycle_count: usize,
|
pub(super) cycle_count: usize,
|
||||||
pub(crate) is_skipping: bool,
|
pub(crate) is_skipping: bool,
|
||||||
|
pub(crate) no_output: bool,
|
||||||
pub(super) next_joypad_state: Option<JoypadState>,
|
pub(super) next_joypad_state: Option<JoypadState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ where
|
||||||
ColourFormat: From<Colour> + Copy,
|
ColourFormat: From<Colour> + Copy,
|
||||||
C: PocketCamera + Send + 'static,
|
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 {
|
Self {
|
||||||
memory,
|
memory,
|
||||||
reg: Registers::init(),
|
reg: Registers::init(),
|
||||||
|
@ -51,6 +52,7 @@ where
|
||||||
should_halt_bug: false,
|
should_halt_bug: false,
|
||||||
cycle_count: 0,
|
cycle_count: 0,
|
||||||
is_skipping: !run_bootrom,
|
is_skipping: !run_bootrom,
|
||||||
|
no_output,
|
||||||
next_joypad_state: None,
|
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