mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-02-16 08:53:20 +00:00
Add image comparison to the mipmap example
This commit is contained in:
parent
3d8a4baeb8
commit
ebbbf2216b
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,3 +15,6 @@
|
|||||||
|
|
||||||
# Output from capture example
|
# Output from capture example
|
||||||
wgpu/red.png
|
wgpu/red.png
|
||||||
|
|
||||||
|
# Output from invalid comparison tests
|
||||||
|
**/screenshot-difference.png
|
||||||
|
@ -95,6 +95,10 @@ test = true
|
|||||||
name="texture-arrays"
|
name="texture-arrays"
|
||||||
required-features = ["spirv"]
|
required-features = ["spirv"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name="mipmap"
|
||||||
|
test = true
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
wasm-bindgen = "0.2.73" # remember to change version in wiki as well
|
wasm-bindgen = "0.2.73" # remember to change version in wiki as well
|
||||||
web-sys = { version = "=0.3.50", features = [
|
web-sys = { version = "=0.3.50", features = [
|
||||||
|
@ -254,14 +254,14 @@ impl framework::Example for Example {
|
|||||||
/// a TriangleList draw call for all NUM_PARTICLES at 3 vertices each
|
/// a TriangleList draw call for all NUM_PARTICLES at 3 vertices each
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
) {
|
) {
|
||||||
// create render pass descriptor and its color attachments
|
// create render pass descriptor and its color attachments
|
||||||
let color_attachments = [wgpu::RenderPassColorAttachment {
|
let color_attachments = [wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
@ -284,7 +284,7 @@ impl framework::Example for Example {
|
|||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -323,7 +323,7 @@ impl framework::Example for Example {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(clear_color),
|
load: wgpu::LoadOp::Clear(clear_color),
|
||||||
|
@ -253,7 +253,7 @@ impl framework::Example for Example {
|
|||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -285,7 +285,7 @@ impl framework::Example for Example {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("full resolution"),
|
label: Some("full resolution"),
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
@ -333,7 +333,7 @@ impl framework::Example for Example {
|
|||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -344,7 +344,7 @@ impl framework::Example for Example {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
@ -6,6 +6,9 @@ use winit::{
|
|||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[path = "../tests/common/mod.rs"]
|
||||||
|
mod test_common;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
||||||
@ -54,7 +57,7 @@ pub trait Example: 'static + Sized {
|
|||||||
fn update(&mut self, event: WindowEvent);
|
fn update(&mut self, event: WindowEvent);
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
spawner: &Spawner,
|
spawner: &Spawner,
|
||||||
@ -280,7 +283,7 @@ fn start<E: Example>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
example.render(&frame.output, &device, &queue, &spawner);
|
example.render(&frame.output.view, &device, &queue, &spawner);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -361,6 +364,108 @@ pub fn run<E: Example>(title: &str) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn test<E: Example>(image_path: &str, width: u32, height: u32, tollerance: u8, max_outliers: usize) {
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
assert_eq!(width % 64, 0, "width needs to be aligned 64");
|
||||||
|
|
||||||
|
let _optional = E::optional_features();
|
||||||
|
let features = E::required_features();
|
||||||
|
let mut limits = E::required_limits();
|
||||||
|
if limits == wgpu::Limits::default() {
|
||||||
|
limits = test_common::lowest_reasonable_limits();
|
||||||
|
}
|
||||||
|
|
||||||
|
test_common::initialize_test(
|
||||||
|
test_common::TestParameters::default()
|
||||||
|
.features(features)
|
||||||
|
.limits(limits),
|
||||||
|
|ctx| {
|
||||||
|
let spawner = Spawner::new();
|
||||||
|
|
||||||
|
let dst_texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("destination"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT | wgpu::TextureUsage::COPY_SRC,
|
||||||
|
});
|
||||||
|
|
||||||
|
let dst_view = dst_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let dst_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("image map buffer"),
|
||||||
|
size: width as u64 * height as u64 * 4,
|
||||||
|
usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut example = E::init(
|
||||||
|
&wgpu::SwapChainDescriptor {
|
||||||
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
present_mode: wgpu::PresentMode::Fifo,
|
||||||
|
},
|
||||||
|
&ctx.adapter,
|
||||||
|
&ctx.device,
|
||||||
|
&ctx.queue,
|
||||||
|
);
|
||||||
|
|
||||||
|
example.render(&dst_view, &ctx.device, &ctx.queue, &spawner);
|
||||||
|
|
||||||
|
let mut cmd_buf = ctx
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
|
||||||
|
|
||||||
|
cmd_buf.copy_texture_to_buffer(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &dst_texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
},
|
||||||
|
wgpu::ImageCopyBuffer {
|
||||||
|
buffer: &dst_buffer,
|
||||||
|
layout: wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: NonZeroU32::new(width * 4),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.queue.submit(Some(cmd_buf.finish()));
|
||||||
|
|
||||||
|
let dst_buffer_slice = dst_buffer.slice(..);
|
||||||
|
let _ = dst_buffer_slice.map_async(wgpu::MapMode::Read);
|
||||||
|
ctx.device.poll(wgpu::Maintain::Wait);
|
||||||
|
let bytes = dst_buffer_slice.get_mapped_range().to_vec();
|
||||||
|
|
||||||
|
test_common::image::compare_image_output(
|
||||||
|
&image_path,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
&bytes,
|
||||||
|
tollerance,
|
||||||
|
max_outliers,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// This allows treating the framework as a standalone example,
|
// This allows treating the framework as a standalone example,
|
||||||
// thus avoiding listing the example names in `Cargo.toml`.
|
// thus avoiding listing the example names in `Cargo.toml`.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -436,7 +436,7 @@ impl framework::Example for Example {
|
|||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -453,7 +453,7 @@ impl framework::Example for Example {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(clear_color),
|
load: wgpu::LoadOp::Clear(clear_color),
|
||||||
@ -474,3 +474,17 @@ impl framework::Example for Example {
|
|||||||
fn main() {
|
fn main() {
|
||||||
framework::run::<Example>("mipmap");
|
framework::run::<Example>("mipmap");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mipmap() {
|
||||||
|
framework::test::<Example>(
|
||||||
|
concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/examples/mipmap/screenshot.png"
|
||||||
|
),
|
||||||
|
1024,
|
||||||
|
768,
|
||||||
|
10, // Mipmap sampling is highly variant between impls.
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 531 KiB |
@ -225,7 +225,7 @@ impl framework::Example for Example {
|
|||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -254,14 +254,14 @@ impl framework::Example for Example {
|
|||||||
};
|
};
|
||||||
let rpass_color_attachment = if self.sample_count == 1 {
|
let rpass_color_attachment = if self.sample_count == 1 {
|
||||||
wgpu::RenderPassColorAttachment {
|
wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops,
|
ops,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wgpu::RenderPassColorAttachment {
|
wgpu::RenderPassColorAttachment {
|
||||||
view: &self.multisampled_framebuffer,
|
view: &self.multisampled_framebuffer,
|
||||||
resolve_target: Some(&frame.view),
|
resolve_target: Some(&view),
|
||||||
ops,
|
ops,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -688,7 +688,7 @@ impl framework::Example for Example {
|
|||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -781,7 +781,7 @@ impl framework::Example for Example {
|
|||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
@ -389,7 +389,7 @@ impl framework::Example for Skybox {
|
|||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
spawner: &framework::Spawner,
|
spawner: &framework::Spawner,
|
||||||
@ -415,7 +415,7 @@ impl framework::Example for Skybox {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
@ -278,7 +278,7 @@ impl framework::Example for Example {
|
|||||||
}
|
}
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -290,7 +290,7 @@ impl framework::Example for Example {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
@ -663,7 +663,7 @@ impl framework::Example for Example {
|
|||||||
#[allow(clippy::eq_op)]
|
#[allow(clippy::eq_op)]
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
frame: &wgpu::SwapChainTexture,
|
view: &wgpu::TextureView,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_spawner: &framework::Spawner,
|
_spawner: &framework::Spawner,
|
||||||
@ -734,7 +734,7 @@ impl framework::Example for Example {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(back_color),
|
load: wgpu::LoadOp::Clear(back_color),
|
||||||
@ -761,7 +761,7 @@ impl framework::Example for Example {
|
|||||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
view: &frame.view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
125
wgpu/tests/common/image.rs
Normal file
125
wgpu/tests/common/image.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
fs::File,
|
||||||
|
io::{BufWriter, Cursor},
|
||||||
|
path::Path,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn read_png(path: impl AsRef<Path>, width: u32, height: u32) -> Option<Vec<u8>> {
|
||||||
|
let data = match std::fs::read(&path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"image comparison invalid: file io error when comparing {}: {}",
|
||||||
|
path.as_ref().display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let decoder = png::Decoder::new(Cursor::new(data));
|
||||||
|
let (info, mut reader) = decoder.read_info().ok()?;
|
||||||
|
if info.width != width {
|
||||||
|
log::warn!("image comparison invalid: size mismatch");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if info.height != height {
|
||||||
|
log::warn!("image comparison invalid: size mismatch");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if info.color_type != png::ColorType::RGBA {
|
||||||
|
log::warn!("image comparison invalid: color type mismatch");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if info.bit_depth != png::BitDepth::Eight {
|
||||||
|
log::warn!("image comparison invalid: bit depth mismatch");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = vec![0; info.buffer_size()];
|
||||||
|
reader.next_frame(&mut buffer).ok()?;
|
||||||
|
|
||||||
|
Some(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_png(path: impl AsRef<Path>, width: u32, height: u32, data: &[u8]) {
|
||||||
|
let file = BufWriter::new(File::create(path).unwrap());
|
||||||
|
|
||||||
|
let mut encoder = png::Encoder::new(file, width, height);
|
||||||
|
encoder.set_color(png::ColorType::RGBA);
|
||||||
|
encoder.set_depth(png::BitDepth::Eight);
|
||||||
|
encoder.set_compression(png::Compression::Best);
|
||||||
|
let mut writer = encoder.write_header().unwrap();
|
||||||
|
|
||||||
|
writer.write_image_data(&data).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_difference(lhs: u8, rhs: u8) -> u8 {
|
||||||
|
(lhs as i16 - rhs as i16).abs() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compare_image_output(
|
||||||
|
path: impl AsRef<Path> + AsRef<OsStr>,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
data: &[u8],
|
||||||
|
tollerance: u8,
|
||||||
|
max_outliers: usize,
|
||||||
|
) {
|
||||||
|
let comparison_data = read_png(&path, width, height);
|
||||||
|
|
||||||
|
if let Some(cmp) = comparison_data {
|
||||||
|
assert_eq!(cmp.len(), data.len());
|
||||||
|
|
||||||
|
let difference_data: Vec<_> = cmp
|
||||||
|
.chunks_exact(4)
|
||||||
|
.zip(data.chunks_exact(4))
|
||||||
|
.flat_map(|(cmp_chunk, data_chunk)| {
|
||||||
|
[
|
||||||
|
calc_difference(cmp_chunk[0], data_chunk[0]),
|
||||||
|
calc_difference(cmp_chunk[1], data_chunk[1]),
|
||||||
|
calc_difference(cmp_chunk[2], data_chunk[2]),
|
||||||
|
255,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let outliers: usize = difference_data
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|colors| {
|
||||||
|
(colors[0] > tollerance) as usize
|
||||||
|
+ (colors[1] > tollerance) as usize
|
||||||
|
+ (colors[2] > tollerance) as usize
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let max_difference = difference_data
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|colors| colors[0].max(colors[1]).max(colors[2]))
|
||||||
|
.max()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if outliers >= max_outliers {
|
||||||
|
// Because the deta is mismatched, lets output the difference to a file.
|
||||||
|
let old_path = Path::new(&path);
|
||||||
|
let difference_path = Path::new(&path).with_file_name(
|
||||||
|
OsString::from_str(
|
||||||
|
&(old_path.file_stem().unwrap().to_string_lossy() + "-difference.png"),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
write_png(&difference_path, width, height, &difference_data);
|
||||||
|
|
||||||
|
panic!("Image data mismatch! Outlier count {} over limit {}. Max difference {}", outliers, max_outliers, max_difference)
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{} outliers over max difference {}",
|
||||||
|
outliers, max_difference
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write_png(&path, width, height, data);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
//! This module contains common test-only code that needs to be shared between the examples and the tests.
|
//! This module contains common test-only code that needs to be shared between the examples and the tests.
|
||||||
|
#![allow(dead_code)] // This module is used in a lot of contexts and only parts of it will be used
|
||||||
|
|
||||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
|
|
||||||
@ -6,6 +7,8 @@ use wgt::{BackendBit, DeviceDescriptor, DownlevelProperties, Features, Limits};
|
|||||||
|
|
||||||
use wgpu::{util, Adapter, Device, Instance, Queue};
|
use wgpu::{util, Adapter, Device, Instance, Queue};
|
||||||
|
|
||||||
|
pub mod image;
|
||||||
|
|
||||||
async fn initialize_device(
|
async fn initialize_device(
|
||||||
adapter: &Adapter,
|
adapter: &Adapter,
|
||||||
features: Features,
|
features: Features,
|
||||||
@ -37,10 +40,10 @@ pub struct TestingContext {
|
|||||||
|
|
||||||
// A rather arbitrary set of limits which should be lower than all devices wgpu reasonably expects to run on and provides enough resources for most tests to run.
|
// A rather arbitrary set of limits which should be lower than all devices wgpu reasonably expects to run on and provides enough resources for most tests to run.
|
||||||
// Adjust as needed if they are too low/high.
|
// Adjust as needed if they are too low/high.
|
||||||
fn lowest_reasonable_limits() -> Limits {
|
pub fn lowest_reasonable_limits() -> Limits {
|
||||||
Limits {
|
Limits {
|
||||||
max_texture_dimension_1d: 512,
|
max_texture_dimension_1d: 1024,
|
||||||
max_texture_dimension_2d: 512,
|
max_texture_dimension_2d: 1024,
|
||||||
max_texture_dimension_3d: 32,
|
max_texture_dimension_3d: 32,
|
||||||
max_texture_array_layers: 32,
|
max_texture_array_layers: 32,
|
||||||
max_bind_groups: 1,
|
max_bind_groups: 1,
|
||||||
@ -97,7 +100,6 @@ impl Default for TestParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Builder pattern to make it easier
|
// Builder pattern to make it easier
|
||||||
#[allow(dead_code)]
|
|
||||||
impl TestParameters {
|
impl TestParameters {
|
||||||
/// Set of common features that most tests require.
|
/// Set of common features that most tests require.
|
||||||
pub fn test_features(self) -> Self {
|
pub fn test_features(self) -> Self {
|
||||||
@ -109,6 +111,11 @@ impl TestParameters {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn limits(mut self, limits: Limits) -> Self {
|
||||||
|
self.required_limits = limits;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn backend_failures(mut self, backends: BackendBit) -> Self {
|
pub fn backend_failures(mut self, backends: BackendBit) -> Self {
|
||||||
self.backend_failures |= backends;
|
self.backend_failures |= backends;
|
||||||
self
|
self
|
||||||
|
Loading…
Reference in New Issue
Block a user