From 14f93b4c4aa935abac94abf03b3145d3240f1ef4 Mon Sep 17 00:00:00 2001 From: Brad Werth Date: Tue, 25 Jun 2024 15:39:41 -0700 Subject: [PATCH] Stub in new test. --- tests/tests/root.rs | 1 + tests/tests/vertex_formats/draw.vert.wgsl | 295 ++++++++++++++++++++ tests/tests/vertex_formats/mod.rs | 322 ++++++++++++++++++++++ 3 files changed, 618 insertions(+) create mode 100644 tests/tests/vertex_formats/draw.vert.wgsl create mode 100644 tests/tests/vertex_formats/mod.rs diff --git a/tests/tests/root.rs b/tests/tests/root.rs index 1cb5b56c7c0..554a434ea1a 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -40,6 +40,7 @@ mod texture_bounds; mod texture_view_creation; mod transfer; mod vertex_indices; +mod vertex_formats; mod write_texture; mod zero_init_texture_after_discard; diff --git a/tests/tests/vertex_formats/draw.vert.wgsl b/tests/tests/vertex_formats/draw.vert.wgsl new file mode 100644 index 00000000000..ed5e0a79635 --- /dev/null +++ b/tests/tests/vertex_formats/draw.vert.wgsl @@ -0,0 +1,295 @@ +@group(0) @binding(0) +var checksums: array; + +const index_uint = 0u; +const index_sint = 1u; +const index_unorm = 2u; +const index_snorm = 3u; +const index_float16 = 4u; +const index_float32 = 5u; + +fn init_checksums() { + checksums[index_uint] = 1.0; + checksums[index_sint] = 2.0; + checksums[index_unorm] = 3.0; + checksums[index_snorm] = 4.0; + checksums[index_float16] = 5.0; + checksums[index_float32] = 6.0; +} + +// Break down the 31 vertex formats specified at +// https://gpuweb.github.io/gpuweb/#vertex-formats into blocks +// of 8, to keep under the limits of max locations. Each +// AttributeBlockX structure will get a corresponding +// vertex_block_X function to process its attributes into +// values written to the checksums buffer. + +struct AttributeBlock0 { + @location(0) unorm8x2: vec2, + @location(1) snorm8x2: vec2, + @location(2) unorm8x4: vec4, + @location(3) snorm8x4: vec4, + @location(4) unorm16x2: vec2, + @location(5) snorm16x2: vec2, + @location(6) unorm16x4: vec4, + @location(7) snorm16x4: vec4, +} + +@vertex +fn vertex_block_0(v_in: AttributeBlock0) -> @builtin(position) vec4 +{ + init_checksums(); + + // Accumulate all unorm into one checksum value. + var all_unorm: f32 = 0.0; + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x2.x); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x2.y); + + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4.x); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4.y); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4.z); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4.w); + + all_unorm = accumulate_unorm(all_unorm, v_in.unorm16x2.x); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm16x2.y); + + all_unorm = accumulate_unorm(all_unorm, v_in.unorm16x4.x); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm16x4.y); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm16x4.z); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm16x4.w); + + + // Accumulate all snorm into one checksum value. + var all_snorm: f32 = 0.0; + all_snorm = accumulate_snorm(all_snorm, v_in.snorm8x2.x); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm8x2.y); + + all_snorm = accumulate_snorm(all_snorm, v_in.snorm8x4.x); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm8x4.y); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm8x4.z); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm8x4.w); + + all_snorm = accumulate_snorm(all_snorm, v_in.snorm16x2.x); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm16x2.y); + + all_snorm = accumulate_snorm(all_snorm, v_in.snorm16x4.x); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm16x4.y); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm16x4.z); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm16x4.w); + + + checksums[index_unorm] = all_unorm; + checksums[index_snorm] = all_snorm; + + return vec4(0.0); +} + +struct AttributeBlock1 { + @location(0) uint8x2: vec2, + @location(1) sint8x2: vec2, + @location(2) uint8x4: vec4, + @location(3) sint8x4: vec4, + @location(4) uint16x2: vec2, + @location(5) uint16x4: vec4, + @location(6) sint16x2: vec2, + @location(7) sint16x4: vec4, +} + +@vertex +fn vertex_block_1(v_in: AttributeBlock1) -> @builtin(position) vec4 +{ + init_checksums(); + + // Accumulate all uint into one checksum value. + var all_uint: u32 = 0; + all_uint = accumulate_uint(all_uint, v_in.uint8x2.x); + all_uint = accumulate_uint(all_uint, v_in.uint8x2.y); + + all_uint = accumulate_uint(all_uint, v_in.uint8x4.x); + all_uint = accumulate_uint(all_uint, v_in.uint8x4.y); + all_uint = accumulate_uint(all_uint, v_in.uint8x4.z); + all_uint = accumulate_uint(all_uint, v_in.uint8x4.w); + + all_uint = accumulate_uint(all_uint, v_in.uint16x2.x); + all_uint = accumulate_uint(all_uint, v_in.uint16x2.y); + + all_uint = accumulate_uint(all_uint, v_in.uint16x4.x); + all_uint = accumulate_uint(all_uint, v_in.uint16x4.y); + all_uint = accumulate_uint(all_uint, v_in.uint16x4.z); + all_uint = accumulate_uint(all_uint, v_in.uint16x4.w); + + + // Accumulate all sint into one checksum value. + var all_sint: i32 = 0; + all_sint = accumulate_sint(all_sint, v_in.sint8x2.x); + all_sint = accumulate_sint(all_sint, v_in.sint8x2.y); + + all_sint = accumulate_sint(all_sint, v_in.sint8x4.x); + all_sint = accumulate_sint(all_sint, v_in.sint8x4.y); + all_sint = accumulate_sint(all_sint, v_in.sint8x4.z); + all_sint = accumulate_sint(all_sint, v_in.sint8x4.w); + + all_sint = accumulate_sint(all_sint, v_in.sint16x2.x); + all_sint = accumulate_sint(all_sint, v_in.sint16x2.y); + + all_sint = accumulate_sint(all_sint, v_in.sint16x4.x); + all_sint = accumulate_sint(all_sint, v_in.sint16x4.y); + all_sint = accumulate_sint(all_sint, v_in.sint16x4.z); + all_sint = accumulate_sint(all_sint, v_in.sint16x4.w); + + + checksums[index_uint] = f32(all_uint); + checksums[index_sint] = f32(all_sint); + + return vec4(0.0); +} + +struct AttributeBlock2{ + @location(0) uint32: u32, + @location(1) uint32x2: vec2, + @location(2) uint32x3: vec3, + @location(3) uint32x4: vec4, + @location(4) sint32: i32, + @location(5) sint32x2: vec2, + @location(6) sint32x3: vec3, + @location(7) sint32x4: vec4, +} + +@vertex +fn vertex_block_2(v_in: AttributeBlock2) -> @builtin(position) vec4 +{ + init_checksums(); + + // Accumulate all uint into one checksum value. + var all_uint: u32 = 0; + all_uint = accumulate_uint(all_uint, v_in.uint32); + + all_uint = accumulate_uint(all_uint, v_in.uint32x2.x); + all_uint = accumulate_uint(all_uint, v_in.uint32x2.y); + + all_uint = accumulate_uint(all_uint, v_in.uint32x3.x); + all_uint = accumulate_uint(all_uint, v_in.uint32x3.y); + all_uint = accumulate_uint(all_uint, v_in.uint32x3.z); + + all_uint = accumulate_uint(all_uint, v_in.uint32x4.x); + all_uint = accumulate_uint(all_uint, v_in.uint32x4.y); + all_uint = accumulate_uint(all_uint, v_in.uint32x4.z); + all_uint = accumulate_uint(all_uint, v_in.uint32x4.w); + + + // Accumulate all sint into one checksum value. + var all_sint: i32 = 0; + all_sint = accumulate_sint(all_sint, v_in.sint32); + + all_sint = accumulate_sint(all_sint, v_in.sint32x2.x); + all_sint = accumulate_sint(all_sint, v_in.sint32x2.y); + + all_sint = accumulate_sint(all_sint, v_in.sint32x3.x); + all_sint = accumulate_sint(all_sint, v_in.sint32x3.y); + all_sint = accumulate_sint(all_sint, v_in.sint32x3.z); + + all_sint = accumulate_sint(all_sint, v_in.sint32x4.x); + all_sint = accumulate_sint(all_sint, v_in.sint32x4.y); + all_sint = accumulate_sint(all_sint, v_in.sint32x4.z); + all_sint = accumulate_sint(all_sint, v_in.sint32x4.w); + + + checksums[index_uint] = f32(all_uint); + checksums[index_sint] = f32(all_sint); + + return vec4(0.0); +} + +struct AttributeBlock3{ + @location(0) unorm10_10_10_2: vec4, + // TODO(SHADER_F16) + /* + @location(1) float16x2: vec2, + @location(2) float16x4: vec4, + */ + @location(3) float32: f32, + @location(4) float32x2: vec2, + @location(5) float32x3: vec3, + @location(6) float32x4: vec4, +} + +@vertex +fn vertex_block_3(v_in: AttributeBlock3) -> @builtin(position) vec4 +{ + init_checksums(); + + // Accumulate all unorm into one checksum value. + var all_unorm: f32 = 0.0; + all_unorm = accumulate_unorm(all_unorm, v_in.unorm10_10_10_2.x); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm10_10_10_2.y); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm10_10_10_2.z); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm10_10_10_2.w); + + + // Accumulate all float16 into one checksum value. + // TODO(SHADER_F16) + /* + var all_float16: f16 = 0.0; + all_float16 = accumulate_float16(all_float16, v_in.float16x2.x); + all_float16 = accumulate_float16(all_float16, v_in.float16x2.y); + */ + + + // Accumulate all float32 into one checksum value. + var all_float32: f32 = 0.0; + all_float32 = accumulate_float32(all_float32, v_in.float32); + + all_float32 = accumulate_float32(all_float32, v_in.float32x2.x); + all_float32 = accumulate_float32(all_float32, v_in.float32x2.y); + + all_float32 = accumulate_float32(all_float32, v_in.float32x3.x); + all_float32 = accumulate_float32(all_float32, v_in.float32x3.y); + all_float32 = accumulate_float32(all_float32, v_in.float32x3.z); + + all_float32 = accumulate_float32(all_float32, v_in.float32x4.x); + all_float32 = accumulate_float32(all_float32, v_in.float32x4.y); + all_float32 = accumulate_float32(all_float32, v_in.float32x4.z); + all_float32 = accumulate_float32(all_float32, v_in.float32x4.w); + + + checksums[index_unorm] = all_unorm; + // TODO(SHADER_F16) + /* + checksums[index_float16] = f32(all_float16); + */ + checksums[index_float32] = all_float32; + + return vec4(0.0); +} + +fn accumulate_uint(accum: u32, val: u32) -> u32 { + return accum + val; +} + +fn accumulate_sint(accum: i32, val: i32) -> i32 { + return accum + val; +} + +fn accumulate_unorm(accum: f32, val: f32) -> f32 { + return accum + val; +} + +fn accumulate_snorm(accum: f32, val: f32) -> f32 { + return accum + val; +} + +// TODO(SHADER_F16) +/* +fn accumulate_float16(accum: f16, val: f16) -> f16 { + return accum + val; +} +*/ + +fn accumulate_float32(accum: f32, val: f32) -> f32 { + return accum + val; +} + +@fragment +fn fragment_main() -> @location(0) vec4 { + return vec4(0.0); +} diff --git a/tests/tests/vertex_formats/mod.rs b/tests/tests/vertex_formats/mod.rs new file mode 100644 index 00000000000..3c5a67ab16c --- /dev/null +++ b/tests/tests/vertex_formats/mod.rs @@ -0,0 +1,322 @@ +//! Tests that vertex formats pass through to vertex shaders accurately. + +use std::{num::NonZeroU64, ops::Range}; + +use wgpu::util::{BufferInitDescriptor, DeviceExt, RenderEncoder}; + +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +/// Generic struct representing a draw call +struct Draw { + vertex: Range, +} + +impl Draw { + /// Directly execute the draw call + fn execute(&self, rpass: &mut dyn RenderEncoder<'_>) { + rpass.draw(self.vertex.clone(), 0..1); + } +} + +#[derive(Debug, Copy, Clone)] +enum TestCase { + /// A single draw call with 1 vertex + DrawOneVertex, +} + +impl TestCase { + const ARRAY: [Self; 1] = [ + Self::DrawOneVertex, + ]; + + // Get the draw calls for this test case + fn draws(&self) -> &'static [Draw] { + match self { + TestCase::DrawOneVertex => &[Draw { + vertex: 0..1, + }], + } + } +} + +struct Test { + case: TestCase, +} + +impl Test { + /// Get the expected result from this test, taking into account + /// the various features and capabilities that may be missing. + fn expectation(&self) -> &'static [f32] { + match self.case { + // Results are 10 floats: + // 0: checksum_uint + // 1: checksum_sint + // 2: checksum_unorm + // 3: checksum_snorm + // 4: checksum_float16 + // 5: checksum_float32 + TestCase::DrawOneVertex => { + &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + } + } + } +} + +async fn vertex_index_common(ctx: TestingContext) { + let shader = ctx + .device + .create_shader_module(wgpu::include_wgsl!("draw.vert.wgsl")); + + let bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(4), + }, + visibility: wgpu::ShaderStages::VERTEX, + count: None, + }], + }); + + let ppl = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); + + + let attributes_block_0 = &wgpu::vertex_attr_array![ + 0 => Unorm8x2, + 1 => Snorm8x2, + 2 => Unorm8x4, + 3 => Snorm8x4, + 4 => Unorm16x2, + 5 => Snorm16x2, + 6 => Unorm16x4, + 7 => Snorm16x4, + ]; + + let buffer_block_0 = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: Some("buffer_block_0"), + contents: bytemuck::cast_slice(&[ + 0u8, 0u8, // Unorm8x2 + 0u8, 0u8, // Snorm8x2 + 0u8, 0u8, 0u8, 0u8, // Unorm8x4 + 0u8, 0u8, 0u8, 0u8, // Snorm8x4 + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, // Unorm16x2 + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, // Snorm16x2 + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, // Unorm16x4 + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, // Snorm16x4 + ]), + usage: wgpu::BufferUsages::VERTEX, + }); + + let pipeline_desc_block_0 = wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&ppl), + vertex: wgpu::VertexState { + buffers: &[ + wgpu::VertexBufferLayout { + array_stride: 0, // Calculate, please! + step_mode: wgpu::VertexStepMode::Vertex, + attributes: attributes_block_0, + }, + ], + module: &shader, + entry_point: "vertex_block_0", + compilation_options: Default::default(), + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fragment_main", + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + cache: None, + }; + + let buffer_pipeline_block_0 = ctx.device.create_render_pipeline(&pipeline_desc_block_0); + + let _attributes_block_1 = &wgpu::vertex_attr_array![ + 0 => Uint8x2, + 1 => Sint8x2, + 2 => Uint8x4, + 3 => Sint8x4, + 4 => Uint16x2, + 5 => Uint16x4, + 6 => Sint16x2, + 7 => Sint16x4, + ]; + + let _attributes_block_2 = &wgpu::vertex_attr_array![ + 0 => Uint32, + 1 => Uint32x2, + 2 => Uint32x3, + 3 => Uint32x4, + 4 => Sint32, + 5 => Sint32x2, + 6 => Sint32x3, + 7 => Sint32x4, + ]; + + let _attributes_block_3 = &wgpu::vertex_attr_array![ + 0 => Unorm10_10_10_2, + 1 => Float16x2, + 2 => Float16x4, + 3 => Float32, + 4 => Float32x2, + 5 => Float32x3, + 6 => Float32x4, + ]; + + let dummy = ctx + .device + .create_texture_with_data( + &ctx.queue, + &wgpu::TextureDescriptor { + label: Some("dummy"), + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &[0, 0, 0, 1], + ) + .create_view(&wgpu::TextureViewDescriptor::default()); + + + let mut tests = Vec::with_capacity(1); + for case in TestCase::ARRAY { + tests.push(Test { + case, + }) + } + + let mut failed = false; + for test in tests { + let pipeline = &buffer_pipeline_block_0; + + let expected = test.expectation(); + + let buffer_size = 4 * expected.len() as u64; + let cpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: buffer_size, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let gpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: buffer_size, + usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::STORAGE, + mapped_at_creation: false, + }); + + let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: gpu_buffer.as_entire_binding(), + }], + }); + + let mut encoder1 = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut rpass = encoder1.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations::default(), + resolve_target: None, + view: &dummy, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(pipeline); + rpass.set_bind_group(0, &bg, &[]); + + rpass.set_vertex_buffer(0, buffer_block_0.slice(..)); + + let draws = test.case.draws(); + + for draw in draws { + draw.execute(&mut rpass); + } + + drop(rpass); + + let mut encoder2 = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + encoder2.copy_buffer_to_buffer(&gpu_buffer, 0, &cpu_buffer, 0, buffer_size); + + // See https://github.com/gfx-rs/wgpu/issues/4732 for why this is split between two submissions + // with a hard wait in between. + ctx.queue.submit([encoder1.finish()]); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + ctx.queue.submit([encoder2.finish()]); + let slice = cpu_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| ()); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + let data: Vec = bytemuck::cast_slice(&slice.get_mapped_range()).to_vec(); + + let case_name = format!( + "Case {:?}", + test.case + ); + if data != expected { + eprintln!( + "Failed: Got: {:?} Expected: {:?} - {case_name}", + data, expected, + ); + failed = true; + } else { + eprintln!("Passed: {case_name}"); + } + } + + assert!(!failed); +} + +#[gpu_test] +static VERTEX_FORMATS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::VERTEX_WRITABLE_STORAGE) + ) + .run_async(vertex_index_common);