1//! `reference OUT.png` paints the render-probe scene through `draw` over wgpu,
2//! `reference --text SCALE OUT.png` the text-probe scene; `reference --compare A.png B.png
3//! DIFF.png` reports how two renders differ and writes their difference, amplified.
4#[path = "../../probe/src/scene.rs"]
5mod scene;
6
7fn main() {
8 let args: Vec<String> = std::env::args().skip(1).collect();
9 match args.as_slice() {
10 [flag, a, b, diff] if flag == "--compare" => compare(a, b, diff),
11 [flag, scale, out] if flag == "--text" => {
12 let scale: f32 = scale.parse().unwrap();
13 let ui = scene::interface(scale);
14 let layers = ui.layers();
15 let size = scene::TEXT_SIZE.map(|side| (side as f32 * scale) as u32);
16 paint(out, size, [1.0; 4], &scene::interface_layers(&layers, scale));
17 }
18 [out] => {
19 let checker = scene::checker();
20 let primitives = scene::primitives(&checker);
21 paint(out, scene::SIZE, scene::CLEAR, &[scene::layer(&primitives)]);
22 }
23 _ => panic!("usage: reference [--text SCALE] OUT.png | reference --compare A.png B.png DIFF.png"),
24 }
25}
26
27fn read(path: &str) -> ([u32; 2], Vec<u8>) {
28 let mut decoder = png::Decoder::new(std::io::BufReader::new(std::fs::File::open(path).unwrap()));
29 decoder.set_transformations(png::Transformations::normalize_to_color8());
30 let mut reader = decoder.read_info().unwrap();
31 let mut pixels = vec![0; reader.output_buffer_size().unwrap()];
32 let info = reader.next_frame(&mut pixels).unwrap();
33 assert_eq!(info.color_type, png::ColorType::Rgba, "{path}");
34 ([info.width, info.height], pixels)
35}
36
37fn compare(a: &str, b: &str, diff: &str) {
38 let (size, a) = read(a);
39 let (other, b) = read(b);
40 assert_eq!(size, other, "sizes differ");
41 let delta: Vec<u8> = a.iter().zip(&b).map(|(a, b)| a.abs_diff(*b)).collect();
42 let worst = delta.iter().max().unwrap();
43 let off = delta.chunks(4).filter(|p| p.iter().any(|v| *v > 2)).count();
44 println!("max channel difference {worst}; {off} of {} pixels differ by more than 2", delta.len() / 4);
45 let shown: Vec<u8> = delta
46 .chunks(4)
47 .flat_map(|p| [p[0], p[1], p[2]].map(|v| v.saturating_mul(16)).into_iter().chain([255]))
48 .collect();
49 write(diff, size, &shown);
50}
51
52fn write(path: &str, [width, height]: [u32; 2], pixels: &[u8]) {
53 let mut encoder = png::Encoder::new(std::fs::File::create(path).unwrap(), width, height);
54 encoder.set_color(png::ColorType::Rgba);
55 encoder.write_header().unwrap().write_image_data(pixels).unwrap();
56}
57
58fn paint(out: &str, size: [u32; 2], clear: [f32; 4], layers: &[draw::Layer<'_>]) {
59 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
60 let adapter = pollster::block_on(instance.request_adapter(&Default::default())).unwrap();
61 let (device, queue) = pollster::block_on(adapter.request_device(&Default::default())).unwrap();
62 let format = wgpu::TextureFormat::Rgba8UnormSrgb;
63 let mut renderer = draw::Renderer::new(device.clone(), queue.clone(), format);
64 let [width, height] = size;
65 let texture = device.create_texture(&wgpu::TextureDescriptor {
66 label: None,
67 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
68 mip_level_count: 1,
69 sample_count: 1,
70 dimension: wgpu::TextureDimension::D2,
71 format,
72 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
73 view_formats: &[],
74 });
75 renderer
76 .draw(&texture.create_view(&Default::default()), size, clear, layers)
77 .unwrap();
78 let row = (width * 4).next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
79 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
80 label: None,
81 size: u64::from(row * height),
82 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
83 mapped_at_creation: false,
84 });
85 let mut encoder = device.create_command_encoder(&Default::default());
86 encoder.copy_texture_to_buffer(
87 texture.as_image_copy(),
88 wgpu::TexelCopyBufferInfo {
89 buffer: &buffer,
90 layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(row), rows_per_image: None },
91 },
92 wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
93 );
94 queue.submit([encoder.finish()]);
95 buffer.map_async(wgpu::MapMode::Read, .., |result| result.unwrap());
96 device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }).unwrap();
97 let mapped = buffer.get_mapped_range(..).unwrap();
98 let pixels: Vec<u8> = mapped.chunks(row as usize).flat_map(|r| &r[..width as usize * 4]).copied().collect();
99 write(out, size, &pixels);
100}