Add tests for scissors (#3877)

This commit is contained in:
artyomd 2023-07-19 17:48:23 -04:00 committed by GitHub
parent d089b9488b
commit 11b8dbbea3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 341 additions and 107 deletions

View File

@ -20,11 +20,12 @@ webgl = ["wgpu/webgl"]
[dependencies]
bitflags.workspace = true
bytemuck.workspace = true
cfg-if.workspace = true
env_logger.workspace = true
log.workspace = true
pollster.workspace = true
png.workspace = true
pollster.workspace = true
wgpu.workspace = true
wgt.workspace = true
@ -38,13 +39,12 @@ wasm-bindgen.workspace = true
web-sys = { workspace = true }
[dev-dependencies]
bytemuck.workspace = true
naga = { workspace = true, features = ["wgsl-in"] }
wasm-bindgen-test.workspace = true
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
js-sys.workspace = true
image.workspace = true
wasm-bindgen.workspace = true
js-sys.workspace = true
wasm-bindgen-futures.workspace = true
wasm-bindgen.workspace = true
web-sys = { workspace = true, features = ["CanvasRenderingContext2d", "Blob"] }

View File

@ -1,5 +1,6 @@
use std::{borrow::Cow, ffi::OsStr, io, path::Path};
use wgpu::util::DeviceExt;
use wgpu::util::{align_to, DeviceExt};
use wgpu::*;
fn read_png(path: impl AsRef<Path>, width: u32, height: u32) -> Option<Vec<u8>> {
@ -149,7 +150,7 @@ impl ComparisonType {
pub fn compare_image_output(
path: impl AsRef<Path> + AsRef<OsStr>,
backend: wgpu::Backend,
backend: Backend,
width: u32,
height: u32,
test_with_alpha: &[u8],
@ -367,6 +368,10 @@ fn copy_texture_to_buffer_with_aspect(
) {
let (block_width, block_height) = texture.format().block_dimensions();
let block_size = texture.format().block_size(Some(aspect)).unwrap();
let bytes_per_row = align_to(
(texture.width() / block_width) * block_size,
COPY_BYTES_PER_ROW_ALIGNMENT,
);
let mip_level = 0;
encoder.copy_texture_to_buffer(
ImageCopyTexture {
@ -382,7 +387,7 @@ fn copy_texture_to_buffer_with_aspect(
},
layout: ImageDataLayout {
offset: 0,
bytes_per_row: Some((texture.width() / block_width) * block_size),
bytes_per_row: Some(bytes_per_row),
rows_per_image: Some(texture.height() / block_height),
},
},
@ -449,6 +454,14 @@ fn copy_texture_to_buffer(
}
pub struct ReadbackBuffers {
/// texture format
texture_format: TextureFormat,
/// texture width
texture_width: u32,
/// texture height
texture_height: u32,
/// texture depth or array layer count
texture_depth_or_array_layers: u32,
/// buffer for color or depth aspects
buffer: Buffer,
/// buffer for stencil aspect
@ -458,20 +471,37 @@ pub struct ReadbackBuffers {
impl ReadbackBuffers {
pub fn new(device: &Device, texture: &Texture) -> Self {
let (block_width, block_height) = texture.format().block_dimensions();
let base_size = (texture.width() / block_width)
* (texture.height() / block_height)
* texture.depth_or_array_layers();
const SKIP_ALIGNMENT_FORMATS: [TextureFormat; 2] = [
TextureFormat::Depth24Plus,
TextureFormat::Depth24PlusStencil8,
];
let should_align_buffer_size = !SKIP_ALIGNMENT_FORMATS.contains(&texture.format());
if texture.format().is_combined_depth_stencil_format() {
let buffer_size = base_size
let mut buffer_depth_bytes_per_row = (texture.width() / block_width)
* texture
.format()
.block_size(Some(TextureAspect::DepthOnly))
.unwrap_or(4);
let buffer_stencil_size = base_size
* texture
.format()
.block_size(Some(TextureAspect::StencilOnly))
.unwrap();
if should_align_buffer_size {
buffer_depth_bytes_per_row =
align_to(buffer_depth_bytes_per_row, COPY_BYTES_PER_ROW_ALIGNMENT);
}
let buffer_size = buffer_depth_bytes_per_row
* (texture.height() / block_height)
* texture.depth_or_array_layers();
let buffer_stencil_bytes_per_row = align_to(
(texture.width() / block_width)
* texture
.format()
.block_size(Some(TextureAspect::StencilOnly))
.unwrap_or(4),
COPY_BYTES_PER_ROW_ALIGNMENT,
);
let buffer_stencil_size = buffer_stencil_bytes_per_row
* (texture.height() / block_height)
* texture.depth_or_array_layers();
let buffer = device.create_buffer_init(&util::BufferInitDescriptor {
label: Some("Texture Readback"),
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
@ -483,17 +513,31 @@ impl ReadbackBuffers {
contents: &vec![255; buffer_stencil_size as usize],
});
ReadbackBuffers {
texture_format: texture.format(),
texture_width: texture.width(),
texture_height: texture.height(),
texture_depth_or_array_layers: texture.depth_or_array_layers(),
buffer,
buffer_stencil: Some(buffer_stencil),
}
} else {
let buffer_size = base_size * texture.format().block_size(None).unwrap_or(4);
let mut bytes_per_row =
(texture.width() / block_width) * texture.format().block_size(None).unwrap_or(4);
if should_align_buffer_size {
bytes_per_row = align_to(bytes_per_row, COPY_BYTES_PER_ROW_ALIGNMENT);
}
let buffer_size =
bytes_per_row * (texture.height() / block_height) * texture.depth_or_array_layers();
let buffer = device.create_buffer_init(&util::BufferInitDescriptor {
label: Some("Texture Readback"),
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
contents: &vec![255; buffer_size as usize],
});
ReadbackBuffers {
texture_format: texture.format(),
texture_width: texture.width(),
texture_height: texture.height(),
texture_depth_or_array_layers: texture.depth_or_array_layers(),
buffer,
buffer_stencil: None,
}
@ -505,24 +549,67 @@ impl ReadbackBuffers {
copy_texture_to_buffer(device, encoder, texture, &self.buffer, &self.buffer_stencil);
}
fn retrieve_buffer(
&self,
device: &Device,
buffer: &Buffer,
aspect: Option<TextureAspect>,
) -> Vec<u8> {
let buffer_slice = buffer.slice(..);
buffer_slice.map_async(MapMode::Read, |_| ());
device.poll(Maintain::Wait);
let (block_width, block_height) = self.texture_format.block_dimensions();
let expected_bytes_per_row = (self.texture_width / block_width)
* self.texture_format.block_size(aspect).unwrap_or(4);
let expected_buffer_size = expected_bytes_per_row
* (self.texture_height / block_height)
* self.texture_depth_or_array_layers;
let data: BufferView = buffer_slice.get_mapped_range();
if expected_buffer_size as usize == data.len() {
data.to_vec()
} else {
bytemuck::cast_slice(&data)
.chunks_exact(
align_to(expected_bytes_per_row, COPY_BYTES_PER_ROW_ALIGNMENT) as usize,
)
.flat_map(|x| x.iter().take(expected_bytes_per_row as usize))
.copied()
.collect()
}
}
fn buffer_aspect(&self) -> Option<TextureAspect> {
if self.texture_format.is_combined_depth_stencil_format() {
Some(TextureAspect::DepthOnly)
} else {
None
}
}
pub fn are_zero(&self, device: &Device) -> bool {
fn is_zero(device: &Device, buffer: &Buffer) -> bool {
let is_zero = {
let buffer_slice = buffer.slice(..);
buffer_slice.map_async(MapMode::Read, |_| ());
device.poll(Maintain::Wait);
let buffer_view = buffer_slice.get_mapped_range();
buffer_view.iter().all(|b| *b == 0)
};
let is_zero = |device: &Device, buffer: &Buffer, aspect: Option<TextureAspect>| -> bool {
let is_zero = self
.retrieve_buffer(device, buffer, aspect)
.iter()
.all(|b| *b == 0);
buffer.unmap();
is_zero
}
};
is_zero(device, &self.buffer)
&& self
.buffer_stencil
.as_ref()
.map(|buffer_stencil| is_zero(device, buffer_stencil))
.unwrap_or(true)
let buffer_zero = is_zero(device, &self.buffer, self.buffer_aspect());
let mut stencil_buffer_zero = true;
if let Some(buffer) = &self.buffer_stencil {
stencil_buffer_zero = is_zero(device, buffer, Some(TextureAspect::StencilOnly));
};
buffer_zero && stencil_buffer_zero
}
pub fn check_buffer_contents(&self, device: &Device, expected_data: &[u8]) -> bool {
let result = self
.retrieve_buffer(device, &self.buffer, self.buffer_aspect())
.iter()
.eq(expected_data.iter());
self.buffer.unmap();
result
}
}

View File

@ -3,6 +3,7 @@ use wasm_bindgen_test::wasm_bindgen_test_configure;
mod regression {
mod issue_3457;
}
mod buffer;
mod buffer_copy;
mod buffer_usages;
@ -16,6 +17,7 @@ mod poll;
mod queue_transfer;
mod resource_descriptor_accessor;
mod resource_error;
mod scissor_tests;
mod shader;
mod shader_primitive_index;
mod shader_view_format;

View File

@ -0,0 +1,163 @@
use wgpu_test::{image, initialize_test, TestParameters, TestingContext};
struct Rect {
x: u32,
y: u32,
width: u32,
height: u32,
}
const TEXTURE_HEIGHT: u32 = 2;
const TEXTURE_WIDTH: u32 = 2;
const BUFFER_SIZE: usize = (TEXTURE_WIDTH * TEXTURE_HEIGHT * 4) as usize;
fn scissor_test_impl(ctx: &TestingContext, scissor_rect: Rect, expected_data: [u8; BUFFER_SIZE]) {
let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
label: Some("Offscreen texture"),
size: wgpu::Extent3d {
width: TEXTURE_WIDTH,
height: TEXTURE_HEIGHT,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let shader = ctx
.device
.create_shader_module(wgpu::include_wgsl!("solid_white.wgsl"));
let pipeline = ctx
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Pipeline"),
layout: None,
vertex: wgpu::VertexState {
entry_point: "vs_main",
module: &shader,
buffers: &[],
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
entry_point: "fs_main",
module: &shader,
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview: None,
});
let readback_buffer = image::ReadbackBuffers::new(&ctx.device, &texture);
{
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Renderpass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: true,
},
})],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&pipeline);
render_pass.set_scissor_rect(
scissor_rect.x,
scissor_rect.y,
scissor_rect.width,
scissor_rect.height,
);
render_pass.draw(0..3, 0..1);
}
readback_buffer.copy_from(&ctx.device, &mut encoder, &texture);
ctx.queue.submit(Some(encoder.finish()));
}
assert!(readback_buffer.check_buffer_contents(&ctx.device, &expected_data));
}
#[test]
fn scissor_test_full_rect() {
initialize_test(TestParameters::default(), |ctx| {
scissor_test_impl(
&ctx,
Rect {
x: 0,
y: 0,
width: TEXTURE_WIDTH,
height: TEXTURE_HEIGHT,
},
[255; BUFFER_SIZE],
);
})
}
#[test]
fn scissor_test_empty_rect() {
initialize_test(TestParameters::default(), |ctx| {
scissor_test_impl(
&ctx,
Rect {
x: 0,
y: 0,
width: 0,
height: 0,
},
[0; BUFFER_SIZE],
);
})
}
#[test]
fn scissor_test_empty_rect_with_offset() {
initialize_test(TestParameters::default(), |ctx| {
scissor_test_impl(
&ctx,
Rect {
x: TEXTURE_WIDTH / 2,
y: TEXTURE_HEIGHT / 2,
width: 0,
height: 0,
},
[0; BUFFER_SIZE],
);
})
}
#[test]
fn scissor_test_custom_rect() {
let mut expected_result = [0; BUFFER_SIZE];
expected_result[((3 * BUFFER_SIZE) / 4)..][..BUFFER_SIZE / 4]
.copy_from_slice(&[255; BUFFER_SIZE / 4]);
initialize_test(TestParameters::default(), |ctx| {
scissor_test_impl(
&ctx,
Rect {
x: TEXTURE_WIDTH / 2,
y: TEXTURE_HEIGHT / 2,
width: TEXTURE_WIDTH / 2,
height: TEXTURE_HEIGHT / 2,
},
expected_result,
);
})
}

View File

@ -0,0 +1,33 @@
// meant to be called with 3 vertex indices: 0, 1, 2
// draws one large triangle over the clip space like this:
// (the asterisks represent the clip space bounds)
//-1,1 1,1
// ---------------------------------
// | * .
// | * .
// | * .
// | * .
// | * .
// | * .
// |***************
// | . 1,-1
// | .
// | .
// | .
// | .
// |.
@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> {
let x = i32(vertex_index) / 2;
let y = i32(vertex_index) & 1;
return vec4<f32>(
f32(x) * 4.0 - 1.0,
1.0 - f32(y) * 4.0,
0.0, 1.0
);
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0);
}

View File

@ -1,6 +1,7 @@
use wasm_bindgen_test::*;
use wgpu::util::{align_to, DeviceExt};
use wgpu_test::{initialize_test, TestParameters, TestingContext};
use wgpu::util::DeviceExt;
use wgpu_test::{image, initialize_test, TestParameters, TestingContext};
//
// These tests render two triangles to a 2x2 render target. The first triangle
@ -89,7 +90,7 @@ fn draw_indexed() {
fn pulling_common(
ctx: TestingContext,
expected: &[u8],
function: impl FnOnce(&mut wgpu::RenderPass<'_>),
draw_command: impl FnOnce(&mut wgpu::RenderPass<'_>),
) {
let shader = ctx
.device
@ -168,83 +169,31 @@ fn pulling_common(
});
let color_view = color_texture.create_view(&wgpu::TextureViewDescriptor::default());
let readback_buffer = image::ReadbackBuffers::new(&ctx.device, &color_texture);
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
store: true,
},
resolve_target: None,
view: &color_view,
})],
depth_stencil_attachment: None,
label: None,
});
rpass.set_pipeline(&pipeline);
rpass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
rpass.set_vertex_buffer(0, vertex_buffer.slice(..));
function(&mut rpass);
drop(rpass);
ctx.queue.submit(Some(encoder.finish()));
let data = capture_rgba_u8_texture(ctx, color_texture, texture_size);
assert_eq!(data, expected);
}
fn capture_rgba_u8_texture(
ctx: TestingContext,
color_texture: wgpu::Texture,
texture_size: wgpu::Extent3d,
) -> Vec<u8> {
let bytes_per_row = align_to(4 * texture_size.width, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
let buffer_size = bytes_per_row * texture_size.height;
let output_buffer = ctx
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
store: true,
},
resolve_target: None,
view: &color_view,
})],
depth_stencil_attachment: None,
label: None,
contents: &vec![0u8; buffer_size as usize],
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
});
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
encoder.copy_texture_to_buffer(
wgpu::ImageCopyTexture {
texture: &color_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::ImageCopyBuffer {
buffer: &output_buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: None,
},
},
texture_size,
);
rpass.set_pipeline(&pipeline);
rpass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
rpass.set_vertex_buffer(0, vertex_buffer.slice(..));
draw_command(&mut rpass);
}
readback_buffer.copy_from(&ctx.device, &mut encoder, &color_texture);
ctx.queue.submit(Some(encoder.finish()));
let slice = output_buffer.slice(..);
slice.map_async(wgpu::MapMode::Read, |_| ());
ctx.device.poll(wgpu::Maintain::Wait);
let data: Vec<u8> = bytemuck::cast_slice(&slice.get_mapped_range()).to_vec();
// Chunk rows from output buffer, take actual pixel
// bytes from each row and flatten into a vector.
data.chunks_exact(bytes_per_row as usize)
.flat_map(|x| x.iter().take(4 * texture_size.width as usize))
.copied()
.collect()
assert!(readback_buffer.check_buffer_contents(&ctx.device, expected));
}