diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 5ab713ddd..85dae4959 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -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"] } diff --git a/tests/src/image.rs b/tests/src/image.rs index 9009909d6..00aa78f66 100644 --- a/tests/src/image.rs +++ b/tests/src/image.rs @@ -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, width: u32, height: u32) -> Option> { @@ -149,7 +150,7 @@ impl ComparisonType { pub fn compare_image_output( path: impl AsRef + AsRef, - 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, + ) -> Vec { + 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 { + 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| -> 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 } } diff --git a/tests/tests/root.rs b/tests/tests/root.rs index 27422ffb9..a84eab3fb 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -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; diff --git a/tests/tests/scissor_tests/mod.rs b/tests/tests/scissor_tests/mod.rs new file mode 100644 index 000000000..6855b410b --- /dev/null +++ b/tests/tests/scissor_tests/mod.rs @@ -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, + ); + }) +} diff --git a/tests/tests/scissor_tests/solid_white.wgsl b/tests/tests/scissor_tests/solid_white.wgsl new file mode 100644 index 000000000..19b06543b --- /dev/null +++ b/tests/tests/scissor_tests/solid_white.wgsl @@ -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 { + let x = i32(vertex_index) / 2; + let y = i32(vertex_index) & 1; + return vec4( + f32(x) * 4.0 - 1.0, + 1.0 - f32(y) * 4.0, + 0.0, 1.0 + ); +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return vec4(1.0); +} diff --git a/tests/tests/shader_primitive_index/mod.rs b/tests/tests/shader_primitive_index/mod.rs index fafd4b682..68daae873 100644 --- a/tests/tests/shader_primitive_index/mod.rs +++ b/tests/tests/shader_primitive_index/mod.rs @@ -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 { - 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 = 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)); }