Add original ARM graphics programs, input controls, DMA initialization, and a bounded frame runner.
297 lines
11 KiB
Rust
297 lines
11 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|