feat(video): track windows and compose color effects
Add persistent window comparators, region masks, alpha blending, and brightness effects.
This commit is contained in:
parent
fad034ae74
commit
9d14dd7cf4
9 changed files with 1408 additions and 0 deletions
209
tests/window_tracking/horizontal.rs
Normal file
209
tests/window_tracking/horizontal.rs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
use super::*;
|
||||
|
||||
fn scene(horizontal: u16) -> Memory {
|
||||
let mut m = setup(160);
|
||||
m.write16(WIN0H, horizontal).unwrap();
|
||||
m
|
||||
}
|
||||
|
||||
fn check_line(f: &Framebuffer, y: usize, inside: impl Fn(usize) -> bool) {
|
||||
for x in 0..WIDTH {
|
||||
assert_eq!(
|
||||
f.pixels()[y * WIDTH + x],
|
||||
if inside(x) { 0 } else { rgb(31) },
|
||||
"pixel={x},{y}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_left_after_entry_preserves_earlier_pixels_and_active_state() {
|
||||
let mut m = scene(0x0a50);
|
||||
m.advance_cycles(4 * 20 + 1);
|
||||
m.write8(WIN0H + 1, 40).unwrap();
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |x| (10..80).contains(&x));
|
||||
check_line(&f, 1, |x| (40..80).contains(&x));
|
||||
let mut snapshot = Framebuffer::default();
|
||||
m.render_frame(&mut snapshot).unwrap();
|
||||
check_line(&snapshot, 0, |x| (40..80).contains(&x));
|
||||
assert_eq!(present(&m).pixels(), f.pixels());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_left_behind_current_column_does_not_open_an_inactive_window() {
|
||||
let mut m = scene(0x6478);
|
||||
m.advance_cycles(4 * 20);
|
||||
m.write8(WIN0H + 1, 10).unwrap();
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |_| false);
|
||||
check_line(&f, 1, |x| (10..120).contains(&x));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_right_keeps_window_open_into_the_next_line() {
|
||||
let mut m = scene(0x0a50);
|
||||
m.advance_cycles(4 * 30);
|
||||
m.write8(WIN0H, 5).unwrap();
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |x| x >= 10);
|
||||
check_line(&f, 1, |x| !(5..10).contains(&x));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_four_cycle_sampling_boundaries_and_zero_cycle_advances_are_observable() {
|
||||
for elapsed in [79, 80, 81, 83] {
|
||||
let mut m = scene(0x6450);
|
||||
m.advance_cycles(elapsed);
|
||||
m.write16(WIN0H, 0x1450).unwrap();
|
||||
m.advance_cycles(0);
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |x| elapsed <= 80 && (20..80).contains(&x));
|
||||
check_line(&f, 1, |x| (20..80).contains(&x));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal_bounds_clear_at_the_edge_not_at_register_write() {
|
||||
for equal in [20, 40] {
|
||||
let mut m = scene(0x0ac8);
|
||||
m.advance_cycles(4 * 30);
|
||||
m.write16(WIN0H, equal * 0x101).unwrap();
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |x| x >= 10 && (equal == 20 || x < 40));
|
||||
check_line(&f, 1, |x| equal == 20 && x < 20);
|
||||
check_line(&f, 2, |_| false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offscreen_columns_including_late_hblank_set_next_lines_wrapping_state() {
|
||||
for left in [240, 251, 252, 255] {
|
||||
let mut m = scene((left << 8) | 10);
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |_| false); // No previous line at startup.
|
||||
check_line(&f, 1, |x| x < 10);
|
||||
check_line(&f, 159, |x| x < 10);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hblank_writes_can_cancel_a_late_offscreen_edge_without_changing_captured_row() {
|
||||
let mut m = scene(0xff0a);
|
||||
m.advance_cycles(HB);
|
||||
m.write8(WIN0H + 1, 20).unwrap(); // X255 not reached; X20 already passed.
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |_| false);
|
||||
check_line(&f, 1, |x| x >= 20);
|
||||
check_line(&f, 2, |x| !(10..20).contains(&x));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independent_window_histories_preserve_win0_precedence() {
|
||||
let mut m = scene(0x0a50);
|
||||
m.write16(WIN1H, 0x1464).unwrap();
|
||||
m.write16(WIN1V, 160).unwrap();
|
||||
m.write16(DISPCNT, 0x6000).unwrap();
|
||||
m.write16(WININ, 0x2000).unwrap(); // WIN0 red, WIN1 black.
|
||||
m.advance_cycles(4 * 30);
|
||||
m.write32(WIN0H, 0x1464_3250).unwrap(); // Move WIN0's left edge only.
|
||||
let f = finish(&mut m);
|
||||
check_line(&f, 0, |x| (80..100).contains(&x));
|
||||
check_line(&f, 1, |x| (20..50).contains(&x) || (80..100).contains(&x));
|
||||
assert_eq!(m.read32(WIN0H).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_windows_and_forced_blank_do_not_pause_horizontal_history() {
|
||||
for control in [0x4000, 0x2080] {
|
||||
let mut m = scene(0x0a50);
|
||||
m.write16(DISPCNT, control).unwrap();
|
||||
m.advance_cycles(4 * 30);
|
||||
m.write16(DISPCNT, 0x2000).unwrap();
|
||||
let f = finish(&mut m);
|
||||
// Enable/blank bits still sample at HBlank, unlike WIN0H comparator history.
|
||||
check_line(&f, 0, |x| (10..80).contains(&x));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hblank_dma_can_set_an_offscreen_edge_before_the_next_line() {
|
||||
let mut m = scene(0);
|
||||
for row in 0..160 {
|
||||
m.write16(0x0200_0000 + row * 2, 0xff0a).unwrap();
|
||||
}
|
||||
m.write32(DMA_BASE, 0x0200_0000).unwrap();
|
||||
m.write32(DMA_BASE + 4, WIN0H).unwrap();
|
||||
m.write32(DMA_BASE + 8, 0xa240_0001).unwrap();
|
||||
m.write8(HALTCNT, 0).unwrap();
|
||||
let mut machine = Machine::new(Cpu::new(ROM_START), m);
|
||||
machine.run_until_vblank(1000).unwrap();
|
||||
let f = present(machine.memory());
|
||||
check_line(&f, 0, |_| false);
|
||||
for y in 1..HEIGHT {
|
||||
check_line(&f, y, |x| x < 10);
|
||||
}
|
||||
assert!(machine.halted());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_store_crossing_a_comparator_uses_committed_bounds_not_final_instruction_time() {
|
||||
for after in [false, true] {
|
||||
let mut m = scene(0x6450);
|
||||
let code = [
|
||||
0xe3a0_0301, // MOV r0,#0x04000000
|
||||
0xe3a0_1b05, // MOV r1,#0x1400
|
||||
0xe381_1050, // ORR r1,r1,#0x50
|
||||
0xe1c0_14b0, // STRH r1,[r0,#0x40] (WIN0H)
|
||||
];
|
||||
for (i, value) in code.into_iter().enumerate() {
|
||||
m.write32(0x0300_0000 + i as u32 * 4, value).unwrap();
|
||||
}
|
||||
let mut machine = Machine::new(Cpu::new(0x0300_0000), m);
|
||||
for _ in 0..3 {
|
||||
machine.step().unwrap();
|
||||
}
|
||||
let cycles = machine.cycles() as u32;
|
||||
machine
|
||||
.memory_mut()
|
||||
.advance_cycles(if after { 81 } else { 79 } - cycles);
|
||||
machine.step().unwrap();
|
||||
assert!(machine.cycles() > 80);
|
||||
let f = finish(machine.memory_mut());
|
||||
check_line(&f, 0, |x| !after && (20..80).contains(&x));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pixel_colors_still_sample_at_hblank_while_window_edges_keep_history() {
|
||||
let mut m = scene(0x0a50);
|
||||
m.advance_cycles(4 * 30);
|
||||
m.write8(WIN0H + 1, 40).unwrap();
|
||||
m.write16(PAL, 0x7c00).unwrap();
|
||||
let f = finish(&mut m);
|
||||
for x in 0..WIDTH {
|
||||
assert_eq!(
|
||||
f.pixels()[x],
|
||||
if (10..80).contains(&x) {
|
||||
0
|
||||
} else {
|
||||
rgb(0x7c00)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capture_enable_preserves_partial_line_history_and_hidden_lines_update_flags() {
|
||||
let mut m = scene(0x0a50);
|
||||
m.set_scanline_rendering(false);
|
||||
m.advance_cycles(4 * 30);
|
||||
m.write8(WIN0H + 1, 40).unwrap();
|
||||
m.set_scanline_rendering(true); // Before first HBlank, this frame is still complete.
|
||||
check_line(&finish(&mut m), 0, |x| (10..80).contains(&x));
|
||||
m.set_scanline_rendering(false);
|
||||
m.write16(WIN0H, 0xff0a).unwrap();
|
||||
m.advance_cycles(FRAME - VB);
|
||||
m.set_scanline_rendering(true);
|
||||
check_line(&finish(&mut m), 0, |x| x < 10);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue