diff --git a/Cargo.lock b/Cargo.lock index cfa89e71cb..6791be9c71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4385,6 +4385,19 @@ dependencies = [ "winit 0.29.3", ] +[[package]] +name = "wgpu-srgb-blend-example" +version = "0.18.0" +dependencies = [ + "bytemuck", + "glam", + "wasm-bindgen-test", + "wgpu", + "wgpu-example", + "wgpu-test", + "winit 0.29.2", +] + [[package]] name = "wgpu-stencil-triangle-example" version = "0.18.0" diff --git a/examples/common/src/framework.rs b/examples/common/src/framework.rs index 67da84a568..bced1eaa8b 100644 --- a/examples/common/src/framework.rs +++ b/examples/common/src/framework.rs @@ -30,6 +30,8 @@ pub enum ShaderStage { } pub trait Example: 'static + Sized { + const SRGB: bool = true; + fn optional_features() -> wgpu::Features { wgpu::Features::empty() } @@ -281,7 +283,12 @@ fn start( let mut config = surface .get_default_config(&adapter, size.width, size.height) .expect("Surface isn't supported by the adapter."); - let surface_view_format = config.format.add_srgb_suffix(); + let surface_view_format = if E::SRGB { + config.format.add_srgb_suffix() + } else { + config.format.remove_srgb_suffix() + }; + config.format = surface_view_format; config.view_formats.push(surface_view_format); surface.configure(&device, &config); @@ -474,6 +481,11 @@ impl From> for GpuT params.base_test_parameters.clone().features(features) }) .run_async(move |ctx| async move { + let format = if E::SRGB { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }; let dst_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: Some("destination"), size: wgpu::Extent3d { @@ -484,7 +496,7 @@ impl From> for GpuT mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); @@ -501,12 +513,12 @@ impl From> for GpuT let mut example = E::init( &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format, width: params.width, height: params.height, present_mode: wgpu::PresentMode::Fifo, alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![wgpu::TextureFormat::Rgba8UnormSrgb], + view_formats: vec![format], }, &ctx.adapter, &ctx.device, diff --git a/examples/srgb-blend/Cargo.toml b/examples/srgb-blend/Cargo.toml new file mode 100644 index 0000000000..d21fbcee0f --- /dev/null +++ b/examples/srgb-blend/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "wgpu-srgb-blend-example" +version.workspace = true +license.workspace = true +edition.workspace = true +description = "wgpu sRGB blend example" +publish = false + +[[bin]] +name = "srgb-blend" +path = "src/main.rs" +harness = false + +[dependencies] +bytemuck.workspace = true +glam.workspace = true +wasm-bindgen-test.workspace = true +wgpu-example.workspace = true +wgpu.workspace = true +winit.workspace = true + +[dev-dependencies] +wgpu-test.workspace = true diff --git a/examples/srgb-blend/README.md b/examples/srgb-blend/README.md new file mode 100644 index 0000000000..abf8b23ebf --- /dev/null +++ b/examples/srgb-blend/README.md @@ -0,0 +1,23 @@ +# cube + +This example shows blending in sRGB or linear space. + +## To Run + +``` +cargo run --bin cube -- linear +``` + +``` +cargo run --bin cube +``` + +## Screenshots + +Blending in linear space: + +![sRGB blend example](./screenshot-srgb.png) + +Blending in sRGB space: + +![sRGB blend example](./screenshot-linear.png) diff --git a/examples/srgb-blend/screenshot-linear.png b/examples/srgb-blend/screenshot-linear.png new file mode 100644 index 0000000000..34eafd05f5 Binary files /dev/null and b/examples/srgb-blend/screenshot-linear.png differ diff --git a/examples/srgb-blend/screenshot-srgb.png b/examples/srgb-blend/screenshot-srgb.png new file mode 100644 index 0000000000..ed64cf09ab Binary files /dev/null and b/examples/srgb-blend/screenshot-srgb.png differ diff --git a/examples/srgb-blend/src/main.rs b/examples/srgb-blend/src/main.rs new file mode 100644 index 0000000000..4c328facc1 --- /dev/null +++ b/examples/srgb-blend/src/main.rs @@ -0,0 +1,256 @@ +use bytemuck::{Pod, Zeroable}; +use std::{borrow::Cow, mem}; +use wgpu::util::DeviceExt; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Vertex { + _pos: [f32; 4], + _color: [f32; 4], +} + +fn vertex(pos: [i8; 2], _color: [f32; 4], offset: f32) -> Vertex { + let scale = 0.5; + Vertex { + _pos: [ + (pos[0] as f32 + offset) * scale, + (pos[1] as f32 + offset) * scale, + 0.0, + 1.0, + ], + _color, + } +} + +fn quad(vertices: &mut Vec, indices: &mut Vec, color: [f32; 4], offset: f32) { + let base = vertices.len() as u16; + + vertices.extend_from_slice(&[ + vertex([-1, -1], color, offset), + vertex([1, -1], color, offset), + vertex([1, 1], color, offset), + vertex([-1, 1], color, offset), + ]); + + indices.extend([0, 1, 2, 2, 3, 0].iter().map(|i| base + *i)); +} + +fn create_vertices() -> (Vec, Vec) { + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + + let red = [1.0, 0.0, 0.0, 0.5]; + let blue = [0.0, 0.0, 1.0, 0.5]; + + quad(&mut vertices, &mut indices, red, 0.5); + quad(&mut vertices, &mut indices, blue, -0.5); + + (vertices, indices) +} + +struct Example { + vertex_buf: wgpu::Buffer, + index_buf: wgpu::Buffer, + index_count: usize, + bind_group: wgpu::BindGroup, + pipeline: wgpu::RenderPipeline, +} + +impl wgpu_example::framework::Example for Example { + const SRGB: bool = SRGB; + + fn optional_features() -> wgpu::Features { + wgpu::Features::POLYGON_MODE_LINE + } + + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + _queue: &wgpu::Queue, + ) -> Self { + // Create the vertex and index buffers + let vertex_size = mem::size_of::(); + let (vertex_data, index_data) = create_vertices(); + + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX, + }); + + // Create pipeline layout + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + // Create bind group + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[], + label: None, + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let vertex_buffers = [wgpu::VertexBufferLayout { + array_stride: vertex_size as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x4, + offset: 0, + shader_location: 0, + }, + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x4, + offset: 4 * 4, + shader_location: 1, + }, + ], + }]; + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &vertex_buffers, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.view_formats[0], + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + cull_mode: Some(wgpu::Face::Back), + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + // Done + Example { + vertex_buf, + index_buf, + index_count: index_data.len(), + bind_group, + pipeline, + } + } + + fn update(&mut self, _event: winit::event::WindowEvent) { + //empty + } + + fn resize( + &mut self, + _config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + _queue: &wgpu::Queue, + ) { + } + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + device.push_error_scope(wgpu::ErrorFilter::Validation); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.push_debug_group("Prepare data for draw."); + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.bind_group, &[]); + rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16); + rpass.set_vertex_buffer(0, self.vertex_buf.slice(..)); + rpass.pop_debug_group(); + rpass.insert_debug_marker("Draw!"); + rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); + } + + queue.submit(Some(encoder.finish())); + } +} + +#[cfg(not(test))] +fn main() { + let mut args = std::env::args(); + args.next(); + if Some("linear") == args.next().as_deref() { + wgpu_example::framework::run::>("srgb-blend-linear"); + } else { + wgpu_example::framework::run::>("srgb-blend-srg"); + } +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_SRGB: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "srgb-blend-srg", + // Generated on WARP/Windows + image_path: "/examples/srgb-blend/screenshot-srgb.png", + width: 192, + height: 192, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.04)], + _phantom: std::marker::PhantomData::>, + }; + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_LINEAR: wgpu_example::framework::ExampleTestParams = + wgpu_example::framework::ExampleTestParams { + name: "srgb-blend-linear", + // Generated on WARP/Windows + image_path: "/examples/srgb-blend/screenshot-linear.png", + width: 192, + height: 192, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.04)], + _phantom: std::marker::PhantomData::>, + }; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/srgb-blend/src/shader.wgsl b/examples/srgb-blend/src/shader.wgsl new file mode 100644 index 0000000000..766b229e7b --- /dev/null +++ b/examples/srgb-blend/src/shader.wgsl @@ -0,0 +1,24 @@ +struct VertexOutput { + @location(0) color: vec4, + @builtin(position) position: vec4, +}; + +@vertex +fn vs_main( + @location(0) position: vec4, + @location(1) color: vec4, +) -> VertexOutput { + var result: VertexOutput; + result.color = color; + result.position = position; + return result; +} + +@group(0) +@binding(1) +var color: vec4; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + return vertex.color; +}