use cranpose_ui_graphics::{Brush, Color, Point};
use crate::support;
const WIDTH: u32 = 128;
const HEIGHT: u32 = 96;
const RECORDS: usize = 72;
const RED: Color = Color(0.8, 0.2, 0.2, 1.0);
const GREEN: Color = Color(0.2, 0.7, 0.3, 1.0);
fn pixel_at_record(pixels: &[u8], index: usize) -> [u8; 3] {
let rect = support::stored_run_rect(index);
let x = (rect.x + rect.width * 0.5) as usize;
let y = (rect.y + rect.height * 0.5) as usize;
let at = (y * WIDTH as usize + x) * 4;
[pixels[at + 2], pixels[at + 1], pixels[at]]
}
fn is_red(pixel: [u8; 3]) -> bool {
pixel[0] > 200 && pixel[1] < 160
}
fn is_green(pixel: [u8; 3]) -> bool {
pixel[1] > 200 && pixel[0] < 160
}
fn background(pixels: &[u8]) -> [u8; 3] {
let at = ((HEIGHT as usize - 1) * WIDTH as usize + WIDTH as usize - 1) * 4;
[pixels[at + 2], pixels[at + 1], pixels[at]]
}
#[test]
fn a_stored_run_draws_every_record_the_recording_changed() {
let mut renderer = match support::headless_renderer() {
Ok(renderer) => renderer,
Err(err) => {
eprintln!("skipping run store upload: headless WGPU init failed: {err}");
return;
}
};
let present = |renderer: &mut support::LockedRenderer, colors: &[Color]| {
support::present_and_read(
renderer,
WIDTH,
HEIGHT,
support::stored_run_graph(WIDTH, HEIGHT, colors),
)
};
let all_red = vec![RED; RECORDS];
let first = present(&mut renderer, &all_red);
assert!(is_red(pixel_at_record(&first, 0)));
assert!(is_red(pixel_at_record(&first, 50)));
let mut one_green = all_red.clone();
one_green[50] = GREEN;
let middle = present(&mut renderer, &one_green);
assert!(
is_green(pixel_at_record(&middle, 50)),
"the recoloured record in the middle of the table must draw green"
);
assert!(is_red(pixel_at_record(&middle, 0)));
assert!(is_red(pixel_at_record(&middle, RECORDS - 1)));
let mut last_green = all_red.clone();
last_green[RECORDS - 1] = GREEN;
let last = present(&mut renderer, &last_green);
assert!(
is_red(pixel_at_record(&last, 50)),
"the middle record must be red again"
);
assert!(is_green(pixel_at_record(&last, RECORDS - 1)));
let mut appended = all_red.clone();
appended.push(GREEN);
let longer = present(&mut renderer, &appended);
assert!(
is_green(pixel_at_record(&longer, RECORDS)),
"a record appended past the old table's end must draw"
);
assert!(is_red(pixel_at_record(&longer, RECORDS - 1)));
let shorter = present(&mut renderer, &all_red[..RECORDS - 1]);
let background = background(&shorter);
assert_eq!(
pixel_at_record(&shorter, RECORDS - 1),
background,
"a record the recording dropped must not draw from the stale table"
);
assert_eq!(pixel_at_record(&shorter, RECORDS), background);
assert!(is_red(pixel_at_record(&shorter, RECORDS - 2)));
}
#[test]
fn stored_runs_changed_every_frame_draw_their_own_tables_while_earlier_frames_are_in_flight() {
const FRAMES: usize = 8;
type Renderer = fn() -> Result<support::LockedRenderer, String>;
let renderers: [(&str, Renderer); 2] = [
("default uploads", support::headless_renderer),
("copied uploads", support::headless_renderer_copying_uploads),
];
for (uploads, renderer) in renderers {
let mut renderer = match renderer() {
Ok(renderer) => renderer,
Err(err) => {
eprintln!("skipping run store upload: headless WGPU init failed: {err}");
return;
}
};
let green = |frame: usize| frame * 7 % RECORDS;
let frames: Vec<Vec<Color>> = (0..FRAMES)
.map(|frame| {
let mut colors = vec![RED; RECORDS + frame % 3];
colors[green(frame)] = GREEN;
colors
})
.collect();
let presented: Vec<wgpu::Texture> = frames
.iter()
.map(|colors| {
support::present(
&mut renderer,
WIDTH,
HEIGHT,
support::stored_run_graph(WIDTH, HEIGHT, colors),
)
})
.collect();
for (frame, (colors, texture)) in frames.iter().zip(&presented).enumerate() {
let pixels = support::read_presented(&renderer, texture);
for index in 0..colors.len() {
let pixel = pixel_at_record(&pixels, index);
let expected = if index == green(frame) {
is_green(pixel)
} else {
is_red(pixel)
};
assert!(
expected,
"{uploads}: frame {frame} must draw record {index} as recorded, drew {pixel:?}"
);
}
}
}
}
#[test]
fn stored_runs_that_take_gradients_after_solid_frames_paint_their_own() {
const TALL: u32 = 160;
const PER_RUN: usize = 64;
const BLUE: Color = Color(0.2, 0.3, 0.9, 1.0);
let mut renderer = match support::headless_renderer() {
Ok(renderer) => renderer,
Err(err) => {
eprintln!("skipping run store upload: headless WGPU init failed: {err}");
return;
}
};
let present = |renderer: &mut support::LockedRenderer, runs: [Vec<Brush>; 2]| {
support::present_and_read(
renderer,
WIDTH,
TALL,
support::stored_runs_graph_of(WIDTH, TALL, runs),
)
};
let at = |pixels: &[u8], index: usize| {
let rect = support::stored_run_rect(index);
let x = (rect.x + rect.width * 0.5) as usize;
let y = (rect.y + rect.height * 0.5) as usize;
let at = (y * WIDTH as usize + x) * 4;
[pixels[at + 2], pixels[at + 1], pixels[at]]
};
let gradient = |index: usize, color: Color| {
let rect = support::stored_run_rect(index);
Brush::linear_gradient_range(
vec![color, color],
Point::new(rect.x, rect.y),
Point::new(rect.x + rect.width, rect.y),
)
};
let solid = || vec![Brush::solid(RED); PER_RUN];
let first = present(&mut renderer, [solid(), solid()]);
assert!(is_red(at(&first, 10)) && is_red(at(&first, PER_RUN + 10)));
let mut green = solid();
green[10] = gradient(10, GREEN);
let mut blue = solid();
blue[10] = gradient(PER_RUN + 10, BLUE);
let second = present(&mut renderer, [green, blue]);
assert!(
is_green(at(&second, 10)),
"the first run paints its gradient from a brush table of its own"
);
let second_blue = at(&second, PER_RUN + 10);
assert!(
second_blue[2] > 200 && second_blue[1] < 160,
"the second run paints its own gradient, not the first's: {second_blue:?}"
);
assert!(is_red(at(&second, 0)) && is_red(at(&second, PER_RUN)));
}