From 434f144fb97e6c1ce31397c0465649539972917c Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Tue, 14 May 2024 19:18:45 +0200 Subject: [PATCH 1/8] introduce dithering to reduce banding --- crates/egui-wgpu/src/egui.wgsl | 36 ++++++++++++++++++++++++++++---- crates/egui-wgpu/src/renderer.rs | 10 ++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index 552bcbbf391..52924127fa4 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -7,13 +7,36 @@ struct VertexOutput { }; struct Locals { - screen_size: vec2, + screen_size: vec2, // in points + pixels_per_point: f32, // Uniform buffers need to be at least 16 bytes in WebGL. // See https://github.com/gfx-rs/wgpu/issues/2072 - _padding: vec2, + _padding: u32, }; @group(0) @binding(0) var r_locals: Locals; + +// ----------------------------------------------- +// Adapted from +// https://www.shadertoy.com/view/llVGzG +// Originally presented in: +// Jimenez 2014, "Next Generation Post-Processing in Call of Duty" +// +// A good overview can be found in +// https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/ +// via https://github.com/rerun-io/rerun/ +fn interleaved_gradient_noise(n: vec2) -> f32 { + let f = 0.06711056 * n.x + 0.00583715 * n.y; + return fract(52.9829189 * fract(f)); +} + +fn dither_interleaved(rgb: vec3, levels: f32, frag_coord: vec4) -> vec3 { + var noise = interleaved_gradient_noise(frag_coord.xy); + // scale down the noise slightly to ensure flat colors aren't getting dithered + noise = (noise - 0.5) * 0.95; + return rgb + noise / (levels - 1.0); +} + // 0-1 linear from 0-1 sRGB gamma fn linear_from_gamma_rgb(srgb: vec3) -> vec3 { let cutoff = srgb < vec3(0.04045); @@ -78,7 +101,11 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); let tex_gamma = gamma_from_linear_rgba(tex_linear); let out_color_gamma = in.color * tex_gamma; - return vec4(linear_from_gamma_rgb(out_color_gamma.rgb), out_color_gamma.a); + let out_color_linear = linear_from_gamma_rgb(out_color_gamma.rgb); + // Dither the float color down to eight bits to reduce banding. + // This step is optional for egui backends. + let out_color_dithered = dither_interleaved(out_color_linear, 256.0, in.position); + return vec4(out_color_dithered, out_color_gamma.a); } @fragment @@ -87,5 +114,6 @@ fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); let tex_gamma = gamma_from_linear_rgba(tex_linear); let out_color_gamma = in.color * tex_gamma; - return out_color_gamma; + let out_color_dithered = vec4(dither_interleaved(out_color_gamma.rgb, 256.0, in.position * r_locals.pixels_per_point), out_color_gamma.a); + return out_color_dithered; } diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 18e13a89caf..6cc386769d6 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -133,9 +133,10 @@ impl ScreenDescriptor { #[repr(C)] struct UniformBuffer { screen_size_in_points: [f32; 2], + pixels_per_point: f32, // Uniform buffers need to be at least 16 bytes in WebGL. // See https://github.com/gfx-rs/wgpu/issues/2072 - _padding: [u32; 2], + _padding: u32, } impl PartialEq for UniformBuffer { @@ -201,6 +202,7 @@ impl Renderer { label: Some("egui_uniform_buffer"), contents: bytemuck::cast_slice(&[UniformBuffer { screen_size_in_points: [0.0, 0.0], + pixels_per_point: 0., _padding: Default::default(), }]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, @@ -212,7 +214,7 @@ impl Renderer { label: Some("egui_uniform_bind_group_layout"), entries: &[wgpu::BindGroupLayoutEntry { binding: 0, - visibility: wgpu::ShaderStages::VERTEX, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { has_dynamic_offset: false, min_binding_size: NonZeroU64::new(std::mem::size_of::() as _), @@ -364,7 +366,8 @@ impl Renderer { // Buffers on wgpu are zero initialized, so this is indeed its current state! previous_uniform_buffer_content: UniformBuffer { screen_size_in_points: [0.0, 0.0], - _padding: [0, 0], + pixels_per_point: 0., + _padding: 0, }, uniform_bind_group, texture_bind_group_layout, @@ -781,6 +784,7 @@ impl Renderer { let uniform_buffer_content = UniformBuffer { screen_size_in_points, + pixels_per_point: screen_descriptor.pixels_per_point, _padding: Default::default(), }; if uniform_buffer_content != self.previous_uniform_buffer_content { From 27288024c9573025ce38877fd4f5fc8086871680 Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Tue, 14 May 2024 19:37:51 +0200 Subject: [PATCH 2/8] introduce dithering in egui_glow --- crates/egui_glow/src/shader/fragment.glsl | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/egui_glow/src/shader/fragment.glsl b/crates/egui_glow/src/shader/fragment.glsl index c1fc1740148..d77861f9284 100644 --- a/crates/egui_glow/src/shader/fragment.glsl +++ b/crates/egui_glow/src/shader/fragment.glsl @@ -16,6 +16,27 @@ uniform sampler2D u_sampler; varying vec2 v_tc; #endif +// ----------------------------------------------- +// Adapted from +// https://www.shadertoy.com/view/llVGzG +// Originally presented in: +// Jimenez 2014, "Next Generation Post-Processing in Call of Duty" +// +// A good overview can be found in +// https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/ +// via https://github.com/rerun-io/rerun/ +float interleaved_gradient_noise(vec2 n) { + float f = 0.06711056 * n.x + 0.00583715 * n.y; + return fract(52.9829189 * fract(f)); +} + +vec3 dither_interleaved(vec3 rgb, float levels) { + float noise = interleaved_gradient_noise(gl_FragCoord.xy); + // scale down the noise slightly to ensure flat colors aren't getting dithered + noise = (noise - 0.5) * 0.95; + return rgb + noise / (levels - 1.0); +} + // 0-1 sRGB gamma from 0-1 linear vec3 srgb_gamma_from_linear(vec3 rgb) { bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); @@ -37,5 +58,9 @@ void main() { #endif // We multiply the colors in gamma space, because that's the only way to get text to look right. - gl_FragColor = v_rgba_in_gamma * texture_in_gamma; + vec4 frag_color_gamma = v_rgba_in_gamma * texture_in_gamma; + + // Dither the float color down to eight bits to reduce banding. + // This step is optional for egui backends. + gl_FragColor = vec4(dither_interleaved(frag_color_gamma.rgb, 256.), frag_color_gamma.a); } From 026b8d08d61c17721e49e4b260a896f10ec18cd4 Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Thu, 30 May 2024 18:52:55 +0000 Subject: [PATCH 3/8] Make dithering configurable --- crates/eframe/src/epi.rs | 12 ++++++++++++ crates/eframe/src/native/glow_integration.rs | 2 +- crates/eframe/src/native/wgpu_integration.rs | 1 + crates/eframe/src/web/web_painter_glow.rs | 2 +- crates/eframe/src/web/web_painter_wgpu.rs | 14 ++++++++++---- crates/egui-wgpu/src/egui.wgsl | 15 +++++++++++++-- crates/egui-wgpu/src/lib.rs | 9 ++++++++- crates/egui-wgpu/src/renderer.rs | 12 ++++++++---- crates/egui-wgpu/src/winit.rs | 4 ++++ crates/egui_glow/examples/pure_glow.rs | 2 +- crates/egui_glow/src/painter.rs | 4 +++- crates/egui_glow/src/shader/fragment.glsl | 5 ++++- crates/egui_glow/src/winit.rs | 3 ++- 13 files changed, 68 insertions(+), 17 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 4a05c97aaf2..ac8ba89287e 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -369,6 +369,10 @@ pub struct NativeOptions { /// The folder where `eframe` will store the app state. If not set, eframe will get the paths /// from [directories]. pub persistence_path: Option, + + /// Controls whether colors should be dithered to minimize banding. + /// Default to true. + pub dithering: bool, } #[cfg(not(target_arch = "wasm32"))] @@ -429,6 +433,8 @@ impl Default for NativeOptions { persist_window: true, persistence_path: None, + + dithering: true, } } } @@ -466,6 +472,10 @@ pub struct WebOptions { /// Configures wgpu instance/device/adapter/surface creation and renderloop. #[cfg(feature = "wgpu")] pub wgpu_options: egui_wgpu::WgpuConfiguration, + + /// Controls whether colors should be dithered to minimize banding. + /// Defaults to true. + pub dithering: bool, } #[cfg(target_arch = "wasm32")] @@ -481,6 +491,8 @@ impl Default for WebOptions { #[cfg(feature = "wgpu")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), + + dithering: true, } } } diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 13576bdbd2d..75a76b7e2db 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -184,7 +184,7 @@ impl GlowWinitApp { })) }; - let painter = egui_glow::Painter::new(gl, "", native_options.shader_version)?; + let painter = egui_glow::Painter::new(gl, "", native_options.shader_version, true)?; Ok((glutin_window_context, painter)) } diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 5b9785fc9f5..fc3f6e9e134 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -194,6 +194,7 @@ impl WgpuWinitApp { self.native_options.stencil_buffer, ), self.native_options.viewport.transparent.unwrap_or(false), + self.native_options.dithering, ); let window = Arc::new(window); diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index b54f6f64423..29e23fa2fa8 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -26,7 +26,7 @@ impl WebPainterGlow { #[allow(clippy::arc_with_non_send_sync)] let gl = std::sync::Arc::new(gl); - let painter = egui_glow::Painter::new(gl, shader_prefix, None) + let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering) .map_err(|err| format!("Error starting glow painter: {err}"))?; Ok(Self { canvas, painter }) diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index de5ba601111..1da12b0a083 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -169,10 +169,16 @@ impl WebPainterWgpu { let depth_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0); - let render_state = - RenderState::create(&options.wgpu_options, &instance, &surface, depth_format, 1) - .await - .map_err(|err| err.to_string())?; + let render_state = RenderState::create( + &options.wgpu_options, + &instance, + &surface, + depth_format, + 1, + options.dithering, + ) + .await + .map_err(|err| err.to_string())?; let surface_configuration = wgpu::SurfaceConfiguration { format: render_state.target_format, diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index 52924127fa4..9acae8b9f01 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -8,7 +8,7 @@ struct VertexOutput { struct Locals { screen_size: vec2, // in points - pixels_per_point: f32, + dithering: u32, // Uniform buffers need to be at least 16 bytes in WebGL. // See https://github.com/gfx-rs/wgpu/issues/2072 _padding: u32, @@ -104,6 +104,9 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { let out_color_linear = linear_from_gamma_rgb(out_color_gamma.rgb); // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. + if r_locals.dithering == 0 { + return vec4(out_color_linear, out_color_gamma.a); + } let out_color_dithered = dither_interleaved(out_color_linear, 256.0, in.position); return vec4(out_color_dithered, out_color_gamma.a); } @@ -114,6 +117,14 @@ fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); let tex_gamma = gamma_from_linear_rgba(tex_linear); let out_color_gamma = in.color * tex_gamma; - let out_color_dithered = vec4(dither_interleaved(out_color_gamma.rgb, 256.0, in.position * r_locals.pixels_per_point), out_color_gamma.a); + if r_locals.dithering == 0 { + return out_color_gamma; + } + // Dither the float color down to eight bits to reduce banding. + // This step is optional for egui backends. + let out_color_dithered = vec4( + dither_interleaved(out_color_gamma.rgb, 256.0, in.position), + out_color_gamma.a + ); return out_color_dithered; } diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 118f246540a..d03e4e046f6 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -90,6 +90,7 @@ impl RenderState { surface: &wgpu::Surface<'static>, depth_format: Option, msaa_samples: u32, + dithering: bool, ) -> Result { crate::profile_scope!("RenderState::create"); // async yield give bad names using `profile_function` @@ -164,7 +165,13 @@ impl RenderState { .await? }; - let renderer = Renderer::new(&device, target_format, depth_format, msaa_samples); + let renderer = Renderer::new( + &device, + target_format, + depth_format, + msaa_samples, + dithering, + ); Ok(Self { adapter: Arc::new(adapter), diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 6cc386769d6..3dd03f7a3f5 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -133,7 +133,7 @@ impl ScreenDescriptor { #[repr(C)] struct UniformBuffer { screen_size_in_points: [f32; 2], - pixels_per_point: f32, + dithering: u32, // Uniform buffers need to be at least 16 bytes in WebGL. // See https://github.com/gfx-rs/wgpu/issues/2072 _padding: u32, @@ -170,6 +170,8 @@ pub struct Renderer { next_user_texture_id: u64, samplers: HashMap, + dithering: bool, + /// Storage for resources shared with all invocations of [`CallbackTrait`]'s methods. /// /// See also [`CallbackTrait`]. @@ -186,6 +188,7 @@ impl Renderer { output_color_format: wgpu::TextureFormat, output_depth_format: Option, msaa_samples: u32, + dithering: bool, ) -> Self { crate::profile_function!(); @@ -202,7 +205,7 @@ impl Renderer { label: Some("egui_uniform_buffer"), contents: bytemuck::cast_slice(&[UniformBuffer { screen_size_in_points: [0.0, 0.0], - pixels_per_point: 0., + dithering: u32::from(dithering), _padding: Default::default(), }]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, @@ -366,7 +369,7 @@ impl Renderer { // Buffers on wgpu are zero initialized, so this is indeed its current state! previous_uniform_buffer_content: UniformBuffer { screen_size_in_points: [0.0, 0.0], - pixels_per_point: 0., + dithering: 0, _padding: 0, }, uniform_bind_group, @@ -374,6 +377,7 @@ impl Renderer { textures: HashMap::default(), next_user_texture_id: 0, samplers: HashMap::default(), + dithering, callback_resources: CallbackResources::default(), } } @@ -784,7 +788,7 @@ impl Renderer { let uniform_buffer_content = UniformBuffer { screen_size_in_points, - pixels_per_point: screen_descriptor.pixels_per_point, + dithering: u32::from(self.dithering), _padding: Default::default(), }; if uniform_buffer_content != self.previous_uniform_buffer_content { diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 4a909bfc75f..46db8821e02 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -83,6 +83,7 @@ pub struct Painter { configuration: WgpuConfiguration, msaa_samples: u32, support_transparent_backbuffer: bool, + dithering: bool, depth_format: Option, screen_capture_state: Option, @@ -113,6 +114,7 @@ impl Painter { msaa_samples: u32, depth_format: Option, support_transparent_backbuffer: bool, + dithering: bool, ) -> Self { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: configuration.supported_backends, @@ -123,6 +125,7 @@ impl Painter { configuration, msaa_samples, support_transparent_backbuffer, + dithering, depth_format, screen_capture_state: None, @@ -264,6 +267,7 @@ impl Painter { &surface, self.depth_format, self.msaa_samples, + self.dithering, ) .await?; self.render_state.get_or_insert(render_state) diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index 70f07421475..0066b2ea815 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -161,7 +161,7 @@ fn main() { let (gl_window, gl) = create_display(&event_loop); let gl = std::sync::Arc::new(gl); - let mut egui_glow = egui_glow::EguiGlow::new(&event_loop, gl.clone(), None, None); + let mut egui_glow = egui_glow::EguiGlow::new(&event_loop, gl.clone(), None, None, true); let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); egui_glow diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 5116c95d585..db2ff76d733 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -138,6 +138,7 @@ impl Painter { gl: Arc, shader_prefix: &str, shader_version: Option, + dithering: bool, ) -> Result { crate::profile_function!(); crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new"); @@ -197,9 +198,10 @@ impl Painter { &gl, glow::FRAGMENT_SHADER, &format!( - "{}\n#define NEW_SHADER_INTERFACE {}\n#define SRGB_TEXTURES {}\n{}\n{}", + "{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n#define SRGB_TEXTURES {}\n{}\n{}", shader_version_declaration, shader_version.is_new_shader_interface() as i32, + dithering as i32, srgb_textures as i32, shader_prefix, FRAG_SRC diff --git a/crates/egui_glow/src/shader/fragment.glsl b/crates/egui_glow/src/shader/fragment.glsl index d77861f9284..30da2809ee4 100644 --- a/crates/egui_glow/src/shader/fragment.glsl +++ b/crates/egui_glow/src/shader/fragment.glsl @@ -62,5 +62,8 @@ void main() { // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. - gl_FragColor = vec4(dither_interleaved(frag_color_gamma.rgb, 256.), frag_color_gamma.a); +#if DITHERING + frag_color_gamma.rgb = dither_interleaved(frag_color_gamma.rgb, 256.); +#endif + gl_FragColor = frag_color_gamma; } diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index a9bec5fd039..c3bcfe386b5 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -27,8 +27,9 @@ impl EguiGlow { gl: std::sync::Arc, shader_version: Option, native_pixels_per_point: Option, + dithering: bool, ) -> Self { - let painter = crate::Painter::new(gl, "", shader_version) + let painter = crate::Painter::new(gl, "", shader_version, dithering) .map_err(|err| { log::error!("error occurred in initializing painter:\n{err}"); }) From bc942474100a45ccdc0e7ebce32c69febb512250 Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Thu, 30 May 2024 18:57:50 +0000 Subject: [PATCH 4/8] use native_options.dithering when creating glow painter --- crates/eframe/src/native/glow_integration.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 75a76b7e2db..7af9973be14 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -184,7 +184,12 @@ impl GlowWinitApp { })) }; - let painter = egui_glow::Painter::new(gl, "", native_options.shader_version, true)?; + let painter = egui_glow::Painter::new( + gl, + "", + native_options.shader_version, + native_options.dithering, + )?; Ok((glutin_window_context, painter)) } From 4d2786382b88864515e24c528a7355d44de895d9 Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Thu, 30 May 2024 21:32:30 +0000 Subject: [PATCH 5/8] Remove incorrect comment --- crates/egui-wgpu/src/egui.wgsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index 9acae8b9f01..7b33177c326 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -7,8 +7,8 @@ struct VertexOutput { }; struct Locals { - screen_size: vec2, // in points - dithering: u32, + screen_size: vec2, + dithering: u32, // 1 if dithering is enabled, 0 otherwise // Uniform buffers need to be at least 16 bytes in WebGL. // See https://github.com/gfx-rs/wgpu/issues/2072 _padding: u32, From 43a1f1fefdb3f0fbe729b73faa1562319027c72b Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Thu, 30 May 2024 21:46:52 +0000 Subject: [PATCH 6/8] Apply dithering to gamma encoded values in fs_main_linear_framebuffer --- crates/egui-wgpu/src/egui.wgsl | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index 7b33177c326..b60d9de9e83 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -100,15 +100,17 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { // We always have an sRGB aware texture at the moment. let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); let tex_gamma = gamma_from_linear_rgba(tex_linear); - let out_color_gamma = in.color * tex_gamma; - let out_color_linear = linear_from_gamma_rgb(out_color_gamma.rgb); + var out_color_gamma = in.color * tex_gamma; // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. - if r_locals.dithering == 0 { - return vec4(out_color_linear, out_color_gamma.a); + // Note that dithering is performed on the gamma encoded values, + // because this function is used together with a srgb converting target. + if r_locals.dithering == 1 { + let out_color_gamma_rgb = dither_interleaved(out_color_gamma.rgb, 256.0, in.position); + out_color_gamma = vec4(out_color_gamma_rgb, out_color_gamma.a); } - let out_color_dithered = dither_interleaved(out_color_linear, 256.0, in.position); - return vec4(out_color_dithered, out_color_gamma.a); + let out_color_linear = linear_from_gamma_rgb(out_color_gamma.rgb); + return vec4(out_color_linear, out_color_gamma.a); } @fragment @@ -116,15 +118,12 @@ fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { // We always have an sRGB aware texture at the moment. let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); let tex_gamma = gamma_from_linear_rgba(tex_linear); - let out_color_gamma = in.color * tex_gamma; - if r_locals.dithering == 0 { - return out_color_gamma; - } + var out_color_gamma = in.color * tex_gamma; // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. - let out_color_dithered = vec4( - dither_interleaved(out_color_gamma.rgb, 256.0, in.position), - out_color_gamma.a - ); - return out_color_dithered; + if r_locals.dithering == 1 { + let out_color_gamma_rgb = dither_interleaved(out_color_gamma.rgb, 256.0, in.position); + out_color_gamma = vec4(out_color_gamma_rgb, out_color_gamma.a); + } + return out_color_gamma; } From 636b2a0b8543c9658c6762e4b57d769e799548a2 Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Fri, 31 May 2024 16:37:49 +0000 Subject: [PATCH 7/8] Cleanup, Comments, formatting --- crates/eframe/src/epi.rs | 2 +- crates/egui-wgpu/src/renderer.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index ac8ba89287e..52079dec7e7 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -371,7 +371,7 @@ pub struct NativeOptions { pub persistence_path: Option, /// Controls whether colors should be dithered to minimize banding. - /// Default to true. + /// Defaults to true. pub dithering: bool, } diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 3dd03f7a3f5..016af3f4477 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -142,6 +142,7 @@ struct UniformBuffer { impl PartialEq for UniformBuffer { fn eq(&self, other: &Self) -> bool { self.screen_size_in_points == other.screen_size_in_points + && self.dithering == other.dithering } } From 1e9564d63093992a519fb35c30fbbcc7bfd31312 Mon Sep 17 00:00:00 2001 From: Jonas Wagner Date: Sun, 7 Jul 2024 08:43:47 +0000 Subject: [PATCH 8/8] Review feedback --- crates/eframe/src/epi.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 52079dec7e7..c8c9069a0bf 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -370,7 +370,12 @@ pub struct NativeOptions { /// from [directories]. pub persistence_path: Option, - /// Controls whether colors should be dithered to minimize banding. + /// Controls whether to apply dithering to minimize banding artifacts. + /// + /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between + /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space". + /// This means that only inputs from texture interpolation and vertex colors should be affected in practice. + /// /// Defaults to true. pub dithering: bool, } @@ -473,7 +478,12 @@ pub struct WebOptions { #[cfg(feature = "wgpu")] pub wgpu_options: egui_wgpu::WgpuConfiguration, - /// Controls whether colors should be dithered to minimize banding. + /// Controls whether to apply dithering to minimize banding artifacts. + /// + /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between + /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space". + /// This means that only inputs from texture interpolation and vertex colors should be affected in practice. + /// /// Defaults to true. pub dithering: bool, }