| 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"] |
| 5 | mod scene; |
| 6 | |
| 7 | fn 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 | |
| 27 | fn 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 | |
| 37 | fn 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 | |
| 52 | fn 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 | |
| 58 | fn 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 | } |