feat(demo): run CPU-driven bitmap tile and sprite scenes
Add original ARM graphics programs, input controls, DMA initialization, and a bounded frame runner.
This commit is contained in:
parent
0280563430
commit
169fab9bf6
3 changed files with 1143 additions and 0 deletions
297
tests/graphics.rs
Normal file
297
tests/graphics.rs
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
use gba_rust::{
|
||||
graphics_demo::{GraphicsDemo, BACKGROUND, DEMO_STATE, SQUARE_SIZE},
|
||||
input::{Button, Buttons},
|
||||
io::{BG2PA, BG2PD, DISPCNT, GREENSWAP, KEYINPUT},
|
||||
memory::{Memory, MemoryError, PALETTE_START, VRAM_START},
|
||||
timing::{bus_cycles, AccessKind, AccessWidth},
|
||||
video::{rgb555_to_rgb888, Framebuffer, VideoError, HEIGHT, WIDTH},
|
||||
};
|
||||
|
||||
fn mode3() -> Memory {
|
||||
let mut memory = Memory::new(vec![]).unwrap();
|
||||
memory.write16(DISPCNT, 0x403).unwrap();
|
||||
memory.write16(BG2PA, 256).unwrap();
|
||||
memory.write16(BG2PD, 256).unwrap();
|
||||
memory
|
||||
}
|
||||
|
||||
fn assert_demo_frame(demo: &GraphicsDemo, frame: &Framebuffer, x: usize, y: usize, color: u16) {
|
||||
let memory = demo.machine().memory();
|
||||
assert_eq!(memory.read32(DEMO_STATE + 4).unwrap(), x as u32);
|
||||
assert_eq!(memory.read32(DEMO_STATE + 8).unwrap(), y as u32);
|
||||
assert_eq!(memory.read32(DEMO_STATE + 12).unwrap(), u32::from(color));
|
||||
assert_eq!(memory.read16(DISPCNT).unwrap(), 0x403);
|
||||
for py in 0..HEIGHT {
|
||||
for px in 0..WIDTH {
|
||||
let expected =
|
||||
if (x..x + SQUARE_SIZE).contains(&px) && (y..y + SQUARE_SIZE).contains(&py) {
|
||||
color
|
||||
} else {
|
||||
BACKGROUND
|
||||
};
|
||||
assert_eq!(
|
||||
frame.pixels()[py * WIDTH + px],
|
||||
rgb555_to_rgb888(expected),
|
||||
"pixel {px},{py}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_program_draws_mode3_and_moves_from_keyinput_without_host_vram_writes() {
|
||||
let mut demo = GraphicsDemo::new().unwrap();
|
||||
let mut frame = Framebuffer::default();
|
||||
assert_eq!(demo.machine().memory().read16(DISPCNT).unwrap(), 0);
|
||||
assert_eq!(demo.machine().memory().read16(VRAM_START).unwrap(), 0);
|
||||
let startup_steps = demo.frame(Buttons::default(), &mut frame).unwrap();
|
||||
assert!(startup_steps > WIDTH * HEIGHT);
|
||||
assert_demo_frame(&demo, &frame, 112, 72, 0x7fff);
|
||||
let previous = demo.machine().memory().display_position();
|
||||
let previous_cycles = demo.machine().cycles();
|
||||
assert_eq!(previous.scanline, 160);
|
||||
let steps = demo
|
||||
.frame(
|
||||
Buttons::default()
|
||||
.with(Button::Right, true)
|
||||
.with(Button::Up, true),
|
||||
&mut frame,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(steps < startup_steps);
|
||||
let position = demo.machine().memory().display_position();
|
||||
assert_eq!(position.scanline, 160);
|
||||
assert_eq!(position.vblanks, previous.vblanks + 1);
|
||||
assert_eq!(position.frames, previous.frames + 1);
|
||||
assert_eq!(
|
||||
demo.machine().cycles() - previous_cycles,
|
||||
u64::from(gba_rust::display::CYCLES_PER_FRAME) + u64::from(position.line_cycle)
|
||||
- u64::from(previous.line_cycle)
|
||||
);
|
||||
assert_demo_frame(&demo, &frame, 114, 70, 0x7fff);
|
||||
assert_eq!(demo.machine().memory().read32(DEMO_STATE).unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_program_clamps_edges_cancels_opposites_changes_color_and_resets() {
|
||||
let mut demo = GraphicsDemo::new().unwrap();
|
||||
let mut frame = Framebuffer::default();
|
||||
let upper_left = Buttons::default()
|
||||
.with(Button::Left, true)
|
||||
.with(Button::Up, true);
|
||||
for _ in 0..65 {
|
||||
demo.frame(upper_left, &mut frame).unwrap();
|
||||
}
|
||||
assert_demo_frame(&demo, &frame, 0, 0, 0x7fff);
|
||||
let all_directions = Buttons::from_bits(0xf0);
|
||||
demo.frame(all_directions, &mut frame).unwrap();
|
||||
assert_demo_frame(&demo, &frame, 0, 0, 0x7fff);
|
||||
let lower_right = Buttons::default()
|
||||
.with(Button::Right, true)
|
||||
.with(Button::Down, true);
|
||||
for _ in 0..120 {
|
||||
demo.frame(lower_right, &mut frame).unwrap();
|
||||
}
|
||||
assert_demo_frame(&demo, &frame, 224, 144, 0x7fff);
|
||||
demo.frame(all_directions, &mut frame).unwrap();
|
||||
assert_demo_frame(&demo, &frame, 224, 144, 0x7fff);
|
||||
for (buttons, color) in [
|
||||
(Buttons::from_bits(2), 0x3e0),
|
||||
(Buttons::from_bits(3), 0x1f),
|
||||
(Buttons::default(), 0x7fff),
|
||||
] {
|
||||
demo.frame(buttons, &mut frame).unwrap();
|
||||
assert_demo_frame(&demo, &frame, 224, 144, color);
|
||||
}
|
||||
demo.frame(Buttons::default().with(Button::Start, true), &mut frame)
|
||||
.unwrap();
|
||||
assert_demo_frame(&demo, &frame, 112, 72, 0x7fff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode3_renders_row_major_colors_and_ignores_page_and_pixel_top_bit() {
|
||||
let mut memory = mode3();
|
||||
let mut frame = Framebuffer::default();
|
||||
for (index, color) in [
|
||||
(0, 0x801f),
|
||||
(1, 0x03e0),
|
||||
(WIDTH, 0x7c00),
|
||||
(WIDTH * HEIGHT - 1, 0x7fff),
|
||||
] {
|
||||
memory
|
||||
.write16(VRAM_START + index as u32 * 2, color)
|
||||
.unwrap();
|
||||
}
|
||||
memory.write16(DISPCNT, 0x413).unwrap(); // Page bit has no effect in Mode 3.
|
||||
memory.render_frame(&mut frame).unwrap();
|
||||
assert_eq!(frame.pixels()[0], 0xff0000);
|
||||
assert_eq!(frame.pixels()[1], 0x00ff00);
|
||||
assert_eq!(frame.pixels()[WIDTH], 0x0000ff);
|
||||
assert_eq!(frame.pixels()[WIDTH * HEIGHT - 1], 0xffffff);
|
||||
assert_eq!(frame.pixels()[2], 0);
|
||||
assert_eq!(memory.cycles(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forced_blank_is_white_and_disabled_bg2_uses_palette_backdrop() {
|
||||
let mut memory = mode3();
|
||||
let mut frame = Framebuffer::default();
|
||||
memory.write16(PALETTE_START, 0x3e0).unwrap();
|
||||
memory.write16(DISPCNT, 3).unwrap();
|
||||
memory.render_frame(&mut frame).unwrap();
|
||||
assert!(frame.pixels().iter().all(|&pixel| pixel == 0x00ff00));
|
||||
memory.write16(DISPCNT, 0x403).unwrap();
|
||||
memory.render_frame(&mut frame).unwrap();
|
||||
assert!(frame.pixels().iter().all(|&pixel| pixel == 0)); // Mode 3 black is opaque.
|
||||
memory.write16(DISPCNT, 0xffff).unwrap();
|
||||
memory.render_frame(&mut frame).unwrap();
|
||||
assert!(frame.pixels().iter().all(|&pixel| pixel == 0xffffff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_modes_and_layers_report_errors_without_changing_output() {
|
||||
let mut memory = mode3();
|
||||
let mut frame = Framebuffer::default();
|
||||
frame.clear(0x001f);
|
||||
for mode in [6, 7] {
|
||||
memory.write16(DISPCNT, 0x400 | mode).unwrap();
|
||||
assert_eq!(
|
||||
memory.render_frame(&mut frame),
|
||||
Err(VideoError::UnsupportedMode(mode as u8))
|
||||
);
|
||||
}
|
||||
for layer in [0x100, 0x200, 0x800] {
|
||||
memory.write16(DISPCNT, 0x403 | layer).unwrap();
|
||||
assert_eq!(
|
||||
memory.render_frame(&mut frame),
|
||||
Err(VideoError::UnsupportedLayers(0x403 | layer))
|
||||
);
|
||||
}
|
||||
assert!(frame.pixels().iter().all(|&pixel| pixel == 0xff0000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_control_masks_cgb_bit_and_green_swap_exchanges_only_green() {
|
||||
let mut memory = mode3();
|
||||
memory.write32(DISPCNT, u32::MAX).unwrap();
|
||||
assert_eq!(memory.read32(DISPCNT).unwrap(), 0x0001_fff7);
|
||||
memory.write8(DISPCNT, 3).unwrap();
|
||||
memory.write8(DISPCNT + 1, 4).unwrap();
|
||||
memory.write8(GREENSWAP + 1, 0xff).unwrap();
|
||||
assert_eq!(memory.read32(DISPCNT).unwrap(), 0x0001_0403);
|
||||
memory.write16(VRAM_START, 0x001f).unwrap();
|
||||
memory.write16(VRAM_START + 2, 0x7fe0).unwrap(); // Green + blue
|
||||
let mut frame = Framebuffer::default();
|
||||
memory.render_frame(&mut frame).unwrap();
|
||||
assert_eq!(frame.pixels()[0], 0xffff00);
|
||||
assert_eq!(frame.pixels()[1], 0x0000ff);
|
||||
memory.write8(GREENSWAP, 0).unwrap();
|
||||
memory.render_frame(&mut frame).unwrap();
|
||||
assert_eq!(frame.pixels()[0], 0xff0000);
|
||||
assert_eq!(frame.pixels()[1], 0x00ffff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn palette_and_vram_mirrors_preserve_word_and_halfword_layouts() {
|
||||
let mut memory = mode3();
|
||||
memory.write32(PALETTE_START + 0x400, 0x1234_5678).unwrap();
|
||||
assert_eq!(memory.read32(PALETTE_START).unwrap(), 0x1234_5678);
|
||||
memory.write16(0x05ff_fffe, 0xabcd).unwrap();
|
||||
assert_eq!(memory.read16(PALETTE_START + 0x3fe).unwrap(), 0xabcd);
|
||||
for (mirror, physical) in [
|
||||
(0x0602_0000, VRAM_START),
|
||||
(0x0601_8000, VRAM_START + 0x10000),
|
||||
(0x06ff_fffc, VRAM_START + 0x17ffc),
|
||||
] {
|
||||
memory.write32(mirror, 0x1234_5678).unwrap();
|
||||
assert_eq!(memory.read32(physical).unwrap(), 0x1234_5678);
|
||||
assert_eq!(memory.read8(mirror).unwrap(), 0x78);
|
||||
assert_eq!(memory.read16(mirror + 2).unwrap(), 0x1234);
|
||||
}
|
||||
memory.write32(VRAM_START + 0x17ffc, 0xabcd_1234).unwrap();
|
||||
assert_eq!(memory.read32(VRAM_START + 0x1fffc).unwrap(), 0xabcd_1234);
|
||||
assert_eq!(memory.read32(0x0700_0000).unwrap(), 0); // OAM starts cleared.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_byte_writes_duplicate_bg_and_palette_but_ignore_obj_area() {
|
||||
let mut memory = mode3();
|
||||
for address in [
|
||||
PALETTE_START,
|
||||
PALETTE_START + 0x3ff,
|
||||
VRAM_START,
|
||||
VRAM_START + 1,
|
||||
VRAM_START + 0x13fff,
|
||||
] {
|
||||
memory.write8(address, 0x5a).unwrap();
|
||||
assert_eq!(memory.read16(address & !1).unwrap(), 0x5a5a);
|
||||
}
|
||||
for address in [
|
||||
VRAM_START + 0x14000,
|
||||
VRAM_START + 0x17fff,
|
||||
VRAM_START + 0x1ffff,
|
||||
] {
|
||||
memory.write16(address & !1, 0x1234).unwrap();
|
||||
memory.write8(address, 0xff).unwrap();
|
||||
assert_eq!(memory.read16(address & !1).unwrap(), 0x1234);
|
||||
}
|
||||
memory.write16(DISPCNT, 0).unwrap(); // Tile-mode OBJ area starts at 64 KiB.
|
||||
memory.write16(VRAM_START + 0x10000, 0xabcd).unwrap();
|
||||
memory.write8(VRAM_START + 0x18000, 0).unwrap();
|
||||
assert_eq!(memory.read16(VRAM_START + 0x10000).unwrap(), 0xabcd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn misaligned_video_writes_leave_all_bytes_unchanged() {
|
||||
let mut memory = mode3();
|
||||
for address in [PALETTE_START, VRAM_START] {
|
||||
memory.write32(address, 0x1234_5678).unwrap();
|
||||
assert_eq!(
|
||||
memory.write16(address + 1, 0),
|
||||
Err(MemoryError::Unaligned(address + 1))
|
||||
);
|
||||
assert_eq!(
|
||||
memory.write32(address + 2, 0),
|
||||
Err(MemoryError::Unaligned(address + 2))
|
||||
);
|
||||
assert_eq!(memory.read32(address).unwrap(), 0x1234_5678);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyinput_exposes_all_ten_buttons_active_low_and_ignores_writes() {
|
||||
let mut memory = mode3();
|
||||
assert_eq!(memory.read16(KEYINPUT).unwrap(), 0x3ff);
|
||||
for bits in 0..=0x3ff {
|
||||
memory.set_buttons(Buttons::from_bits(bits));
|
||||
assert_eq!(memory.read16(KEYINPUT).unwrap(), !bits & 0x3ff);
|
||||
memory.write16(KEYINPUT, 0).unwrap();
|
||||
assert_eq!(memory.read16(KEYINPUT).unwrap(), !bits & 0x3ff);
|
||||
}
|
||||
let buttons = Buttons::from_bits(0xffff)
|
||||
.with(Button::A, false)
|
||||
.with(Button::L, false);
|
||||
assert_eq!(buttons.bits(), 0x1fe);
|
||||
memory.set_buttons(buttons);
|
||||
memory.write8(KEYINPUT + 1, 0).unwrap();
|
||||
assert_eq!(memory.read8(KEYINPUT).unwrap(), 1);
|
||||
assert_eq!(memory.read8(KEYINPUT + 1).unwrap(), 2);
|
||||
memory.set_buttons(Buttons::default());
|
||||
assert_eq!(memory.read16(KEYINPUT).unwrap(), 0x3ff);
|
||||
assert_eq!(memory.cycles(), 0);
|
||||
assert_eq!(
|
||||
memory.read16(KEYINPUT + 2),
|
||||
Err(MemoryError::Unmapped(KEYINPUT + 2))
|
||||
); // KEYCNT deferred.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_bus_widths_have_nominal_16_bit_bus_costs() {
|
||||
for address in [PALETTE_START, VRAM_START, 0x06ff_fffc] {
|
||||
for kind in [AccessKind::Sequential, AccessKind::NonSequential] {
|
||||
assert_eq!(bus_cycles(0, address, AccessWidth::Byte, kind), 1);
|
||||
assert_eq!(bus_cycles(0, address, AccessWidth::Halfword, kind), 1);
|
||||
assert_eq!(bus_cycles(0, address, AccessWidth::Word, kind), 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue