Move the emulator into crates/gba-core, the original demo programs into crates/gba-demos, and the minifb frontend into crates/desktop. The binary is now gameboy-rust. Imports change from gba_rust to gba_core/gba_demos; no behavior changes. gba-core has no dependencies. Its integration tests use gba-demos as a development dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
169 lines
6.3 KiB
Rust
169 lines
6.3 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn capture_is_optional_and_snapshot_api_remains_independent() {
|
|
let mut m = Memory::new(vec![]).unwrap();
|
|
m.write16(PAL, 31).unwrap();
|
|
m.advance_cycles(CYCLES_PER_FRAME * 2);
|
|
let mut f = Framebuffer::default();
|
|
f.clear(0x3e0);
|
|
assert!(!m.present_frame(&mut f).unwrap());
|
|
assert!(f.pixels().iter().all(|&p| p == rgb(0x3e0)));
|
|
assert_eq!(m.captured_vblank(), None);
|
|
m.render_frame(&mut f).unwrap();
|
|
assert!(f.pixels().iter().all(|&p| p == rgb(31)));
|
|
let cycles = m.cycles();
|
|
m.set_scanline_rendering(true);
|
|
assert_eq!(m.cycles(), cycles);
|
|
m.advance_cycles(VBLANK_START);
|
|
assert_eq!(m.captured_vblank(), Some(3));
|
|
assert_eq!(present(&m).pixels(), f.pixels());
|
|
}
|
|
|
|
#[test]
|
|
fn hblank_captures_old_row_and_later_palette_writes_do_not_recolor_it() {
|
|
let mut m = memory();
|
|
m.write16(PAL, 31).unwrap();
|
|
m.advance_cycles(HBLANK_START);
|
|
m.write16(PAL, 0x3e0).unwrap();
|
|
m.advance_cycles(VBLANK_START - HBLANK_START);
|
|
let f = present(&m);
|
|
for y in 0..HEIGHT {
|
|
assert!(f.pixels()[y * WIDTH..(y + 1) * WIDTH]
|
|
.iter()
|
|
.all(|&p| p == rgb(if y == 0 { 31 } else { 0x3e0 })));
|
|
}
|
|
m.write16(PAL, 0x7c00).unwrap();
|
|
assert_eq!(present(&m).pixels(), f.pixels());
|
|
let cycles = m.cycles();
|
|
let mut snapshot = Framebuffer::default();
|
|
m.render_frame(&mut snapshot).unwrap();
|
|
assert!(snapshot.pixels().iter().all(|&p| p == rgb(0x7c00)));
|
|
assert_eq!(m.cycles(), cycles);
|
|
}
|
|
|
|
#[test]
|
|
fn last_row_capture_does_not_publish_before_vblank_entry() {
|
|
let mut m = memory();
|
|
m.write16(PAL, 31).unwrap();
|
|
let mut f = Framebuffer::default();
|
|
f.clear(0x3e0);
|
|
m.advance_cycles(159 * CYCLES_PER_LINE + HBLANK_START);
|
|
assert!(!m.present_frame(&mut f).unwrap());
|
|
m.advance_cycles(CYCLES_PER_LINE - HBLANK_START - 1);
|
|
assert!(!m.present_frame(&mut f).unwrap());
|
|
assert!(f.pixels().iter().all(|&p| p == rgb(0x3e0)));
|
|
m.advance_cycles(1);
|
|
assert!(m.present_frame(&mut f).unwrap());
|
|
assert_eq!(m.captured_vblank(), Some(1));
|
|
assert!(f.pixels().iter().all(|&p| p == rgb(31)));
|
|
}
|
|
|
|
#[test]
|
|
fn enabling_mid_frame_discards_partial_frame_and_repeated_enable_preserves_progress() {
|
|
for start in [HBLANK_START, 40 * CYCLES_PER_LINE, VBLANK_START - 1] {
|
|
let mut m = Memory::new(vec![]).unwrap();
|
|
m.advance_cycles(start);
|
|
m.set_scanline_rendering(true);
|
|
m.write16(PAL, 31).unwrap();
|
|
m.advance_cycles(VBLANK_START - start);
|
|
assert_eq!(m.captured_vblank(), None);
|
|
m.advance_cycles(CYCLES_PER_FRAME - VBLANK_START + HBLANK_START);
|
|
m.set_scanline_rendering(true); // Must not reset row0.
|
|
m.advance_cycles(VBLANK_START - HBLANK_START);
|
|
assert_eq!(m.captured_vblank(), Some(2));
|
|
assert!(present(&m).pixels().iter().all(|&p| p == rgb(31)));
|
|
m.set_scanline_rendering(false);
|
|
assert_eq!(m.captured_vblank(), None);
|
|
assert!(!m.present_frame(&mut Framebuffer::default()).unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn enable_before_first_hblank_and_during_vblank_can_capture_next_complete_image() {
|
|
for start in [0, HBLANK_START - 1, VBLANK_START, CYCLES_PER_FRAME - 1] {
|
|
let mut m = Memory::new(vec![]).unwrap();
|
|
m.advance_cycles(start);
|
|
m.set_scanline_rendering(true);
|
|
let target = if start < HBLANK_START {
|
|
VBLANK_START
|
|
} else {
|
|
CYCLES_PER_FRAME + VBLANK_START
|
|
};
|
|
m.advance_cycles(target - start);
|
|
assert_eq!(
|
|
m.captured_vblank(),
|
|
Some(if start < HBLANK_START { 1 } else { 2 })
|
|
);
|
|
assert!(m.present_frame(&mut Framebuffer::default()).unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn large_batches_publish_latest_frame_and_zero_cycles_have_no_effect() {
|
|
let mut m = memory();
|
|
m.write16(PAL, 0x4210).unwrap();
|
|
m.advance_cycles(CYCLES_PER_FRAME * 3 + VBLANK_START);
|
|
assert_eq!(m.captured_vblank(), Some(4));
|
|
let before = m.display_position();
|
|
let f = present(&m);
|
|
m.advance_cycles(0);
|
|
m.write16(PAL, 0).unwrap();
|
|
assert_eq!(m.display_position(), before);
|
|
assert_eq!(present(&m).pixels(), f.pixels());
|
|
assert!(f.pixels().iter().all(|&p| p == rgb(0x4210)));
|
|
}
|
|
|
|
#[test]
|
|
fn first_render_error_is_reported_at_publication_without_partial_output_then_recovers() {
|
|
let mut m = memory();
|
|
m.write16(PAL, 31).unwrap();
|
|
m.advance_cycles(VBLANK_START);
|
|
let mut f = present(&m);
|
|
m.advance_cycles(CYCLES_PER_FRAME - VBLANK_START);
|
|
m.write16(DISPCNT, 6).unwrap();
|
|
m.advance_cycles(HBLANK_START);
|
|
m.write16(DISPCNT, 7).unwrap();
|
|
m.advance_cycles(CYCLES_PER_LINE);
|
|
m.write16(DISPCNT, 0).unwrap();
|
|
assert!(m.present_frame(&mut f).unwrap()); // Previous frame remains available while drawing.
|
|
m.advance_cycles(VBLANK_START - HBLANK_START - CYCLES_PER_LINE);
|
|
assert_eq!(m.captured_vblank(), Some(2));
|
|
assert_eq!(m.present_frame(&mut f), Err(VideoError::UnsupportedMode(6)));
|
|
assert!(f.pixels().iter().all(|&p| p == rgb(31)));
|
|
m.write16(PAL, 0x3e0).unwrap();
|
|
m.advance_cycles(CYCLES_PER_FRAME);
|
|
assert_eq!(m.captured_vblank(), Some(3));
|
|
assert!(m.present_frame(&mut f).unwrap());
|
|
assert!(f.pixels().iter().all(|&p| p == rgb(0x3e0)));
|
|
}
|
|
|
|
#[test]
|
|
fn split_clock_advance_matches_single_cycle_reference_and_disabled_capture_devices() {
|
|
let mut bulk = memory();
|
|
let mut single = memory();
|
|
let mut disabled = Memory::new(vec![]).unwrap();
|
|
for m in [&mut bulk, &mut single, &mut disabled] {
|
|
m.write16(DISPSTAT, 0x5038).unwrap();
|
|
m.write32(TIMER_BASE, 0x00c0_fff1).unwrap();
|
|
m.write16(IE, 15).unwrap();
|
|
m.write8(HALTCNT, 0).unwrap();
|
|
m.write16(PAL, 0x4210).unwrap();
|
|
}
|
|
let cycles = CYCLES_PER_FRAME + VBLANK_START + 73;
|
|
bulk.advance_cycles(cycles);
|
|
disabled.advance_cycles(cycles);
|
|
for _ in 0..cycles {
|
|
single.advance_cycles(1);
|
|
}
|
|
for m in [&single, &disabled] {
|
|
assert_eq!(bulk.display_position(), m.display_position());
|
|
assert_eq!(bulk.cycles(), m.cycles());
|
|
assert_eq!(bulk.halted(), m.halted());
|
|
for register in [DISPSTAT, IF, TIMER_BASE] {
|
|
assert_eq!(bulk.read16(register), m.read16(register));
|
|
}
|
|
}
|
|
assert_eq!(present(&bulk).pixels(), present(&single).pixels());
|
|
assert_eq!(bulk.captured_vblank(), single.captured_vblank());
|
|
}
|