use mir::bevy::resources::GraphCamera;
use mir::frame::cull::TierLevel;
use mir::frame::paint::{PaintPass, edge_segments, sort_by_depth};
const W: u32 = 320;
const H: u32 = 240;
fn scene() -> (Vec<f32>, Vec<f32>, Vec<f32>, Vec<(u32, TierLevel)>) {
let mut positions = Vec::new();
let mut radii = Vec::new();
let mut colors = Vec::new();
let mut visible = Vec::new();
for i in 0..12u32 {
let a = match i { 3 => 2u32, 7 => 6, other => other } as f32 * 0.7;
positions.extend_from_slice(&[a.cos() * 700.0, a.sin() * 500.0,
(match i { 3 => 2u32, 7 => 6, o => o } % 4) as f32 * 300.0]);
radii.push(180.0);
colors.extend_from_slice(&[0.2, 0.5, 1.0]);
visible.push((i, if i % 2 == 0 { TierLevel::T2 } else { TierLevel::T3 }));
}
(positions, radii, colors, visible)
}
fn camera() -> GraphCamera {
let mut cam = GraphCamera::default();
cam.viewport = [W as f32, H as f32];
cam.yaw = 0.6;
cam.pitch = 0.25;
let f = cam.forward();
cam.position = [-f[0] * 3000.0, -f[1] * 3000.0, -f[2] * 3000.0];
cam
}
fn render(visible: &[(u32, TierLevel)]) -> Vec<u8> {
let (positions, radii, colors, _) = scene();
let cam = camera();
let gpu_cam = cam.to_gpu_camera();
let ordered: Vec<(u32, TierLevel)> = visible.to_vec();
let sorted = sort_by_depth(&ordered, &positions, &gpu_cam);
let mut zipped: Vec<(u32, u32, f32)> = ordered
.iter()
.filter(|&&(i, _)| i + 1 < 12)
.map(|&(i, _)| (i, i + 1, 0.8f32))
.collect();
zipped.sort_unstable_by_key(|&(p, q, _)| (p, q));
let edges: Vec<(u32, u32)> = zipped.iter().map(|&(p, q, _)| (p, q)).collect();
let weights: Vec<f32> = zipped.iter().map(|&(_, _, w)| w).collect();
let segments = edge_segments(&edges, &weights, &positions, &gpu_cam, [W, H]);
let gpu = mir::gpu::Gpu::open().expect("no GPU for the render test");
let queue = gpu.new_command_queue().expect("command queue");
let paint = PaintPass::new().expect("paint pipeline");
let dst = gpu.buffer((W as usize) * (H as usize) * 4).expect("frame buffer");
let cmd = queue.commands().expect("commands");
paint
.draw(&sorted, &ordered, &positions, &radii, &colors,
&segments, &gpu_cam, [W, H], &dst, &cmd)
.expect("draw");
cmd.submit();
let mut pixels = vec![0u8; (W as usize) * (H as usize) * 4];
mir::gpu::FrameReader::new().fetch(&gpu, &queue, &dst, &mut pixels);
pixels
}
fn differing_pixels(a: &[u8], b: &[u8]) -> usize {
a.chunks_exact(4).zip(b.chunks_exact(4)).filter(|(p, q)| p != q).count()
}
#[test]
fn the_same_scene_twice_is_the_same_frame() {
let (_, _, _, visible) = scene();
let a = render(&visible);
let b = render(&visible);
let d = differing_pixels(&a, &b);
assert_eq!(d, 0, "two renders of one scene differ in {d} pixels");
}
#[test]
fn the_order_the_cull_returns_particles_in_does_not_matter() {
let (_, _, _, visible) = scene();
let baseline = render(&visible);
let mut reversed = visible.clone();
reversed.reverse();
let mut rotated = visible.clone();
rotated.rotate_left(5);
let mut interleaved: Vec<_> = visible.iter().step_by(2).copied().collect();
interleaved.extend(visible.iter().skip(1).step_by(2).copied());
for (name, order) in [
("reversed", reversed),
("rotated", rotated),
("interleaved", interleaved),
] {
let d = differing_pixels(&baseline, &render(&order));
assert_eq!(
d, 0,
"the {name} particle order draws {d} pixels differently. Something \
downstream is order-dependent โ which means the image depends on \
GPU scheduling, and the graph shimmers whenever the camera moves."
);
}
}
#[test]
fn the_depth_sort_breaks_ties_the_same_way_every_time() {
let (positions, _, _, visible) = scene();
let cam = camera().to_gpu_camera();
let baseline = sort_by_depth(&visible, &positions, &cam);
let mut reversed = visible.clone();
reversed.reverse();
assert_eq!(
baseline,
sort_by_depth(&reversed, &positions, &cam),
"the depth sort gives a different answer for the same particles in a \
different order โ so the order the cull happened to return them in \
reaches the pixels"
);
}