diff --git a/CHANGELOG.md b/CHANGELOG.md index 12c6ab23aba..f738b00295b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG ## Unreleased * ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)). +* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)). + +### Fixed 🐛 +* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)). ## 0.19.0 - 2022-08-20 diff --git a/Cargo.lock b/Cargo.lock index 91fa5c3d8f6..4a7b56ad284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1304,6 +1304,7 @@ dependencies = [ "document-features", "egui", "egui-winit", + "egui_demo_lib", "glium", "image", ] diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 77c75e2d0d7..0266c73189b 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -338,7 +338,7 @@ mod glow_integration { .with_hardware_acceleration(hardware_acceleration) .with_depth_buffer(native_options.depth_buffer) .with_multisampling(native_options.multisampling) - .with_srgb(true) + .with_srgb(false) .with_stencil_buffer(native_options.stencil_buffer) .with_vsync(native_options.vsync) .build_windowed(window_builder, event_loop) @@ -365,7 +365,7 @@ mod glow_integration { let gl = Arc::new(gl); let painter = - egui_glow::Painter::new(gl.clone(), None, "", self.native_options.shader_version) + egui_glow::Painter::new(gl.clone(), "", self.native_options.shader_version) .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); let system_theme = self.native_options.system_theme(); diff --git a/crates/eframe/src/web/web_glow_painter.rs b/crates/eframe/src/web/web_glow_painter.rs index 16a600ade79..e5e239cc7a2 100644 --- a/crates/eframe/src/web/web_glow_painter.rs +++ b/crates/eframe/src/web/web_glow_painter.rs @@ -22,8 +22,7 @@ impl WebPainter { let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options)?; let gl = std::sync::Arc::new(gl); - let dimension = [canvas.width() as i32, canvas.height() as i32]; - let painter = egui_glow::Painter::new(gl, Some(dimension), shader_prefix, None) + let painter = egui_glow::Painter::new(gl, shader_prefix, None) .map_err(|error| format!("Error starting glow painter: {}", error))?; Ok(Self { diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index b56e45f0880..c652281d7bf 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -3,10 +3,10 @@ All notable changes to the `egui-wgpu` integration will be noted in this file. ## Unreleased -* Rename `RenderPass` to `Renderer` -* Rename `RenderPass::execute` to `RenderPass::render` -* Rename `RenderPass::execute_with_renderpass` to `Renderer::render_onto_renderpass` -* Reexport `Renderer` +* Renamed `RenderPass` to `Renderer`. +* Renamed `RenderPass::execute` to `RenderPass::render`. +* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render_onto_renderpass`. +* Reexported `Renderer`. ## 0.19.0 - 2022-08-20 diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index 60e14e71a1d..552bcbbf391 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -2,7 +2,7 @@ struct VertexOutput { @location(0) tex_coord: vec2, - @location(1) color: vec4, + @location(1) color: vec4, // gamma 0-1 @builtin(position) position: vec4, }; @@ -14,22 +14,35 @@ struct Locals { }; @group(0) @binding(0) var r_locals: Locals; -// 0-1 from 0-255 -fn linear_from_srgb(srgb: vec3) -> vec3 { - let cutoff = srgb < vec3(10.31475); - let lower = srgb / vec3(3294.6); - let higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); +// 0-1 linear from 0-1 sRGB gamma +fn linear_from_gamma_rgb(srgb: vec3) -> vec3 { + let cutoff = srgb < vec3(0.04045); + let lower = srgb / vec3(12.92); + let higher = pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4)); return select(higher, lower, cutoff); } -// [u8; 4] SRGB as u32 -> [r, g, b, a] +// 0-1 sRGB gamma from 0-1 linear +fn gamma_from_linear_rgb(rgb: vec3) -> vec3 { + let cutoff = rgb < vec3(0.0031308); + let lower = rgb * vec3(12.92); + let higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055); + return select(higher, lower, cutoff); +} + +// 0-1 sRGBA gamma from 0-1 linear +fn gamma_from_linear_rgba(linear_rgba: vec4) -> vec4 { + return vec4(gamma_from_linear_rgb(linear_rgba.rgb), linear_rgba.a); +} + +// [u8; 4] SRGB as u32 -> [r, g, b, a] in 0.-1 fn unpack_color(color: u32) -> vec4 { return vec4( f32(color & 255u), f32((color >> 8u) & 255u), f32((color >> 16u) & 255u), f32((color >> 24u) & 255u), - ); + ) / 255.0; } fn position_from_screen(screen_pos: vec2) -> vec4 { @@ -49,22 +62,7 @@ fn vs_main( ) -> VertexOutput { var out: VertexOutput; out.tex_coord = a_tex_coord; - let color = unpack_color(a_color); - out.color = vec4(linear_from_srgb(color.rgb), color.a / 255.0); - out.position = position_from_screen(a_pos); - return out; -} - -@vertex -fn vs_conv_main( - @location(0) a_pos: vec2, - @location(1) a_tex_coord: vec2, - @location(2) a_color: u32, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coord = a_tex_coord; - let color = unpack_color(a_color); - out.color = vec4(color.rgba / 255.0); + out.color = unpack_color(a_color); out.position = position_from_screen(a_pos); return out; } @@ -75,6 +73,19 @@ fn vs_conv_main( @group(1) @binding(1) var r_tex_sampler: sampler; @fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return in.color * textureSample(r_tex_color, r_tex_sampler, in.tex_coord); +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; + return vec4(linear_from_gamma_rgb(out_color_gamma.rgb), out_color_gamma.a); +} + +@fragment +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; + return out_color_gamma; } diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 80813bd37e0..9c1651214cc 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -140,7 +140,8 @@ pub struct Renderer { impl Renderer { /// Creates a renderer for a egui UI. /// - /// If the format passed is not a *Srgb format, the shader will automatically convert to `sRGB` colors in the shader. + /// `output_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or + /// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space. pub fn new( device: &wgpu::Device, output_format: wgpu::TextureFormat, @@ -235,11 +236,7 @@ impl Renderer { label: Some("egui_pipeline"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { - entry_point: if output_format.describe().srgb { - "vs_main" - } else { - "vs_conv_main" - }, + entry_point: "vs_main", module: &module, buffers: &[wgpu::VertexBufferLayout { array_stride: 5 * 4, @@ -268,7 +265,12 @@ impl Renderer { fragment: Some(wgpu::FragmentState { module: &module, - entry_point: "fs_main", + entry_point: if output_format.describe().srgb { + tracing::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_format); + "fs_main_linear_framebuffer" + } else { + "fs_main_gamma_framebuffer" // this is what we prefer + }, targets: &[Some(wgpu::ColorTargetState { format: output_format, blend: Some(wgpu::BlendState { @@ -518,7 +520,7 @@ impl Renderer { image.pixels.len(), "Mismatch between texture size and texel count" ); - Cow::Owned(image.srgba_pixels(1.0).collect::>()) + Cow::Owned(image.srgba_pixels(None).collect::>()) } }; let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice()); @@ -564,7 +566,7 @@ impl Renderer { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: wgpu::TextureFormat::Rgba8UnormSrgb, // TODO(emilk): handle WebGL1 where this is not always supported! usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }); let filter = match image_delta.filter { diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 43f9eda5d27..008c5b231e9 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -117,7 +117,9 @@ impl<'a> Painter<'a> { if self.render_state.is_none() { let adapter = self.adapter.as_ref().unwrap(); - let swapchain_format = surface.get_supported_formats(adapter)[0]; + + let swapchain_format = + select_framebuffer_format(&surface.get_supported_formats(adapter)); let rs = pollster::block_on(self.init_render_state(adapter, swapchain_format)); self.render_state = Some(rs); @@ -322,3 +324,15 @@ impl<'a> Painter<'a> { // TODO(emilk): something here? } } + +fn select_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat { + for &format in formats { + if matches!( + format, + wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm + ) { + return format; + } + } + formats[0] // take the first +} diff --git a/crates/egui_demo_lib/src/color_test.rs b/crates/egui_demo_lib/src/color_test.rs index ae3b347aac8..771efbf8893 100644 --- a/crates/egui_demo_lib/src/color_test.rs +++ b/crates/egui_demo_lib/src/color_test.rs @@ -1,7 +1,8 @@ -use egui::{color::*, widgets::color_picker::show_color, TextureFilter, *}; use std::collections::HashMap; -const GRADIENT_SIZE: Vec2 = vec2(256.0, 24.0); +use egui::{color::*, widgets::color_picker::show_color, TextureFilter, *}; + +const GRADIENT_SIZE: Vec2 = vec2(256.0, 18.0); const BLACK: Color32 = Color32::BLACK; const GREEN: Color32 = Color32::GREEN; @@ -16,7 +17,6 @@ pub struct ColorTest { tex_mngr: TextureManager, vertex_gradients: bool, texture_gradients: bool, - srgb: bool, } impl Default for ColorTest { @@ -25,7 +25,6 @@ impl Default for ColorTest { tex_mngr: Default::default(), vertex_gradients: true, texture_gradients: true, - srgb: false, } } } @@ -38,12 +37,17 @@ impl ColorTest { ui.add(crate::egui_github_link_file!()); }); - ui.label("This is made to test that the egui painter backend is set up correctly, so that all colors are interpolated and blended in linear space with premultiplied alpha."); - ui.label("If everything is set up correctly, all groups of gradients will look uniform"); + ui.horizontal_wrapped(|ui|{ + ui.label("This is made to test that the egui painter backend is set up correctly."); + ui.add(egui::Label::new("❓").sense(egui::Sense::click())) + .on_hover_text("The texture sampling should be sRGB-aware, and everyt other color operation should be done in gamma-space (sRGB). All colors should use pre-multiplied alpha"); + }); + ui.label("If the rendering is done right, all groups of gradients will look uniform."); - ui.checkbox(&mut self.vertex_gradients, "Vertex gradients"); - ui.checkbox(&mut self.texture_gradients, "Texture gradients"); - ui.checkbox(&mut self.srgb, "Show naive sRGBA horror"); + ui.horizontal(|ui| { + ui.checkbox(&mut self.vertex_gradients, "Vertex gradients"); + ui.checkbox(&mut self.texture_gradients, "Texture gradients"); + }); ui.heading("sRGB color test"); ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500"); @@ -56,12 +60,13 @@ impl ColorTest { ui.separator(); - ui.label("Test that vertex color times texture color is done in linear space:"); + ui.label("Test that vertex color times texture color is done in gamma space:"); ui.scope(|ui| { ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients - let tex_color = Rgba::from_rgb(1.0, 0.25, 0.25); - let vertex_color = Rgba::from_rgb(0.5, 0.75, 0.75); + let tex_color = Color32::from_rgb(64, 128, 255); + let vertex_color = Color32::from_rgb(128, 196, 196); + let ground_truth = mul_color_gamma(tex_color, vertex_color); ui.horizontal(|ui| { let color_size = ui.spacing().interact_size; @@ -72,13 +77,13 @@ impl ColorTest { ui.label(" vertex color ="); }); { - let g = Gradient::one_color(Color32::from(tex_color * vertex_color)); + let g = Gradient::one_color(ground_truth); self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g); self.tex_gradient(ui, "Ground truth (texture)", WHITE, &g); } ui.horizontal(|ui| { - let g = Gradient::one_color(Color32::from(tex_color)); + let g = Gradient::one_color(tex_color); let tex = self.tex_mngr.get(ui.ctx(), &g); let texel_offset = 0.5 / (g.0.len() as f32); let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); @@ -93,31 +98,25 @@ impl ColorTest { // TODO(emilk): test color multiplication (image tint), // to make sure vertex and texture color multiplication is done in linear space. - self.show_gradients(ui, WHITE, (RED, GREEN)); - if self.srgb { - ui.label("Notice the darkening in the center of the naive sRGB interpolation."); - } + ui.separator(); + ui.label("Gamma interpolation:"); + self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Gamma); ui.separator(); - self.show_gradients(ui, RED, (TRANSPARENT, GREEN)); + self.show_gradients(ui, RED, (TRANSPARENT, GREEN), Interpolation::Gamma); ui.separator(); - self.show_gradients(ui, WHITE, (TRANSPARENT, GREEN)); - if self.srgb { - ui.label( - "Notice how the linear blend stays green while the naive sRGBA interpolation looks gray in the middle.", - ); - } + self.show_gradients(ui, WHITE, (TRANSPARENT, GREEN), Interpolation::Gamma); ui.separator(); - self.show_gradients(ui, BLACK, (BLACK, WHITE)); + self.show_gradients(ui, BLACK, (BLACK, WHITE), Interpolation::Gamma); ui.separator(); - self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT)); + self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT), Interpolation::Gamma); ui.separator(); - self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE)); + self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE), Interpolation::Gamma); ui.separator(); ui.label("Additive blending: add more and more blue to the red background:"); @@ -125,18 +124,40 @@ impl ColorTest { ui, RED, (TRANSPARENT, Color32::from_rgb_additive(0, 0, 255)), + Interpolation::Gamma, ); ui.separator(); + ui.label("Linear interpolation (texture sampling):"); + self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Linear); + + ui.separator(); + pixel_test(ui); + ui.separator(); + ui.label("Testing text rendering:"); + + text_on_bg(ui, Color32::from_gray(200), Color32::from_gray(230)); // gray on gray + text_on_bg(ui, Color32::from_gray(140), Color32::from_gray(28)); // dark mode normal text + + // Matches Mac Font book (useful for testing): + text_on_bg(ui, Color32::from_gray(39), Color32::from_gray(255)); + text_on_bg(ui, Color32::from_gray(220), Color32::from_gray(30)); + ui.separator(); blending_and_feathering_test(ui); } - fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) { + fn show_gradients( + &mut self, + ui: &mut Ui, + bg_fill: Color32, + (left, right): (Color32, Color32), + interpolation: Interpolation, + ) { let is_opaque = left.is_opaque() && right.is_opaque(); ui.horizontal(|ui| { @@ -154,11 +175,12 @@ impl ColorTest { ui.scope(|ui| { ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients if is_opaque { - let g = Gradient::ground_truth_linear_gradient(left, right); + let g = Gradient::ground_truth_gradient(left, right, interpolation); self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g); self.tex_gradient(ui, "Ground Truth (CPU gradient) - texture", bg_fill, &g); } else { - let g = Gradient::ground_truth_linear_gradient(left, right).with_bg_fill(bg_fill); + let g = Gradient::ground_truth_gradient(left, right, interpolation) + .with_bg_fill(bg_fill); self.vertex_gradient( ui, "Ground Truth (CPU gradient, CPU blending) - vertices", @@ -171,30 +193,27 @@ impl ColorTest { bg_fill, &g, ); - let g = Gradient::ground_truth_linear_gradient(left, right); + let g = Gradient::ground_truth_gradient(left, right, interpolation); self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g); self.tex_gradient(ui, "CPU gradient, GPU blending - texture", bg_fill, &g); } - let g = Gradient::texture_gradient(left, right); - self.vertex_gradient( - ui, - "Triangle mesh of width 2 (test vertex decode and interpolation)", - bg_fill, - &g, - ); - self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g); - - if self.srgb { - let g = - Gradient::ground_truth_bad_srgba_gradient(left, right).with_bg_fill(bg_fill); - self.vertex_gradient( - ui, - "Triangle mesh with naive sRGBA interpolation (WRONG)", - bg_fill, - &g, - ); - self.tex_gradient(ui, "Naive sRGBA interpolation (WRONG)", bg_fill, &g); + let g = Gradient::endpoints(left, right); + + match interpolation { + Interpolation::Linear => { + // texture sampler is sRGBA aware, and should therefore be linear + self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g); + } + Interpolation::Gamma => { + // vertex shader uses gamma + self.vertex_gradient( + ui, + "Triangle mesh of width 2 (test vertex decode and interpolation)", + bg_fill, + &g, + ); + } } }); } @@ -258,6 +277,12 @@ fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Respon response } +#[derive(Clone, Copy)] +enum Interpolation { + Linear, + Gamma, +} + #[derive(Clone, Hash, PartialEq, Eq)] struct Gradient(pub Vec); @@ -266,10 +291,21 @@ impl Gradient { Self(vec![srgba, srgba]) } - pub fn texture_gradient(left: Color32, right: Color32) -> Self { + pub fn endpoints(left: Color32, right: Color32) -> Self { Self(vec![left, right]) } + pub fn ground_truth_gradient( + left: Color32, + right: Color32, + interpolation: Interpolation, + ) -> Self { + match interpolation { + Interpolation::Linear => Self::ground_truth_linear_gradient(left, right), + Interpolation::Gamma => Self::ground_truth_gamma_gradient(left, right), + } + } + pub fn ground_truth_linear_gradient(left: Color32, right: Color32) -> Self { let left = Rgba::from(left); let right = Rgba::from(right); @@ -285,33 +321,32 @@ impl Gradient { ) } - /// This is how a bad person blends `sRGBA` - pub fn ground_truth_bad_srgba_gradient(left: Color32, right: Color32) -> Self { + pub fn ground_truth_gamma_gradient(left: Color32, right: Color32) -> Self { let n = 255; Self( (0..=n) .map(|i| { let t = i as f32 / n as f32; - Color32::from_rgba_premultiplied( - lerp((left[0] as f32)..=(right[0] as f32), t).round() as u8, // Don't ever do this please! - lerp((left[1] as f32)..=(right[1] as f32), t).round() as u8, // Don't ever do this please! - lerp((left[2] as f32)..=(right[2] as f32), t).round() as u8, // Don't ever do this please! - lerp((left[3] as f32)..=(right[3] as f32), t).round() as u8, // Don't ever do this please! - ) + lerp_color_gamma(left, right, t) }) .collect(), ) } /// Do premultiplied alpha-aware blending of the gradient on top of the fill color + /// in gamma-space. pub fn with_bg_fill(self, bg: Color32) -> Self { - let bg = Rgba::from(bg); Self( self.0 .into_iter() .map(|fg| { - let fg = Rgba::from(fg); - Color32::from(bg * (1.0 - fg.a()) + fg) + let a = fg.a() as f32 / 255.0; + Color32::from_rgba_premultiplied( + (bg[0] as f32 * (1.0 - a) + fg[0] as f32).round() as u8, + (bg[1] as f32 * (1.0 - a) + fg[1] as f32).round() as u8, + (bg[2] as f32 * (1.0 - a) + fg[2] as f32).round() as u8, + (bg[3] as f32 * (1.0 - a) + fg[3] as f32).round() as u8, + ) }) .collect(), ) @@ -380,8 +415,6 @@ fn pixel_test(ui: &mut Ui) { } fn blending_and_feathering_test(ui: &mut Ui) { - ui.label("Some fine lines for testing anti-aliasing and blending:"); - let size = vec2(512.0, 512.0); let (response, painter) = ui.allocate_painter(size, Sense::hover()); let rect = response.rect; @@ -397,32 +430,67 @@ fn blending_and_feathering_test(ui: &mut Ui) { paint_fine_lines_and_text(&painter, bottom_half, Color32::BLACK); } +fn text_on_bg(ui: &mut egui::Ui, fg: Color32, bg: Color32) { + assert!(fg.is_opaque()); + assert!(bg.is_opaque()); + + ui.horizontal(|ui| { + ui.label( + RichText::from("▣ The quick brown fox jumps over the lazy dog and runs away.") + .background_color(bg) + .color(fg), + ); + ui.label(format!( + "({} {} {}) on ({} {} {})", + fg.r(), + fg.g(), + fg.b(), + bg.r(), + bg.g(), + bg.b(), + )); + }); +} + fn paint_fine_lines_and_text(painter: &egui::Painter, mut rect: Rect, color: Color32) { { - let mut x = 0.0; + let mut y = 0.0; for opacity in [1.00, 0.50, 0.25, 0.10, 0.05, 0.02, 0.01, 0.00] { painter.text( - rect.center_top() + vec2(0.0, x), + rect.center_top() + vec2(0.0, y), Align2::LEFT_TOP, format!("{:.0}% white", 100.0 * opacity), FontId::proportional(14.0), Color32::WHITE.linear_multiply(opacity), ); painter.text( - rect.center_top() + vec2(80.0, x), + rect.center_top() + vec2(80.0, y), Align2::LEFT_TOP, format!("{:.0}% gray", 100.0 * opacity), FontId::proportional(14.0), Color32::GRAY.linear_multiply(opacity), ); painter.text( - rect.center_top() + vec2(160.0, x), + rect.center_top() + vec2(160.0, y), Align2::LEFT_TOP, format!("{:.0}% black", 100.0 * opacity), FontId::proportional(14.0), Color32::BLACK.linear_multiply(opacity), ); - x += 20.0; + y += 20.0; + } + + for font_size in [6.0, 7.0, 8.0, 9.0, 10.0, 12.0, 14.0] { + painter.text( + rect.center_top() + vec2(0.0, y), + Align2::LEFT_TOP, + format!( + "{font_size}px - The quick brown fox jumps over the lazy dog and runs away." + ), + FontId::proportional(font_size), + color, + ); + y += font_size + 1.0; } } @@ -472,3 +540,21 @@ fn paint_fine_lines_and_text(painter: &egui::Painter, mut rect: Rect, color: Col mesh.add_triangle(1, 2, 3); painter.add(mesh); } + +fn mul_color_gamma(left: Color32, right: Color32) -> Color32 { + Color32::from_rgba_premultiplied( + (left.r() as f32 * right.r() as f32 / 255.0).round() as u8, + (left.g() as f32 * right.g() as f32 / 255.0).round() as u8, + (left.b() as f32 * right.b() as f32 / 255.0).round() as u8, + (left.a() as f32 * right.a() as f32 / 255.0).round() as u8, + ) +} + +fn lerp_color_gamma(left: Color32, right: Color32, t: f32) -> Color32 { + Color32::from_rgba_premultiplied( + lerp((left[0] as f32)..=(right[0] as f32), t).round() as u8, + lerp((left[1] as f32)..=(right[1] as f32), t).round() as u8, + lerp((left[2] as f32)..=(right[2] as f32), t).round() as u8, + lerp((left[3] as f32)..=(right[3] as f32), t).round() as u8, + ) +} diff --git a/crates/egui_glium/Cargo.toml b/crates/egui_glium/Cargo.toml index 35f5bd21fc2..7322c495033 100644 --- a/crates/egui_glium/Cargo.toml +++ b/crates/egui_glium/Cargo.toml @@ -54,4 +54,5 @@ document-features = { version = "0.2", optional = true } [dev-dependencies] +egui_demo_lib = { version = "0.19.0", path = "../egui_demo_lib", default-features = false } image = { version = "0.24", default-features = false, features = ["png"] } diff --git a/crates/egui_glium/examples/native_texture.rs b/crates/egui_glium/examples/native_texture.rs index 53d6840ddf9..d383c349f0e 100644 --- a/crates/egui_glium/examples/native_texture.rs +++ b/crates/egui_glium/examples/native_texture.rs @@ -115,7 +115,7 @@ fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Disp let context_builder = glutin::ContextBuilder::new() .with_depth_buffer(0) - .with_srgb(true) + .with_srgb(false) .with_stencil_buffer(0) .with_vsync(true); diff --git a/crates/egui_glium/examples/pure_glium.rs b/crates/egui_glium/examples/pure_glium.rs index ac069a93473..840a43a3158 100644 --- a/crates/egui_glium/examples/pure_glium.rs +++ b/crates/egui_glium/examples/pure_glium.rs @@ -10,6 +10,8 @@ fn main() { let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop); + let mut color_test = egui_demo_lib::ColorTest::default(); + event_loop.run(move |event, _, control_flow| { let mut redraw = || { let mut quit = false; @@ -21,6 +23,12 @@ fn main() { quit = true; } }); + + egui::CentralPanel::default().show(egui_ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + color_test.ui(ui); + }); + }); }); *control_flow = if quit { @@ -93,7 +101,7 @@ fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Disp let context_builder = glutin::ContextBuilder::new() .with_depth_buffer(0) - .with_srgb(true) + .with_srgb(false) .with_stencil_buffer(0) .with_vsync(true); diff --git a/crates/egui_glium/src/painter.rs b/crates/egui_glium/src/painter.rs index 4f02aca1f9c..775a84f469d 100644 --- a/crates/egui_glium/src/painter.rs +++ b/crates/egui_glium/src/painter.rs @@ -8,7 +8,6 @@ use { glium::{ implement_vertex, index::PrimitiveType, - program, texture::{self, srgb_texture2d::SrgbTexture2d}, uniform, uniforms::{MagnifySamplerFilter, SamplerWrapFunction}, @@ -26,31 +25,77 @@ pub struct Painter { next_native_tex_id: u64, } +fn create_program( + facade: &dyn glium::backend::Facade, + vertex_shader: &str, + fragment_shader: &str, +) -> glium::program::Program { + let input = glium::program::ProgramCreationInput::SourceCode { + vertex_shader, + tessellation_control_shader: None, + tessellation_evaluation_shader: None, + geometry_shader: None, + fragment_shader, + transform_feedback_varyings: None, + outputs_srgb: true, + uses_point_size: false, + }; + + glium::program::Program::new(facade, input) + .unwrap_or_else(|err| panic!("Failed to compile shader: {}", err)) +} + impl Painter { pub fn new(facade: &dyn glium::backend::Facade) -> Painter { use glium::CapabilitiesSource as _; let max_texture_side = facade.get_capabilities().max_texture_size as _; - let program = program! { - facade, - 120 => { - vertex: include_str!("shader/vertex_120.glsl"), - fragment: include_str!("shader/fragment_120.glsl"), - }, - 140 => { - vertex: include_str!("shader/vertex_140.glsl"), - fragment: include_str!("shader/fragment_140.glsl"), - }, - 100 es => { - vertex: include_str!("shader/vertex_100es.glsl"), - fragment: include_str!("shader/fragment_100es.glsl"), - }, - 300 es => { - vertex: include_str!("shader/vertex_300es.glsl"), - fragment: include_str!("shader/fragment_300es.glsl"), - }, - } - .expect("Failed to compile shader"); + let program = if facade + .get_context() + .is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 4)) + { + eprintln!("Using GL 1.4"); + create_program( + facade, + include_str!("shader/vertex_140.glsl"), + include_str!("shader/fragment_140.glsl"), + ) + } else if facade + .get_context() + .is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 2)) + { + eprintln!("Using GL 1.2"); + create_program( + facade, + include_str!("shader/vertex_120.glsl"), + include_str!("shader/fragment_120.glsl"), + ) + } else if facade + .get_context() + .is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 3, 0)) + { + eprintln!("Using GL ES 3.0"); + create_program( + facade, + include_str!("shader/vertex_300es.glsl"), + include_str!("shader/fragment_300es.glsl"), + ) + } else if facade + .get_context() + .is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 1, 0)) + { + eprintln!("Using GL ES 1.0"); + create_program( + facade, + include_str!("shader/vertex_100es.glsl"), + include_str!("shader/fragment_100es.glsl"), + ) + } else { + panic!( + "Failed to find a compatible shader for OpenGL version {:?}", + facade.get_version() + ) + }; Painter { max_texture_side, @@ -236,13 +281,10 @@ impl Painter { ); image.pixels.iter().map(|color| color.to_tuple()).collect() } - egui::ImageData::Font(image) => { - let gamma = 1.0; - image - .srgba_pixels(gamma) - .map(|color| color.to_tuple()) - .collect() - } + egui::ImageData::Font(image) => image + .srgba_pixels(None) + .map(|color| color.to_tuple()) + .collect(), }; let glium_image = glium::texture::RawImage2d { data: std::borrow::Cow::Owned(pixels), diff --git a/crates/egui_glium/src/shader/fragment_100es.glsl b/crates/egui_glium/src/shader/fragment_100es.glsl index 2987819190d..912f47f8fcf 100644 --- a/crates/egui_glium/src/shader/fragment_100es.glsl +++ b/crates/egui_glium/src/shader/fragment_100es.glsl @@ -2,7 +2,7 @@ precision mediump float; uniform sampler2D u_sampler; -varying vec4 v_rgba; +varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA varying vec2 v_tc; // 0-255 sRGB from 0-1 linear @@ -30,16 +30,9 @@ vec4 linear_from_srgba(vec4 srgba) { } void main() { - // We must decode the colors, since WebGL doesn't come with sRGBA textures: - vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0); + // WebGL doesn't come with sRGBA textures: + vec4 texture_in_gamma = texture2D(u_sampler, v_tc); - /// Multiply vertex color with texture color (in linear space). - gl_FragColor = v_rgba * texture_rgba; - - // We must gamma-encode again since WebGL doesn't support linear blending in the framebuffer. - gl_FragColor = srgba_from_linear(v_rgba * texture_rgba) / 255.0; - - // WebGL doesn't support linear blending in the framebuffer, - // so we apply this hack to at least get a bit closer to the desired blending: - gl_FragColor.a = pow(gl_FragColor.a, 1.6); // Empiric nonsense + // Multiply vertex color with texture color (in gamma space). + gl_FragColor = v_rgba_gamma * texture_in_gamma; } diff --git a/crates/egui_glium/src/shader/fragment_120.glsl b/crates/egui_glium/src/shader/fragment_120.glsl index 6cd250df26b..e697278d93e 100644 --- a/crates/egui_glium/src/shader/fragment_120.glsl +++ b/crates/egui_glium/src/shader/fragment_120.glsl @@ -1,11 +1,31 @@ #version 120 uniform sampler2D u_sampler; -varying vec4 v_rgba; +varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA varying vec2 v_tc; +// 0-255 sRGB from 0-1 linear +vec3 srgb_from_linear(vec3 rgb) { + bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); + vec3 lower = rgb * vec3(3294.6); + vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); + return mix(higher, lower, vec3(cutoff)); +} + +// 0-255 sRGBA from 0-1 linear +vec4 srgba_from_linear(vec4 rgba) { + return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); +} + +// 0-1 gamma from 0-1 linear +vec4 gamma_from_linear_rgba(vec4 linear_rgba) { + return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); +} + void main() { - // The texture sampler is sRGB aware, and glium already expects linear rgba output - // so no need for any sRGB conversions here: - gl_FragColor = v_rgba * texture2D(u_sampler, v_tc); + // The texture is set up with `SRGB8_ALPHA8` + vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); + + // Multiply vertex color with texture color (in gamma space). + gl_FragColor = v_rgba_gamma * texture_in_gamma; } diff --git a/crates/egui_glium/src/shader/fragment_140.glsl b/crates/egui_glium/src/shader/fragment_140.glsl index f2e38ccda56..4db357f51ee 100644 --- a/crates/egui_glium/src/shader/fragment_140.glsl +++ b/crates/egui_glium/src/shader/fragment_140.glsl @@ -1,12 +1,32 @@ #version 140 uniform sampler2D u_sampler; -in vec4 v_rgba; +in vec4 v_rgba_gamma; in vec2 v_tc; out vec4 f_color; +// 0-255 sRGB from 0-1 linear +vec3 srgb_from_linear(vec3 rgb) { + bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); + vec3 lower = rgb * vec3(3294.6); + vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); + return mix(higher, lower, vec3(cutoff)); +} + +// 0-255 sRGBA from 0-1 linear +vec4 srgba_from_linear(vec4 rgba) { + return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); +} + +// 0-1 gamma from 0-1 linear +vec4 gamma_from_linear_rgba(vec4 linear_rgba) { + return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); +} + void main() { - // The texture sampler is sRGB aware, and glium already expects linear rgba output - // so no need for any sRGB conversions here: - f_color = v_rgba * texture(u_sampler, v_tc); + // The texture is set up with `SRGB8_ALPHA8` + vec4 texture_in_gamma = gamma_from_linear_rgba(texture(u_sampler, v_tc)); + + // Multiply vertex color with texture color (in gamma space). + f_color = v_rgba_gamma * texture_in_gamma; } diff --git a/crates/egui_glium/src/shader/fragment_300es.glsl b/crates/egui_glium/src/shader/fragment_300es.glsl index 509c0a12aa8..8d478616560 100644 --- a/crates/egui_glium/src/shader/fragment_300es.glsl +++ b/crates/egui_glium/src/shader/fragment_300es.glsl @@ -2,7 +2,7 @@ precision mediump float; uniform sampler2D u_sampler; -varying vec4 v_rgba; +varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA varying vec2 v_tc; // 0-255 sRGB from 0-1 linear @@ -13,21 +13,20 @@ vec3 srgb_from_linear(vec3 rgb) { return mix(higher, lower, vec3(cutoff)); } +// 0-255 sRGBA from 0-1 linear vec4 srgba_from_linear(vec4 rgba) { return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); } -void main() { - // The texture is set up with `SRGB8_ALPHA8`, so no need to decode here! - vec4 texture_rgba = texture2D(u_sampler, v_tc); - - /// Multiply vertex color with texture color (in linear space). - gl_FragColor = v_rgba * texture_rgba; +// 0-1 gamma from 0-1 linear +vec4 gamma_from_linear_rgba(vec4 linear_rgba) { + return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); +} - // We must gamma-encode again since WebGL doesn't support linear blending in the framebuffer. - gl_FragColor = srgba_from_linear(v_rgba * texture_rgba) / 255.0; +void main() { + // The texture is set up with `SRGB8_ALPHA8` + vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); - // WebGL doesn't support linear blending in the framebuffer, - // so we apply this hack to at least get a bit closer to the desired blending: - gl_FragColor.a = pow(gl_FragColor.a, 1.6); // Empiric nonsense + // Multiply vertex color with texture color (in gamma space). + gl_FragColor = v_rgba_gamma * texture_in_gamma; } diff --git a/crates/egui_glium/src/shader/vertex_100es.glsl b/crates/egui_glium/src/shader/vertex_100es.glsl index a0332888825..4cc48136503 100644 --- a/crates/egui_glium/src/shader/vertex_100es.glsl +++ b/crates/egui_glium/src/shader/vertex_100es.glsl @@ -5,28 +5,15 @@ uniform vec2 u_screen_size; attribute vec2 a_pos; attribute vec2 a_tc; attribute vec4 a_srgba; -varying vec4 v_rgba; +varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA varying vec2 v_tc; -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - void main() { gl_Position = vec4( 2.0 * a_pos.x / u_screen_size.x - 1.0, 1.0 - 2.0 * a_pos.y / u_screen_size.y, 0.0, 1.0); - // egui encodes vertex colors in gamma spaces, so we must decode the colors here: - v_rgba = linear_from_srgba(a_srgba); + v_rgba_gamma = a_srgba / 255.0; v_tc = a_tc; } diff --git a/crates/egui_glium/src/shader/vertex_120.glsl b/crates/egui_glium/src/shader/vertex_120.glsl index ef04ef76416..2f1a032395f 100644 --- a/crates/egui_glium/src/shader/vertex_120.glsl +++ b/crates/egui_glium/src/shader/vertex_120.glsl @@ -4,28 +4,15 @@ uniform vec2 u_screen_size; attribute vec2 a_pos; attribute vec4 a_srgba; // 0-255 sRGB attribute vec2 a_tc; -varying vec4 v_rgba; +varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA varying vec2 v_tc; -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - void main() { gl_Position = vec4( 2.0 * a_pos.x / u_screen_size.x - 1.0, 1.0 - 2.0 * a_pos.y / u_screen_size.y, 0.0, 1.0); - // egui encodes vertex colors in gamma spaces, so we must decode the colors here: - v_rgba = linear_from_srgba(a_srgba); + v_rgba_gamma = a_srgba / 255.0; v_tc = a_tc; } diff --git a/crates/egui_glium/src/shader/vertex_140.glsl b/crates/egui_glium/src/shader/vertex_140.glsl index c3a5b79844b..03d0a8d7658 100644 --- a/crates/egui_glium/src/shader/vertex_140.glsl +++ b/crates/egui_glium/src/shader/vertex_140.glsl @@ -4,28 +4,15 @@ uniform vec2 u_screen_size; in vec2 a_pos; in vec4 a_srgba; // 0-255 sRGB in vec2 a_tc; -out vec4 v_rgba; +out vec4 v_rgba_gamma; out vec2 v_tc; -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, cutoff); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - void main() { gl_Position = vec4( 2.0 * a_pos.x / u_screen_size.x - 1.0, 1.0 - 2.0 * a_pos.y / u_screen_size.y, 0.0, 1.0); - // egui encodes vertex colors in gamma spaces, so we must decode the colors here: - v_rgba = linear_from_srgba(a_srgba); + v_rgba_gamma = a_srgba / 255.0; v_tc = a_tc; } diff --git a/crates/egui_glium/src/shader/vertex_300es.glsl b/crates/egui_glium/src/shader/vertex_300es.glsl index 0cf5dc11e6c..60fdac1c021 100644 --- a/crates/egui_glium/src/shader/vertex_300es.glsl +++ b/crates/egui_glium/src/shader/vertex_300es.glsl @@ -5,28 +5,15 @@ uniform vec2 u_screen_size; attribute vec2 a_pos; attribute vec2 a_tc; attribute vec4 a_srgba; -varying vec4 v_rgba; +varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA varying vec2 v_tc; -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - void main() { gl_Position = vec4( 2.0 * a_pos.x / u_screen_size.x - 1.0, 1.0 - 2.0 * a_pos.y / u_screen_size.y, 0.0, 1.0); - // egui encodes vertex colors in gamma spaces, so we must decode the colors here: - v_rgba = linear_from_srgba(a_srgba); + v_rgba_gamma = a_srgba / 255.0; v_tc = a_tc; } diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 40ccd5f5592..dca670db706 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the `egui_glow` integration will be noted in this file. * Allow empty textures. * Added `shader_version` variable on `EguiGlow::new` for easier cross compilling on different OpenGL | ES targets ([#1993](https://github.com/emilk/egui/pull/1993)). + ## 0.19.0 - 2022-08-20 * MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). * `EguiGlow::new` now takes an `EventLoopWindowTarget` instead of a `winit::Window` ([#1634](https://github.com/emilk/egui/pull/1634)). diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index e6f4f0b4709..df19f9c4010 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -116,7 +116,7 @@ fn create_display( let gl_window = unsafe { glutin::ContextBuilder::new() .with_depth_buffer(0) - .with_srgb(true) + .with_srgb(false) .with_stencil_buffer(0) .with_vsync(true) .build_windowed(window_builder, event_loop) diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index daf62b425c6..bc149a06556 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -15,7 +15,6 @@ pub mod painter; pub use glow; pub use painter::{CallbackFn, Painter}; mod misc_util; -mod post_process; mod shader_version; mod vao; diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 4f6ce975539..d05ee7a397b 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -12,7 +12,6 @@ use memoffset::offset_of; use crate::check_for_gl_error; use crate::misc_util::{compile_shader, link_program}; -use crate::post_process::PostProcess; use crate::shader_version::ShaderVersion; use crate::vao; @@ -52,10 +51,8 @@ pub struct Painter { u_screen_size: glow::UniformLocation, u_sampler: glow::UniformLocation, is_webgl_1: bool, - is_embedded: bool, vao: crate::vao::VertexArrayObject, srgb_textures: bool, - post_process: Option, vbo: glow::Buffer, element_array_buffer: glow::Buffer, @@ -105,7 +102,6 @@ impl Painter { /// * failed to create buffer pub fn new( gl: Arc, - pp_fb_extent: Option<[i32; 2]>, shader_prefix: &str, shader_version: Option, ) -> Result { @@ -115,8 +111,8 @@ impl Painter { let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize; let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl)); let is_webgl_1 = shader_version == ShaderVersion::Es100; - let header = shader_version.version_declaration(); - tracing::debug!("Shader header: {:?}.", header); + let shader_version_declaration = shader_version.version_declaration(); + tracing::debug!("Shader header: {:?}.", shader_version_declaration); let supported_extensions = gl.supported_extensions(); tracing::trace!("OpenGL extensions: {supported_extensions:?}"); @@ -125,44 +121,17 @@ impl Painter { // EXT_sRGB, GL_ARB_framebuffer_sRGB, GL_EXT_sRGB, GL_EXT_texture_sRGB_decode, … extension.contains("sRGB") }); - tracing::debug!("SRGB Support: {:?}", srgb_textures); - - let (post_process, srgb_support_define) = if shader_version.is_embedded() { - // WebGL doesn't support linear framebuffer blending… but maybe we can emulate it with `PostProcess`? - - if let Some(size) = pp_fb_extent { - tracing::debug!("WebGL with sRGB support. Turning on post processing for linear framebuffer blending."); - // install post process to correct sRGB color: - ( - Some(unsafe { PostProcess::new(gl.clone(), shader_prefix, is_webgl_1, size)? }), - "#define SRGB_SUPPORTED", - ) - } else { - tracing::warn!("WebGL or OpenGL ES detected but PostProcess disabled because dimension is None. Linear framebuffer blending will not be supported."); - (None, "") - } - } else { - if srgb_textures { - (None, "#define SRGB_SUPPORTED") - } else { - tracing::warn!("sRGB aware texture filtering not available"); - (None, "") - } - }; + tracing::debug!("SRGB texture Support: {:?}", srgb_textures); unsafe { let vert = compile_shader( &gl, glow::VERTEX_SHADER, &format!( - "{}\n{}\n{}\n{}", - header, + "{}\n#define NEW_SHADER_INTERFACE {}\n{}\n{}", + shader_version_declaration, + shader_version.is_new_shader_interface() as i32, shader_prefix, - if shader_version.is_new_shader_interface() { - "#define NEW_SHADER_INTERFACE\n" - } else { - "" - }, VERT_SRC ), )?; @@ -170,15 +139,11 @@ impl Painter { &gl, glow::FRAGMENT_SHADER, &format!( - "{}\n{}\n{}\n{}\n{}", - header, + "{}\n#define NEW_SHADER_INTERFACE {}\n#define SRGB_TEXTURES {}\n{}\n{}", + shader_version_declaration, + shader_version.is_new_shader_interface() as i32, + srgb_textures as i32, shader_prefix, - srgb_support_define, - if shader_version.is_new_shader_interface() { - "#define NEW_SHADER_INTERFACE\n" - } else { - "" - }, FRAG_SRC ), )?; @@ -236,10 +201,8 @@ impl Painter { u_screen_size, u_sampler, is_webgl_1, - is_embedded: shader_version.is_embedded(), vao, srgb_textures, - post_process, vbo, element_array_buffer, textures: Default::default(), @@ -268,8 +231,11 @@ impl Painter { /// So if in a [`egui::Shape::Callback`] you need to use an offscreen FBO, you should /// then restore to this afterwards with /// `gl.bind_framebuffer(glow::FRAMEBUFFER, painter.intermediate_fbo());` + #[allow(clippy::unused_self)] pub fn intermediate_fbo(&self) -> Option { - self.post_process.as_ref().map(|pp| pp.fbo()) + // We don't currently ever render to an offscreen buffer, + // but we may want to start to in order to do anti-aliasing on web, for instance. + None } unsafe fn prepare_painting( @@ -298,7 +264,7 @@ impl Painter { ); if !cfg!(target_arch = "wasm32") { - self.gl.enable(glow::FRAMEBUFFER_SRGB); + self.gl.disable(glow::FRAMEBUFFER_SRGB); check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB"); } @@ -372,17 +338,6 @@ impl Painter { crate::profile_function!(); self.assert_not_destroyed(); - if let Some(ref mut post_process) = self.post_process { - unsafe { - post_process.begin(screen_size_px[0] as i32, screen_size_px[1] as i32); - post_process.bind(); - self.gl.disable(glow::SCISSOR_TEST); - self.gl - .viewport(0, 0, screen_size_px[0] as i32, screen_size_px[1] as i32); - // use the same clear-color as was set for the screen framebuffer. - self.gl.clear(glow::COLOR_BUFFER_BIT); - } - } let size_in_pixels = unsafe { self.prepare_painting(screen_size_px, pixels_per_point) }; for egui::ClippedPrimitive { @@ -435,12 +390,7 @@ impl Painter { check_for_gl_error!(&self.gl, "callback"); // Restore state: - unsafe { - if let Some(ref mut post_process) = self.post_process { - post_process.bind(); - } - self.prepare_painting(screen_size_px, pixels_per_point) - }; + unsafe { self.prepare_painting(screen_size_px, pixels_per_point) }; } } } @@ -450,10 +400,6 @@ impl Painter { self.vao.unbind(&self.gl); self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - if let Some(ref post_process) = self.post_process { - post_process.end(); - } - self.gl.disable(glow::SCISSOR_TEST); check_for_gl_error!(&self.gl, "painting"); @@ -532,13 +478,8 @@ impl Painter { "Mismatch between texture size and texel count" ); - let gamma = if self.is_embedded && self.post_process.is_none() { - 1.0 / 2.2 - } else { - 1.0 - }; let data: Vec = image - .srgba_pixels(gamma) + .srgba_pixels(None) .flat_map(|a| a.to_array()) .collect(); @@ -684,9 +625,6 @@ impl Painter { if !self.destroyed { unsafe { self.destroy_gl(); - if let Some(ref post_process) = self.post_process { - post_process.destroy(); - } } self.destroyed = true; } diff --git a/crates/egui_glow/src/post_process.rs b/crates/egui_glow/src/post_process.rs deleted file mode 100644 index a593d789c31..00000000000 --- a/crates/egui_glow/src/post_process.rs +++ /dev/null @@ -1,284 +0,0 @@ -#![allow(unsafe_code)] -use crate::check_for_gl_error; -use crate::misc_util::{compile_shader, link_program}; -use crate::vao::BufferInfo; -use glow::HasContext as _; - -/// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB` -/// in a separate "post processing" step -pub(crate) struct PostProcess { - gl: std::sync::Arc, - pos_buffer: glow::Buffer, - index_buffer: glow::Buffer, - vao: crate::vao::VertexArrayObject, - is_webgl_1: bool, - color_texture: glow::Texture, - depth_renderbuffer: Option, - texture_size: (i32, i32), - fbo: glow::Framebuffer, - program: glow::Program, -} - -impl PostProcess { - pub(crate) unsafe fn new( - gl: std::sync::Arc, - shader_prefix: &str, - is_webgl_1: bool, - [width, height]: [i32; 2], - ) -> Result { - gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); - - let fbo = gl.create_framebuffer()?; - - gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); - - // ---------------------------------------------- - // Set up color tesxture: - - let color_texture = gl.create_texture()?; - gl.bind_texture(glow::TEXTURE_2D, Some(color_texture)); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_S, - glow::CLAMP_TO_EDGE as i32, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_T, - glow::CLAMP_TO_EDGE as i32, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MIN_FILTER, - glow::NEAREST as i32, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::NEAREST as i32, - ); - - let (internal_format, format) = if is_webgl_1 { - (glow::SRGB_ALPHA, glow::SRGB_ALPHA) - } else { - (glow::SRGB8_ALPHA8, glow::RGBA) - }; - - gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - internal_format as i32, - width, - height, - 0, - format, - glow::UNSIGNED_BYTE, - None, - ); - crate::check_for_gl_error_even_in_release!(&gl, "post process texture initialization"); - - gl.framebuffer_texture_2d( - glow::FRAMEBUFFER, - glow::COLOR_ATTACHMENT0, - glow::TEXTURE_2D, - Some(color_texture), - 0, - ); - gl.bind_texture(glow::TEXTURE_2D, None); - - // --------------------------------------------------------- - // Depth buffer - we only need this when embedding 3D within egui using `egui::PaintCallback`. - // TODO(emilk): add a setting to enable/disable the depth buffer. - - let with_depth_buffer = true; - let depth_renderbuffer = if with_depth_buffer { - let depth_renderbuffer = gl.create_renderbuffer()?; - gl.bind_renderbuffer(glow::RENDERBUFFER, Some(depth_renderbuffer)); - gl.renderbuffer_storage(glow::RENDERBUFFER, glow::DEPTH_COMPONENT16, width, height); - gl.bind_renderbuffer(glow::RENDERBUFFER, None); - Some(depth_renderbuffer) - } else { - None - }; - - // --------------------------------------------------------- - - gl.bind_framebuffer(glow::FRAMEBUFFER, None); - - // --------------------------------------------------------- - - let vert_shader = compile_shader( - &gl, - glow::VERTEX_SHADER, - &format!( - "{}\n{}", - shader_prefix, - include_str!("shader/post_vertex_100es.glsl") - ), - )?; - let frag_shader = compile_shader( - &gl, - glow::FRAGMENT_SHADER, - &format!( - "{}\n{}", - shader_prefix, - include_str!("shader/post_fragment_100es.glsl") - ), - )?; - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - - let positions: Vec = vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]; - - let indices: Vec = vec![0, 1, 2, 1, 2, 3]; - - let pos_buffer = gl.create_buffer()?; - gl.bind_buffer(glow::ARRAY_BUFFER, Some(pos_buffer)); - gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - bytemuck::cast_slice(&positions), - glow::STATIC_DRAW, - ); - - let a_pos_loc = gl - .get_attrib_location(program, "a_pos") - .ok_or_else(|| "failed to get location of a_pos".to_owned())?; - let vao = crate::vao::VertexArrayObject::new( - &gl, - pos_buffer, - vec![BufferInfo { - location: a_pos_loc, - vector_size: 2, - data_type: glow::FLOAT, - normalized: false, - stride: 0, - offset: 0, - }], - ); - - let index_buffer = gl.create_buffer()?; - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); - gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW); - - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - crate::check_for_gl_error_even_in_release!(&gl, "post process initialization"); - - Ok(PostProcess { - gl, - pos_buffer, - index_buffer, - vao, - is_webgl_1, - color_texture, - depth_renderbuffer, - texture_size: (width, height), - fbo, - program, - }) - } - - /// What we render to. - pub(crate) fn fbo(&self) -> glow::Framebuffer { - self.fbo - } - - pub(crate) unsafe fn begin(&mut self, width: i32, height: i32) { - if (width, height) != self.texture_size { - self.gl - .bind_texture(glow::TEXTURE_2D, Some(self.color_texture)); - self.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); - let (internal_format, format) = if self.is_webgl_1 { - (glow::SRGB_ALPHA, glow::SRGB_ALPHA) - } else { - (glow::SRGB8_ALPHA8, glow::RGBA) - }; - self.gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - internal_format as i32, - width, - height, - 0, - format, - glow::UNSIGNED_BYTE, - None, - ); - self.gl.bind_texture(glow::TEXTURE_2D, None); - - if let Some(depth_renderbuffer) = self.depth_renderbuffer { - self.gl - .bind_renderbuffer(glow::RENDERBUFFER, Some(depth_renderbuffer)); - self.gl.renderbuffer_storage( - glow::RENDERBUFFER, - glow::DEPTH_COMPONENT16, - width, - height, - ); - self.gl.bind_renderbuffer(glow::RENDERBUFFER, None); - } - - self.texture_size = (width, height); - } - - check_for_gl_error!(&self.gl, "PostProcess::begin"); - } - - pub(crate) unsafe fn bind(&self) { - self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); - - self.gl.framebuffer_texture_2d( - glow::FRAMEBUFFER, - glow::COLOR_ATTACHMENT0, - glow::TEXTURE_2D, - Some(self.color_texture), - 0, - ); - - self.gl.framebuffer_renderbuffer( - glow::FRAMEBUFFER, - glow::DEPTH_ATTACHMENT, - glow::RENDERBUFFER, - self.depth_renderbuffer, - ); - - check_for_gl_error!(&self.gl, "PostProcess::bind"); - } - - pub(crate) unsafe fn end(&self) { - self.gl.bind_framebuffer(glow::FRAMEBUFFER, None); - self.gl.disable(glow::SCISSOR_TEST); - - self.gl.use_program(Some(self.program)); - - self.gl.active_texture(glow::TEXTURE0); - self.gl - .bind_texture(glow::TEXTURE_2D, Some(self.color_texture)); - let u_sampler_loc = self - .gl - .get_uniform_location(self.program, "u_sampler") - .unwrap(); - self.gl.uniform_1_i32(Some(&u_sampler_loc), 0); - self.vao.bind(&self.gl); - - self.gl - .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); - self.gl - .draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0); - self.vao.unbind(&self.gl); - self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - self.gl.bind_texture(glow::TEXTURE_2D, None); - self.gl.use_program(None); - - check_for_gl_error!(&self.gl, "PostProcess::end"); - } - - pub(crate) unsafe fn destroy(&self) { - self.gl.delete_buffer(self.pos_buffer); - self.gl.delete_buffer(self.index_buffer); - self.gl.delete_program(self.program); - self.gl.delete_framebuffer(self.fbo); - self.gl.delete_texture(self.color_texture); - if let Some(depth_renderbuffer) = self.depth_renderbuffer { - self.gl.delete_renderbuffer(depth_renderbuffer); - } - } -} diff --git a/crates/egui_glow/src/shader/fragment.glsl b/crates/egui_glow/src/shader/fragment.glsl index 9760c4ef00b..c1fc1740148 100644 --- a/crates/egui_glow/src/shader/fragment.glsl +++ b/crates/egui_glow/src/shader/fragment.glsl @@ -4,73 +4,38 @@ uniform sampler2D u_sampler; -#ifdef NEW_SHADER_INTERFACE - in vec4 v_rgba; +#if NEW_SHADER_INTERFACE + in vec4 v_rgba_in_gamma; in vec2 v_tc; out vec4 f_color; // a dirty hack applied to support webGL2 #define gl_FragColor f_color #define texture2D texture #else - varying vec4 v_rgba; + varying vec4 v_rgba_in_gamma; varying vec2 v_tc; #endif -#ifdef SRGB_SUPPORTED - void main() { - // The texture sampler is sRGB aware, and OpenGL already expects linear rgba output - // so no need for any sRGB conversions here: - gl_FragColor = v_rgba * texture2D(u_sampler, v_tc); - } +// 0-1 sRGB gamma from 0-1 linear +vec3 srgb_gamma_from_linear(vec3 rgb) { + bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); + vec3 lower = rgb * vec3(12.92); + vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055); + return mix(higher, lower, vec3(cutoff)); +} + +// 0-1 sRGBA gamma from 0-1 linear +vec4 srgba_gamma_from_linear(vec4 rgba) { + return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a); +} + +void main() { +#if SRGB_TEXTURES + vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc)); #else - // 0-255 sRGB from 0-1 linear - vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); - } - - vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); - } - - // 0-1 linear from 0-255 sRGB - vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); - } - - vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); - } - - void main() { - // We must decode the colors, since WebGL1 doesn't come with sRGBA textures: - vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0); - /// Multiply vertex color with texture color (in linear space). - gl_FragColor = v_rgba * texture_rgba; - - // WebGL1 doesn't support linear blending in the framebuffer, - // so we do a hack here where we change the premultiplied alpha - // to do the multiplication in gamma space instead: - - // Unmultiply alpha: - if (gl_FragColor.a > 0.0) { - gl_FragColor.rgb /= gl_FragColor.a; - } - - // Empiric tweak to make e.g. shadows look more like they should: - gl_FragColor.a *= sqrt(gl_FragColor.a); - - // To gamma: - gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0; - - // Premultiply alpha, this time in gamma space: - if (gl_FragColor.a > 0.0) { - gl_FragColor.rgb *= gl_FragColor.a; - } - } + vec4 texture_in_gamma = texture2D(u_sampler, v_tc); #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; +} diff --git a/crates/egui_glow/src/shader/post_fragment_100es.glsl b/crates/egui_glow/src/shader/post_fragment_100es.glsl deleted file mode 100644 index bbcaed9e620..00000000000 --- a/crates/egui_glow/src/shader/post_fragment_100es.glsl +++ /dev/null @@ -1,26 +0,0 @@ -precision mediump float; -uniform sampler2D u_sampler; -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -void main() { - gl_FragColor = texture2D(u_sampler, v_tc); - - gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0; - - #ifdef APPLY_BRIGHTENING_GAMMA - gl_FragColor = vec4(pow(gl_FragColor.rgb, vec3(1.0/2.2)), gl_FragColor.a); - #endif -} diff --git a/crates/egui_glow/src/shader/post_vertex_100es.glsl b/crates/egui_glow/src/shader/post_vertex_100es.glsl deleted file mode 100644 index 37280bc1dad..00000000000 --- a/crates/egui_glow/src/shader/post_vertex_100es.glsl +++ /dev/null @@ -1,8 +0,0 @@ -precision mediump float; -attribute vec2 a_pos; -varying vec2 v_tc; - -void main() { - gl_Position = vec4(a_pos * 2. - 1., 0.0, 1.0); - v_tc = a_pos; -} diff --git a/crates/egui_glow/src/shader/vertex.glsl b/crates/egui_glow/src/shader/vertex.glsl index 43a3b6b3be4..fff31463ceb 100644 --- a/crates/egui_glow/src/shader/vertex.glsl +++ b/crates/egui_glow/src/shader/vertex.glsl @@ -1,4 +1,4 @@ -#ifdef NEW_SHADER_INTERFACE +#if NEW_SHADER_INTERFACE #define I in #define O out #define V(x) x @@ -16,28 +16,15 @@ uniform vec2 u_screen_size; I vec2 a_pos; I vec4 a_srgba; // 0-255 sRGB I vec2 a_tc; -O vec4 v_rgba; +O vec4 v_rgba_in_gamma; O vec2 v_tc; -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, V(cutoff)); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - void main() { gl_Position = vec4( 2.0 * a_pos.x / u_screen_size.x - 1.0, 1.0 - 2.0 * a_pos.y / u_screen_size.y, 0.0, 1.0); - // egui encodes vertex colors in gamma space, so we must decode the colors here: - v_rgba = linear_from_srgba(a_srgba); + v_rgba_in_gamma = a_srgba / 255.0; v_tc = a_tc; } diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index f3f79a64c57..8cb9f930836 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -20,7 +20,7 @@ impl EguiGlow { gl: std::sync::Arc, shader_version: Option, ) -> Self { - let painter = crate::Painter::new(gl, None, "", shader_version) + let painter = crate::Painter::new(gl, "", shader_version) .map_err(|error| { tracing::error!("error occurred in initializing painter:\n{}", error); }) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 86235dce923..ea107312df0 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the epaint crate will be documented in this file. ## Unreleased * ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)). +* ⚠️ BREAKING: epaint now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)). ## 0.19.0 - 2022-08-20 diff --git a/crates/epaint/src/color.rs b/crates/epaint/src/color.rs index a99d03b1b2e..e6508f34b51 100644 --- a/crates/epaint/src/color.rs +++ b/crates/epaint/src/color.rs @@ -326,7 +326,7 @@ impl Rgba { /// Transparent white #[inline(always)] pub fn from_white_alpha(a: f32) -> Self { - crate::epaint_assert!(0.0 <= a && a <= 1.0); + crate::epaint_assert!(0.0 <= a && a <= 1.0, "a: {}", a); Self([a, a, a, a]) } diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index fa5d587cbb2..2283411c055 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -195,17 +195,19 @@ impl FontImage { /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom. /// - /// `gamma` should normally be set to 1.0. - /// If you are having problems with text looking skinny and pixelated, try - /// setting a lower gamma, e.g. `0.5`. - pub fn srgba_pixels(&'_ self, gamma: f32) -> impl ExactSizeIterator + '_ { + /// `gamma` should normally be set to `None`. + /// + /// If you are having problems with text looking skinny and pixelated, try using a low gamma, e.g. `0.4`. + pub fn srgba_pixels( + &'_ self, + gamma: Option, + ) -> impl ExactSizeIterator + '_ { + let gamma = gamma.unwrap_or(0.55); // TODO(emilk): this default coverage gamma is a magic constant, chosen by eye. I don't even know why we need it. self.pixels.iter().map(move |coverage| { - // This is arbitrarily chosen to make text look as good as possible. - // In particular, it looks good with gamma=1 and the default eframe backend, - // which uses linear blending. - // See https://github.com/emilk/egui/issues/1410 - let a = fast_round(coverage.powf(gamma / 2.2) * 255.0); - Color32::from_rgba_premultiplied(a, a, a, a) // this makes no sense, but works + let alpha = coverage.powf(gamma); + // We want to multiply with `vec4(alpha)` in the fragment shader: + let a = fast_round(alpha * 255.0); + Color32::from_rgba_premultiplied(a, a, a, a) }) } diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index 0fc816deb40..953ff97c490 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -14,7 +14,7 @@ pub struct Shadow { } impl Shadow { - /// Tooltips, menus, … + /// Tooltips, menus, …, for dark mode. pub fn small_dark() -> Self { Self { extrusion: 16.0, @@ -22,15 +22,15 @@ impl Shadow { } } - /// Tooltips, menus, … + /// Tooltips, menus, …, for light mode. pub fn small_light() -> Self { Self { extrusion: 16.0, - color: Color32::from_black_alpha(32), + color: Color32::from_black_alpha(20), } } - /// Subtle and nice on dark backgrounds + /// Used for widnows in dark mode. pub fn big_dark() -> Self { Self { extrusion: 32.0, @@ -38,11 +38,11 @@ impl Shadow { } } - /// Subtle and nice on white backgrounds + /// Used for widnows in light mode. pub fn big_light() -> Self { Self { extrusion: 32.0, - color: Color32::from_black_alpha(40), + color: Color32::from_black_alpha(16), } } diff --git a/examples/retained_image/src/main.rs b/examples/retained_image/src/main.rs index 453e4772128..b67a71d0606 100644 --- a/examples/retained_image/src/main.rs +++ b/examples/retained_image/src/main.rs @@ -18,6 +18,7 @@ fn main() { struct MyApp { image: RetainedImage, + tint: egui::Color32, } impl Default for MyApp { @@ -28,6 +29,7 @@ impl Default for MyApp { include_bytes!("rust-logo-256x256.png"), ) .unwrap(), + tint: egui::Color32::from_rgb(255, 0, 255), } } } @@ -38,12 +40,22 @@ impl eframe::App for MyApp { ui.heading("This is an image:"); self.image.show(ui); - ui.heading("This is a rotated image:"); + ui.heading("This is a rotated image with a tint:"); ui.add( egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2()) - .rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5)), + .rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5)) + .tint(self.tint), ); + ui.horizontal(|ui| { + ui.label("Tint:"); + egui::color_picker::color_edit_button_srgba( + ui, + &mut self.tint, + egui::color_picker::Alpha::BlendOrAdditive, + ); + }); + ui.heading("This is an image you can click:"); ui.add(egui::ImageButton::new( self.image.texture_id(ctx),