From bc22c4f6f314c139c3128e804d3a013bdfcb6b62 Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 09:16:34 +0200 Subject: [PATCH 01/10] Revert "egui_web: make text thicker and less pixelated (#640)" This reverts commit 91bdf9ba --- egui_demo_lib/src/apps/demo/painting.rs | 2 +- egui_web/src/webgl1.rs | 3 +-- egui_web/src/webgl2.rs | 3 +-- epaint/src/texture_atlas.rs | 15 +++------------ 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/egui_demo_lib/src/apps/demo/painting.rs b/egui_demo_lib/src/apps/demo/painting.rs index 2468b385857..2e4d20723cd 100644 --- a/egui_demo_lib/src/apps/demo/painting.rs +++ b/egui_demo_lib/src/apps/demo/painting.rs @@ -12,7 +12,7 @@ impl Default for Painting { fn default() -> Self { Self { lines: Default::default(), - stroke: Stroke::new(2.0, Color32::LIGHT_BLUE), // Thin strokes looks bad on web + stroke: Stroke::new(1.0, Color32::LIGHT_BLUE), } } } diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index b5f6b9d6ee6..b17b82b2750 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -367,8 +367,7 @@ impl crate::Painter for WebGlPainter { } let mut pixels: Vec = Vec::with_capacity(texture.pixels.len() * 4); - let font_gamma = 1.0 / 2.2; // HACK due to non-linear framebuffer blending. - for srgba in texture.srgba_pixels(font_gamma) { + for srgba in texture.srgba_pixels() { pixels.push(srgba.r()); pixels.push(srgba.g()); pixels.push(srgba.b()); diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index f8c72262d0b..3662ab2e011 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -368,8 +368,7 @@ impl crate::Painter for WebGl2Painter { } let mut pixels: Vec = Vec::with_capacity(texture.pixels.len() * 4); - let font_gamma = 1.0 / 2.2; // HACK due to non-linear framebuffer blending. - for srgba in texture.srgba_pixels(font_gamma) { + for srgba in texture.srgba_pixels() { pixels.push(srgba.r()); pixels.push(srgba.g()); pixels.push(srgba.b()); diff --git a/epaint/src/texture_atlas.rs b/epaint/src/texture_atlas.rs index 3c45aba7c9b..fbc3dd7bbbe 100644 --- a/epaint/src/texture_atlas.rs +++ b/epaint/src/texture_atlas.rs @@ -17,19 +17,10 @@ impl Texture { } /// 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 egui text looking skinny and pixelated, try - /// setting a lower gamma, e.g. `0.5`. - pub fn srgba_pixels(&'_ self, gamma: f32) -> impl Iterator + '_ { + pub fn srgba_pixels(&'_ self) -> impl Iterator + '_ { use super::Color32; - - let srgba_from_luminance_lut: Vec = (0..=255) - .map(|a| { - let a = super::color::linear_f32_from_linear_u8(a).powf(gamma); - super::Rgba::from_white_alpha(a).into() - }) - .collect(); + let srgba_from_luminance_lut: Vec = + (0..=255).map(Color32::from_white_alpha).collect(); self.pixels .iter() .map(move |&l| srgba_from_luminance_lut[l as usize]) From 09efdc7256785a0855ec1df20227bb2655de570b Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 15:33:07 +0200 Subject: [PATCH 02/10] Fix WebgGL2 colors --- egui_web/Cargo.toml | 2 + egui_web/src/shader/fragment_300es.glsl | 45 ----- egui_web/src/shader/main_fragment_300es.glsl | 13 ++ ...rtex_300es.glsl => main_vertex_300es.glsl} | 0 egui_web/src/shader/post_fragment_300es.glsl | 22 +++ egui_web/src/shader/post_vertex_300es.glsl | 8 + egui_web/src/webgl2.rs | 178 +++++++++++++++++- 7 files changed, 220 insertions(+), 48 deletions(-) delete mode 100644 egui_web/src/shader/fragment_300es.glsl create mode 100644 egui_web/src/shader/main_fragment_300es.glsl rename egui_web/src/shader/{vertex_300es.glsl => main_vertex_300es.glsl} (100%) create mode 100644 egui_web/src/shader/post_fragment_300es.glsl create mode 100644 egui_web/src/shader/post_vertex_300es.glsl diff --git a/egui_web/Cargo.toml b/egui_web/Cargo.toml index 5b2750692da..7f44d612f61 100644 --- a/egui_web/Cargo.toml +++ b/egui_web/Cargo.toml @@ -92,11 +92,13 @@ features = [ "TouchList", "WebGl2RenderingContext", "WebGlBuffer", + "WebGlFramebuffer", "WebGlProgram", "WebGlRenderingContext", "WebGlShader", "WebGlTexture", "WebGlUniformLocation", + "WebGlVertexArrayObject", "WheelEvent", "Window", ] diff --git a/egui_web/src/shader/fragment_300es.glsl b/egui_web/src/shader/fragment_300es.glsl deleted file mode 100644 index aeb90bfcf80..00000000000 --- a/egui_web/src/shader/fragment_300es.glsl +++ /dev/null @@ -1,45 +0,0 @@ -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba; -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() { - // 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; - - // WebGL 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; - } -} diff --git a/egui_web/src/shader/main_fragment_300es.glsl b/egui_web/src/shader/main_fragment_300es.glsl new file mode 100644 index 00000000000..4dc0eb63acd --- /dev/null +++ b/egui_web/src/shader/main_fragment_300es.glsl @@ -0,0 +1,13 @@ +precision mediump float; +uniform sampler2D u_sampler; +varying vec4 v_rgba; +varying vec2 v_tc; + +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). + // Linear color is written and blended in Framebuffer and converted to sRGB later + gl_FragColor = v_rgba * texture_rgba; +} diff --git a/egui_web/src/shader/vertex_300es.glsl b/egui_web/src/shader/main_vertex_300es.glsl similarity index 100% rename from egui_web/src/shader/vertex_300es.glsl rename to egui_web/src/shader/main_vertex_300es.glsl diff --git a/egui_web/src/shader/post_fragment_300es.glsl b/egui_web/src/shader/post_fragment_300es.glsl new file mode 100644 index 00000000000..001f8a3f236 --- /dev/null +++ b/egui_web/src/shader/post_fragment_300es.glsl @@ -0,0 +1,22 @@ +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.; +} diff --git a/egui_web/src/shader/post_vertex_300es.glsl b/egui_web/src/shader/post_vertex_300es.glsl new file mode 100644 index 00000000000..37280bc1dad --- /dev/null +++ b/egui_web/src/shader/post_vertex_300es.glsl @@ -0,0 +1,8 @@ +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/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index 3662ab2e011..ef4866b9d63 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -3,7 +3,7 @@ use { js_sys::WebAssembly, wasm_bindgen::{prelude::*, JsCast}, - web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlProgram, WebGlShader, WebGlTexture}, + web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlShader, WebGlTexture, WebGlVertexArrayObject}, }; use egui::{ @@ -22,6 +22,7 @@ pub struct WebGl2Painter { pos_buffer: WebGlBuffer, tc_buffer: WebGlBuffer, color_buffer: WebGlBuffer, + post_process: PostProcess, egui_texture: WebGlTexture, egui_texture_version: Option, @@ -62,12 +63,12 @@ impl WebGl2Painter { let vert_shader = compile_shader( &gl, Gl::VERTEX_SHADER, - include_str!("shader/vertex_300es.glsl"), + include_str!("shader/main_vertex_300es.glsl"), )?; let frag_shader = compile_shader( &gl, Gl::FRAGMENT_SHADER, - include_str!("shader/fragment_300es.glsl"), + include_str!("shader/main_fragment_300es.glsl"), )?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?; @@ -76,6 +77,9 @@ impl WebGl2Painter { let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?; let color_buffer = gl.create_buffer().ok_or("failed to create color_buffer")?; + let post_process = + PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?; + Ok(WebGl2Painter { canvas_id: canvas_id.to_owned(), canvas, @@ -85,6 +89,7 @@ impl WebGl2Painter { pos_buffer, tc_buffer, color_buffer, + post_process, egui_texture, egui_texture_version: None, user_textures: Default::default(), @@ -428,6 +433,8 @@ impl crate::Painter for WebGl2Painter { let gl = &self.gl; + self.post_process.begin(self.canvas.width() as i32, self.canvas.height() as i32)?; + gl.enable(Gl::SCISSOR_TEST); gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. gl.enable(Gl::BLEND); @@ -484,8 +491,173 @@ impl crate::Painter for WebGl2Painter { )); } } + + self.post_process.end(); + + Ok(()) + } +} + +struct PostProcess { + gl: Gl, + pos_buffer: WebGlBuffer, + index_buffer: WebGlBuffer, + vao: WebGlVertexArrayObject, + texture: WebGlTexture, + texture_size: (i32, i32), + fbo: WebGlFramebuffer, + program: WebGlProgram, +} + +impl PostProcess { + fn new(gl: Gl, width: i32, height: i32) -> Result { + let fbo = gl.create_framebuffer().ok_or("failed to create framebuffer")?; + gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo)); + + let texture = gl.create_texture().unwrap(); + gl.bind_texture(Gl::TEXTURE_2D, Some(&texture)); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32); + gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); + gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + Gl::TEXTURE_2D, + 0, + Gl::SRGB8_ALPHA8 as i32, + width, + height, + 0, + Gl::RGBA, + Gl::UNSIGNED_BYTE, + None, + ) + .unwrap(); + gl.framebuffer_texture_2d( + Gl::FRAMEBUFFER, + Gl::COLOR_ATTACHMENT0, + Gl::TEXTURE_2D, + Some(&texture), + 0, + ); + + gl.bind_texture(Gl::TEXTURE_2D, None); + gl.bind_framebuffer(Gl::FRAMEBUFFER, None); + + let vert_shader = compile_shader( + &gl, + Gl::VERTEX_SHADER, + include_str!("shader/post_vertex_300es.glsl"), + )?; + let frag_shader = compile_shader( + &gl, + Gl::FRAGMENT_SHADER, + include_str!("shader/post_fragment_300es.glsl"), + )?; + let program = link_program(&gl, [vert_shader, frag_shader].iter())?; + + let vao = gl.create_vertex_array().ok_or("failed to create vao")?; + gl.bind_vertex_array(Some(&vao)); + + let positions = vec![ + 0u8, 0, + 1, 0, + 0, 1, + 1, 1, + ]; + + let indices = vec![0u8, 1, 2, 1, 2, 3]; + + let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; + gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&pos_buffer)); + gl.buffer_data_with_u8_array(Gl::ARRAY_BUFFER, &positions, Gl::STATIC_DRAW); + + let a_pos_loc = gl.get_attrib_location(&program, "a_pos"); + assert!(a_pos_loc >= 0); + gl.vertex_attrib_pointer_with_i32(a_pos_loc as u32, 2, Gl::UNSIGNED_BYTE, false, 0, 0); + gl.enable_vertex_attrib_array(a_pos_loc as u32); + + gl.bind_buffer(Gl::ARRAY_BUFFER, None); + + let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; + gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer)); + gl.buffer_data_with_u8_array(Gl::ELEMENT_ARRAY_BUFFER, &indices, Gl::STATIC_DRAW); + + gl.bind_vertex_array(None); + gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); + + Ok(PostProcess { + gl, + pos_buffer, + index_buffer, + vao, + texture, + texture_size: (width, height), + fbo, + program, + }) + } + + fn begin(&mut self, width: i32, height: i32) -> Result<(), JsValue> { + let gl = &self.gl; + + if (width, height) != self.texture_size { + gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); + gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); + gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + Gl::TEXTURE_2D, + 0, + Gl::SRGB8_ALPHA8 as i32, + width, + height, + 0, + Gl::RGBA, + Gl::UNSIGNED_BYTE, + None, + )?; + gl.bind_texture(Gl::TEXTURE_2D, None); + + self.texture_size = (width, height); + } + + gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&self.fbo)); + Ok(()) } + + fn end(&self) { + let gl = &self.gl; + + gl.bind_framebuffer(Gl::FRAMEBUFFER, None); + gl.disable(Gl::SCISSOR_TEST); + + gl.use_program(Some(&self.program)); + + gl.active_texture(Gl::TEXTURE0); + gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); + let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); + gl.uniform1i(Some(&u_sampler_loc), 0); + + gl.bind_vertex_array(Some(&self.vao)); + + gl.draw_elements_with_i32(Gl::TRIANGLES, 6, Gl::UNSIGNED_BYTE, 0); + + gl.bind_texture(Gl::TEXTURE_2D, None); + gl.bind_vertex_array(None); + gl.use_program(None); + } +} + +impl Drop for PostProcess { + fn drop(&mut self) { + let gl = &self.gl; + gl.delete_vertex_array(Some(&self.vao)); + gl.delete_buffer(Some(&self.pos_buffer)); + gl.delete_buffer(Some(&self.index_buffer)); + gl.delete_program(Some(&self.program)); + gl.delete_framebuffer(Some(&self.fbo)); + gl.delete_texture(Some(&self.texture)); + } } fn compile_shader( From b08d733e955168ebddc7587c958e46c15805972e Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 15:57:22 +0200 Subject: [PATCH 03/10] Fix WebgGL1 colors --- egui_web/src/shader/fragment_100es.glsl | 58 ------ egui_web/src/shader/main_fragment_100es.glsl | 26 +++ ...rtex_100es.glsl => main_vertex_100es.glsl} | 0 egui_web/src/shader/post_fragment_100es.glsl | 22 +++ egui_web/src/shader/post_vertex_100es.glsl | 8 + egui_web/src/webgl1.rs | 175 +++++++++++++++++- 6 files changed, 228 insertions(+), 61 deletions(-) delete mode 100644 egui_web/src/shader/fragment_100es.glsl create mode 100644 egui_web/src/shader/main_fragment_100es.glsl rename egui_web/src/shader/{vertex_100es.glsl => main_vertex_100es.glsl} (100%) create mode 100644 egui_web/src/shader/post_fragment_100es.glsl create mode 100644 egui_web/src/shader/post_vertex_100es.glsl diff --git a/egui_web/src/shader/fragment_100es.glsl b/egui_web/src/shader/fragment_100es.glsl deleted file mode 100644 index c5a3263f13b..00000000000 --- a/egui_web/src/shader/fragment_100es.glsl +++ /dev/null @@ -1,58 +0,0 @@ -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba; -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 sRGB from 0-1 linear -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)); -} - -// 0-1 linear from 0-255 sRGBA -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 WebGL 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; - - // WebGL 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; - } -} diff --git a/egui_web/src/shader/main_fragment_100es.glsl b/egui_web/src/shader/main_fragment_100es.glsl new file mode 100644 index 00000000000..2591e363e54 --- /dev/null +++ b/egui_web/src/shader/main_fragment_100es.glsl @@ -0,0 +1,26 @@ +precision mediump float; +uniform sampler2D u_sampler; +varying vec4 v_rgba; +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)); +} + +// 0-1 linear from 0-255 sRGBA +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 WebGL 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; +} diff --git a/egui_web/src/shader/vertex_100es.glsl b/egui_web/src/shader/main_vertex_100es.glsl similarity index 100% rename from egui_web/src/shader/vertex_100es.glsl rename to egui_web/src/shader/main_vertex_100es.glsl diff --git a/egui_web/src/shader/post_fragment_100es.glsl b/egui_web/src/shader/post_fragment_100es.glsl new file mode 100644 index 00000000000..001f8a3f236 --- /dev/null +++ b/egui_web/src/shader/post_fragment_100es.glsl @@ -0,0 +1,22 @@ +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.; +} diff --git a/egui_web/src/shader/post_vertex_100es.glsl b/egui_web/src/shader/post_vertex_100es.glsl new file mode 100644 index 00000000000..37280bc1dad --- /dev/null +++ b/egui_web/src/shader/post_vertex_100es.glsl @@ -0,0 +1,8 @@ +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/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index b17b82b2750..5bde0015824 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -1,7 +1,7 @@ use { js_sys::WebAssembly, wasm_bindgen::{prelude::*, JsCast}, - web_sys::{WebGlBuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlTexture}, + web_sys::{WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlTexture}, }; use egui::{ @@ -20,6 +20,7 @@ pub struct WebGlPainter { pos_buffer: WebGlBuffer, tc_buffer: WebGlBuffer, color_buffer: WebGlBuffer, + post_process: PostProcess, egui_texture: WebGlTexture, egui_texture_version: Option, @@ -60,12 +61,12 @@ impl WebGlPainter { let vert_shader = compile_shader( &gl, Gl::VERTEX_SHADER, - include_str!("shader/vertex_100es.glsl"), + include_str!("shader/main_vertex_100es.glsl"), )?; let frag_shader = compile_shader( &gl, Gl::FRAGMENT_SHADER, - include_str!("shader/fragment_100es.glsl"), + include_str!("shader/main_fragment_100es.glsl"), )?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?; @@ -74,6 +75,9 @@ impl WebGlPainter { let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?; let color_buffer = gl.create_buffer().ok_or("failed to create color_buffer")?; + let post_process = + PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?; + Ok(WebGlPainter { canvas_id: canvas_id.to_owned(), canvas, @@ -83,6 +87,7 @@ impl WebGlPainter { pos_buffer, tc_buffer, color_buffer, + post_process, egui_texture, egui_texture_version: None, user_textures: Default::default(), @@ -428,6 +433,8 @@ impl crate::Painter for WebGlPainter { let gl = &self.gl; + self.post_process.begin(self.canvas.width() as i32, self.canvas.height() as i32)?; + gl.enable(Gl::SCISSOR_TEST); gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. gl.enable(Gl::BLEND); @@ -484,8 +491,170 @@ impl crate::Painter for WebGlPainter { )); } } + + self.post_process.end(); + + Ok(()) + } +} + +struct PostProcess { + gl: Gl, + pos_buffer: WebGlBuffer, + a_pos_loc: u32, + index_buffer: WebGlBuffer, + texture: WebGlTexture, + texture_size: (i32, i32), + fbo: WebGlFramebuffer, + program: WebGlProgram, +} + +impl PostProcess { + fn new(gl: Gl, width: i32, height: i32) -> Result { + let fbo = gl.create_framebuffer().ok_or("failed to create framebuffer")?; + gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo)); + + let texture = gl.create_texture().unwrap(); + gl.bind_texture(Gl::TEXTURE_2D, Some(&texture)); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32); + gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); + gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + Gl::TEXTURE_2D, + 0, + Gl::RGBA as i32, + width, + height, + 0, + Gl::RGBA, + Gl::UNSIGNED_BYTE, + None, + ) + .unwrap(); + gl.framebuffer_texture_2d( + Gl::FRAMEBUFFER, + Gl::COLOR_ATTACHMENT0, + Gl::TEXTURE_2D, + Some(&texture), + 0, + ); + + gl.bind_texture(Gl::TEXTURE_2D, None); + gl.bind_framebuffer(Gl::FRAMEBUFFER, None); + + let vert_shader = compile_shader( + &gl, + Gl::VERTEX_SHADER, + include_str!("shader/post_vertex_100es.glsl"), + )?; + let frag_shader = compile_shader( + &gl, + Gl::FRAGMENT_SHADER, + include_str!("shader/post_fragment_100es.glsl"), + )?; + let program = link_program(&gl, [vert_shader, frag_shader].iter())?; + + let positions = vec![ + 0u8, 0, + 1, 0, + 0, 1, + 1, 1, + ]; + + let indices = vec![0u8, 1, 2, 1, 2, 3]; + + let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; + gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&pos_buffer)); + gl.buffer_data_with_u8_array(Gl::ARRAY_BUFFER, &positions, Gl::STATIC_DRAW); + gl.bind_buffer(Gl::ARRAY_BUFFER, None); + + let a_pos_loc = gl.get_attrib_location(&program, "a_pos"); + assert!(a_pos_loc >= 0); + let a_pos_loc = a_pos_loc as u32; + + let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; + gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer)); + gl.buffer_data_with_u8_array(Gl::ELEMENT_ARRAY_BUFFER, &indices, Gl::STATIC_DRAW); + gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); + + Ok(PostProcess { + gl, + pos_buffer, + a_pos_loc, + index_buffer, + texture, + texture_size: (width, height), + fbo, + program, + }) + } + + fn begin(&mut self, width: i32, height: i32) -> Result<(), JsValue> { + let gl = &self.gl; + + if (width, height) != self.texture_size { + gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); + gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); + gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + Gl::TEXTURE_2D, + 0, + Gl::RGBA as i32, + width, + height, + 0, + Gl::RGBA, + Gl::UNSIGNED_BYTE, + None, + )?; + gl.bind_texture(Gl::TEXTURE_2D, None); + + self.texture_size = (width, height); + } + + gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&self.fbo)); + Ok(()) } + + fn end(&self) { + let gl = &self.gl; + + gl.bind_framebuffer(Gl::FRAMEBUFFER, None); + gl.disable(Gl::SCISSOR_TEST); + + gl.use_program(Some(&self.program)); + + gl.active_texture(Gl::TEXTURE0); + gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); + let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); + gl.uniform1i(Some(&u_sampler_loc), 0); + + gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer)); + gl.vertex_attrib_pointer_with_i32(self.a_pos_loc, 2, Gl::UNSIGNED_BYTE, false, 0, 0); + gl.enable_vertex_attrib_array(self.a_pos_loc); + + gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer)); + + gl.draw_elements_with_i32(Gl::TRIANGLES, 6, Gl::UNSIGNED_BYTE, 0); + + gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); + gl.bind_buffer(Gl::ARRAY_BUFFER, None); + gl.bind_texture(Gl::TEXTURE_2D, None); + gl.use_program(None); + } +} + +impl Drop for PostProcess { + fn drop(&mut self) { + let gl = &self.gl; + gl.delete_buffer(Some(&self.pos_buffer)); + gl.delete_buffer(Some(&self.index_buffer)); + gl.delete_program(Some(&self.program)); + gl.delete_framebuffer(Some(&self.fbo)); + gl.delete_texture(Some(&self.texture)); + } } fn compile_shader( From 64e41be8ab3c26380dd1b541faed297a3c846d7b Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 16:26:14 +0200 Subject: [PATCH 04/10] Add comments --- egui_web/src/backend.rs | 16 ++++++++-------- egui_web/src/webgl1.rs | 6 ++++++ egui_web/src/webgl2.rs | 2 ++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 34fe6226128..9653c0d0cb5 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -15,14 +15,14 @@ impl WebBackend { pub fn new(canvas_id: &str) -> Result { let ctx = egui::CtxRef::default(); - let painter: Box = - if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { - console_log("Using WebGL2 backend"); - Box::new(webgl2_painter) - } else { - console_log("Falling back to WebGL1 backend"); - Box::new(webgl1::WebGlPainter::new(canvas_id)?) - }; + let painter: Box = Box::new(webgl1::WebGlPainter::new(canvas_id)?); + // if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { + // console_log("Using WebGL2 backend"); + // Box::new(webgl2_painter) + // } else { + // console_log("Falling back to WebGL1 backend"); + // Box::new(webgl1::WebGlPainter::new(canvas_id)?) + // }; Ok(Self { egui_ctx: ctx, diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index 5bde0015824..edba66c6315 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -498,6 +498,8 @@ impl crate::Painter for WebGlPainter { } } +/// Uses a framebuffer to render everything in linear color space and convert it back to sRGB +/// in a separate "post processing" step struct PostProcess { gl: Gl, pos_buffer: WebGlBuffer, @@ -521,6 +523,8 @@ impl PostProcess { gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32); gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32); gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); + // TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB + // Dark colors are slightly wrong when not using SRGB8_ALPHA8 format gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( Gl::TEXTURE_2D, 0, @@ -597,6 +601,8 @@ impl PostProcess { if (width, height) != self.texture_size { gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); + // TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB + // Dark colors are slightly wrong when not using SRGB8_ALPHA8 format gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( Gl::TEXTURE_2D, 0, diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index ef4866b9d63..8512c4b2692 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -498,6 +498,8 @@ impl crate::Painter for WebGl2Painter { } } +/// Uses a framebuffer to render everything in linear color space and convert it back to sRGB +/// in a separate "post processing" step struct PostProcess { gl: Gl, pos_buffer: WebGlBuffer, From 4f948da2f48d46acd53f7be627c3d7bf6e2d18ad Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 16:28:28 +0200 Subject: [PATCH 05/10] Remove color test warning for WebGL backend --- egui_demo_lib/src/apps/color_test.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index f7ea8feccce..873add059d6 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -36,14 +36,6 @@ impl epi::App for ColorTest { fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { egui::CentralPanel::default().show(ctx, |ui| { - if frame.is_web() { - ui.colored_label( - RED, - "NOTE: The WebGL backend does NOT pass the color test." - ); - ui.small("This is because WebGL does not support a linear framebuffer blending (not even WebGL2!).\nMaybe when WebGL3 becomes mainstream in 2030 the web can finally get colors right?"); - ui.separator(); - } ScrollArea::auto_sized().show(ui, |ui| { self.ui(ui, &mut Some(frame.tex_allocator())); }); From 9ed86510ed3b83d8c6ae0248c01befb42f33d384 Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 16:33:05 +0200 Subject: [PATCH 06/10] Add changelog item for fixed alpha blending --- egui_web/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index 207af3dd2f4..8298fea1758 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to the `egui_web` integration will be noted in this file. ## Unreleased +### Fixed 🐛 +* Fix alpha blending, now having identical results as egui_glium + ## 0.14.0 - 2021-08-24 From c0dec5d14fdc663ea63c55d9b346ac21f5f4cbc2 Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 16:39:13 +0200 Subject: [PATCH 07/10] Revert debug change --- egui_web/src/backend.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 9653c0d0cb5..34fe6226128 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -15,14 +15,14 @@ impl WebBackend { pub fn new(canvas_id: &str) -> Result { let ctx = egui::CtxRef::default(); - let painter: Box = Box::new(webgl1::WebGlPainter::new(canvas_id)?); - // if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { - // console_log("Using WebGL2 backend"); - // Box::new(webgl2_painter) - // } else { - // console_log("Falling back to WebGL1 backend"); - // Box::new(webgl1::WebGlPainter::new(canvas_id)?) - // }; + let painter: Box = + if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { + console_log("Using WebGL2 backend"); + Box::new(webgl2_painter) + } else { + console_log("Falling back to WebGL1 backend"); + Box::new(webgl1::WebGlPainter::new(canvas_id)?) + }; Ok(Self { egui_ctx: ctx, From a8c597a6e3fc4608bf2f034ed85111480beea489 Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 17:20:12 +0200 Subject: [PATCH 08/10] cargo fmt --- egui_web/src/webgl1.rs | 21 +++++++++++---------- egui_web/src/webgl2.rs | 19 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index edba66c6315..0bb87f6006d 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -1,7 +1,10 @@ use { js_sys::WebAssembly, wasm_bindgen::{prelude::*, JsCast}, - web_sys::{WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlTexture}, + web_sys::{ + WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, + WebGlTexture, + }, }; use egui::{ @@ -433,7 +436,8 @@ impl crate::Painter for WebGlPainter { let gl = &self.gl; - self.post_process.begin(self.canvas.width() as i32, self.canvas.height() as i32)?; + self.post_process + .begin(self.canvas.width() as i32, self.canvas.height() as i32)?; gl.enable(Gl::SCISSOR_TEST); gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. @@ -513,7 +517,9 @@ struct PostProcess { impl PostProcess { fn new(gl: Gl, width: i32, height: i32) -> Result { - let fbo = gl.create_framebuffer().ok_or("failed to create framebuffer")?; + let fbo = gl + .create_framebuffer() + .ok_or("failed to create framebuffer")?; gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo)); let texture = gl.create_texture().unwrap(); @@ -536,7 +542,7 @@ impl PostProcess { Gl::UNSIGNED_BYTE, None, ) - .unwrap(); + .unwrap(); gl.framebuffer_texture_2d( Gl::FRAMEBUFFER, Gl::COLOR_ATTACHMENT0, @@ -560,12 +566,7 @@ impl PostProcess { )?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - let positions = vec![ - 0u8, 0, - 1, 0, - 0, 1, - 1, 1, - ]; + let positions = vec![0u8, 0, 1, 0, 0, 1, 1, 1]; let indices = vec![0u8, 1, 2, 1, 2, 3]; diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index 8512c4b2692..4cfc71663f0 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -3,7 +3,10 @@ use { js_sys::WebAssembly, wasm_bindgen::{prelude::*, JsCast}, - web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlShader, WebGlTexture, WebGlVertexArrayObject}, + web_sys::{ + WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlShader, + WebGlTexture, WebGlVertexArrayObject, + }, }; use egui::{ @@ -433,7 +436,8 @@ impl crate::Painter for WebGl2Painter { let gl = &self.gl; - self.post_process.begin(self.canvas.width() as i32, self.canvas.height() as i32)?; + self.post_process + .begin(self.canvas.width() as i32, self.canvas.height() as i32)?; gl.enable(Gl::SCISSOR_TEST); gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. @@ -513,7 +517,9 @@ struct PostProcess { impl PostProcess { fn new(gl: Gl, width: i32, height: i32) -> Result { - let fbo = gl.create_framebuffer().ok_or("failed to create framebuffer")?; + let fbo = gl + .create_framebuffer() + .ok_or("failed to create framebuffer")?; gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo)); let texture = gl.create_texture().unwrap(); @@ -561,12 +567,7 @@ impl PostProcess { let vao = gl.create_vertex_array().ok_or("failed to create vao")?; gl.bind_vertex_array(Some(&vao)); - let positions = vec![ - 0u8, 0, - 1, 0, - 0, 1, - 1, 1, - ]; + let positions = vec![0u8, 0, 1, 0, 0, 1, 1, 1]; let indices = vec![0u8, 1, 2, 1, 2, 3]; From 309d3c77683225ff23a4107e25f6a68a775414b6 Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 18:26:16 +0200 Subject: [PATCH 09/10] Correct comment Co-authored-by: Emil Ernerfeldt --- egui_web/src/shader/main_fragment_100es.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/egui_web/src/shader/main_fragment_100es.glsl b/egui_web/src/shader/main_fragment_100es.glsl index 2591e363e54..861a4280fe5 100644 --- a/egui_web/src/shader/main_fragment_100es.glsl +++ b/egui_web/src/shader/main_fragment_100es.glsl @@ -18,7 +18,7 @@ vec4 linear_from_srgba(vec4 srgba) { } void main() { - // We must decode the colors, since WebGL doesn't come with sRGBA textures: + // 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). From 03ae062115c08c7f17d4e5f8af15a980bc9c635e Mon Sep 17 00:00:00 2001 From: AsmPrgmC3 Date: Wed, 25 Aug 2021 20:40:03 +0200 Subject: [PATCH 10/10] Revert changes to WebGL1 backend, add color test warning again --- egui_demo_lib/src/apps/color_test.rs | 7 + egui_web/CHANGELOG.md | 2 +- egui_web/src/shader/fragment_100es.glsl | 58 ++++++ egui_web/src/shader/main_fragment_100es.glsl | 26 --- egui_web/src/shader/post_fragment_100es.glsl | 22 --- egui_web/src/shader/post_vertex_100es.glsl | 8 - ...in_vertex_100es.glsl => vertex_100es.glsl} | 0 egui_web/src/webgl1.rs | 185 +----------------- egui_web/src/webgl2.rs | 2 +- epaint/src/texture_atlas.rs | 15 +- 10 files changed, 84 insertions(+), 241 deletions(-) create mode 100644 egui_web/src/shader/fragment_100es.glsl delete mode 100644 egui_web/src/shader/main_fragment_100es.glsl delete mode 100644 egui_web/src/shader/post_fragment_100es.glsl delete mode 100644 egui_web/src/shader/post_vertex_100es.glsl rename egui_web/src/shader/{main_vertex_100es.glsl => vertex_100es.glsl} (100%) diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index 873add059d6..a8c9c3ebef7 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -36,6 +36,13 @@ impl epi::App for ColorTest { fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { egui::CentralPanel::default().show(ctx, |ui| { + if frame.is_web() { + ui.colored_label( + RED, + "NOTE: The WebGL1 backend does NOT pass the color test. The WebGL2 backend does." + ); + ui.separator(); + } ScrollArea::auto_sized().show(ui, |ui| { self.ui(ui, &mut Some(frame.tex_allocator())); }); diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index 8298fea1758..dfe8f977e0d 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to the `egui_web` integration will be noted in this file. ## Unreleased ### Fixed 🐛 -* Fix alpha blending, now having identical results as egui_glium +* Fix alpha blending for WebGL2 backend, now having identical results as egui_glium ## 0.14.0 - 2021-08-24 diff --git a/egui_web/src/shader/fragment_100es.glsl b/egui_web/src/shader/fragment_100es.glsl new file mode 100644 index 00000000000..5b45edf2dac --- /dev/null +++ b/egui_web/src/shader/fragment_100es.glsl @@ -0,0 +1,58 @@ +precision mediump float; +uniform sampler2D u_sampler; +varying vec4 v_rgba; +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 sRGB from 0-1 linear +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)); +} + +// 0-1 linear from 0-255 sRGBA +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; + + // WebGL 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; + } +} diff --git a/egui_web/src/shader/main_fragment_100es.glsl b/egui_web/src/shader/main_fragment_100es.glsl deleted file mode 100644 index 861a4280fe5..00000000000 --- a/egui_web/src/shader/main_fragment_100es.glsl +++ /dev/null @@ -1,26 +0,0 @@ -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba; -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)); -} - -// 0-1 linear from 0-255 sRGBA -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; -} diff --git a/egui_web/src/shader/post_fragment_100es.glsl b/egui_web/src/shader/post_fragment_100es.glsl deleted file mode 100644 index 001f8a3f236..00000000000 --- a/egui_web/src/shader/post_fragment_100es.glsl +++ /dev/null @@ -1,22 +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.; -} diff --git a/egui_web/src/shader/post_vertex_100es.glsl b/egui_web/src/shader/post_vertex_100es.glsl deleted file mode 100644 index 37280bc1dad..00000000000 --- a/egui_web/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/egui_web/src/shader/main_vertex_100es.glsl b/egui_web/src/shader/vertex_100es.glsl similarity index 100% rename from egui_web/src/shader/main_vertex_100es.glsl rename to egui_web/src/shader/vertex_100es.glsl diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index 0bb87f6006d..b5f6b9d6ee6 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -1,10 +1,7 @@ use { js_sys::WebAssembly, wasm_bindgen::{prelude::*, JsCast}, - web_sys::{ - WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, - WebGlTexture, - }, + web_sys::{WebGlBuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlTexture}, }; use egui::{ @@ -23,7 +20,6 @@ pub struct WebGlPainter { pos_buffer: WebGlBuffer, tc_buffer: WebGlBuffer, color_buffer: WebGlBuffer, - post_process: PostProcess, egui_texture: WebGlTexture, egui_texture_version: Option, @@ -64,12 +60,12 @@ impl WebGlPainter { let vert_shader = compile_shader( &gl, Gl::VERTEX_SHADER, - include_str!("shader/main_vertex_100es.glsl"), + include_str!("shader/vertex_100es.glsl"), )?; let frag_shader = compile_shader( &gl, Gl::FRAGMENT_SHADER, - include_str!("shader/main_fragment_100es.glsl"), + include_str!("shader/fragment_100es.glsl"), )?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?; @@ -78,9 +74,6 @@ impl WebGlPainter { let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?; let color_buffer = gl.create_buffer().ok_or("failed to create color_buffer")?; - let post_process = - PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?; - Ok(WebGlPainter { canvas_id: canvas_id.to_owned(), canvas, @@ -90,7 +83,6 @@ impl WebGlPainter { pos_buffer, tc_buffer, color_buffer, - post_process, egui_texture, egui_texture_version: None, user_textures: Default::default(), @@ -375,7 +367,8 @@ impl crate::Painter for WebGlPainter { } let mut pixels: Vec = Vec::with_capacity(texture.pixels.len() * 4); - for srgba in texture.srgba_pixels() { + let font_gamma = 1.0 / 2.2; // HACK due to non-linear framebuffer blending. + for srgba in texture.srgba_pixels(font_gamma) { pixels.push(srgba.r()); pixels.push(srgba.g()); pixels.push(srgba.b()); @@ -436,9 +429,6 @@ impl crate::Painter for WebGlPainter { let gl = &self.gl; - self.post_process - .begin(self.canvas.width() as i32, self.canvas.height() as i32)?; - gl.enable(Gl::SCISSOR_TEST); gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. gl.enable(Gl::BLEND); @@ -495,175 +485,10 @@ impl crate::Painter for WebGlPainter { )); } } - - self.post_process.end(); - Ok(()) } } -/// Uses a framebuffer to render everything in linear color space and convert it back to sRGB -/// in a separate "post processing" step -struct PostProcess { - gl: Gl, - pos_buffer: WebGlBuffer, - a_pos_loc: u32, - index_buffer: WebGlBuffer, - texture: WebGlTexture, - texture_size: (i32, i32), - fbo: WebGlFramebuffer, - program: WebGlProgram, -} - -impl PostProcess { - fn new(gl: Gl, width: i32, height: i32) -> Result { - let fbo = gl - .create_framebuffer() - .ok_or("failed to create framebuffer")?; - gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo)); - - let texture = gl.create_texture().unwrap(); - gl.bind_texture(Gl::TEXTURE_2D, Some(&texture)); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32); - gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); - // TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB - // Dark colors are slightly wrong when not using SRGB8_ALPHA8 format - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - 0, - Gl::RGBA as i32, - width, - height, - 0, - Gl::RGBA, - Gl::UNSIGNED_BYTE, - None, - ) - .unwrap(); - gl.framebuffer_texture_2d( - Gl::FRAMEBUFFER, - Gl::COLOR_ATTACHMENT0, - Gl::TEXTURE_2D, - Some(&texture), - 0, - ); - - gl.bind_texture(Gl::TEXTURE_2D, None); - gl.bind_framebuffer(Gl::FRAMEBUFFER, None); - - let vert_shader = compile_shader( - &gl, - Gl::VERTEX_SHADER, - include_str!("shader/post_vertex_100es.glsl"), - )?; - let frag_shader = compile_shader( - &gl, - Gl::FRAGMENT_SHADER, - include_str!("shader/post_fragment_100es.glsl"), - )?; - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - - let positions = vec![0u8, 0, 1, 0, 0, 1, 1, 1]; - - let indices = vec![0u8, 1, 2, 1, 2, 3]; - - let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&pos_buffer)); - gl.buffer_data_with_u8_array(Gl::ARRAY_BUFFER, &positions, Gl::STATIC_DRAW); - gl.bind_buffer(Gl::ARRAY_BUFFER, None); - - let a_pos_loc = gl.get_attrib_location(&program, "a_pos"); - assert!(a_pos_loc >= 0); - let a_pos_loc = a_pos_loc as u32; - - let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer)); - gl.buffer_data_with_u8_array(Gl::ELEMENT_ARRAY_BUFFER, &indices, Gl::STATIC_DRAW); - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); - - Ok(PostProcess { - gl, - pos_buffer, - a_pos_loc, - index_buffer, - texture, - texture_size: (width, height), - fbo, - program, - }) - } - - fn begin(&mut self, width: i32, height: i32) -> Result<(), JsValue> { - let gl = &self.gl; - - if (width, height) != self.texture_size { - gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); - gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); - // TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB - // Dark colors are slightly wrong when not using SRGB8_ALPHA8 format - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - 0, - Gl::RGBA as i32, - width, - height, - 0, - Gl::RGBA, - Gl::UNSIGNED_BYTE, - None, - )?; - gl.bind_texture(Gl::TEXTURE_2D, None); - - self.texture_size = (width, height); - } - - gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&self.fbo)); - - Ok(()) - } - - fn end(&self) { - let gl = &self.gl; - - gl.bind_framebuffer(Gl::FRAMEBUFFER, None); - gl.disable(Gl::SCISSOR_TEST); - - gl.use_program(Some(&self.program)); - - gl.active_texture(Gl::TEXTURE0); - gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); - let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); - gl.uniform1i(Some(&u_sampler_loc), 0); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer)); - gl.vertex_attrib_pointer_with_i32(self.a_pos_loc, 2, Gl::UNSIGNED_BYTE, false, 0, 0); - gl.enable_vertex_attrib_array(self.a_pos_loc); - - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer)); - - gl.draw_elements_with_i32(Gl::TRIANGLES, 6, Gl::UNSIGNED_BYTE, 0); - - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); - gl.bind_buffer(Gl::ARRAY_BUFFER, None); - gl.bind_texture(Gl::TEXTURE_2D, None); - gl.use_program(None); - } -} - -impl Drop for PostProcess { - fn drop(&mut self) { - let gl = &self.gl; - gl.delete_buffer(Some(&self.pos_buffer)); - gl.delete_buffer(Some(&self.index_buffer)); - gl.delete_program(Some(&self.program)); - gl.delete_framebuffer(Some(&self.fbo)); - gl.delete_texture(Some(&self.texture)); - } -} - fn compile_shader( gl: &WebGlRenderingContext, shader_type: u32, diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index 4cfc71663f0..479a03ba56e 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -376,7 +376,7 @@ impl crate::Painter for WebGl2Painter { } let mut pixels: Vec = Vec::with_capacity(texture.pixels.len() * 4); - for srgba in texture.srgba_pixels() { + for srgba in texture.srgba_pixels(1.0) { pixels.push(srgba.r()); pixels.push(srgba.g()); pixels.push(srgba.b()); diff --git a/epaint/src/texture_atlas.rs b/epaint/src/texture_atlas.rs index fbc3dd7bbbe..3c45aba7c9b 100644 --- a/epaint/src/texture_atlas.rs +++ b/epaint/src/texture_atlas.rs @@ -17,10 +17,19 @@ impl Texture { } /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom. - pub fn srgba_pixels(&'_ self) -> impl Iterator + '_ { + /// + /// `gamma` should normally be set to 1.0. + /// If you are having problems with egui text looking skinny and pixelated, try + /// setting a lower gamma, e.g. `0.5`. + pub fn srgba_pixels(&'_ self, gamma: f32) -> impl Iterator + '_ { use super::Color32; - let srgba_from_luminance_lut: Vec = - (0..=255).map(Color32::from_white_alpha).collect(); + + let srgba_from_luminance_lut: Vec = (0..=255) + .map(|a| { + let a = super::color::linear_f32_from_linear_u8(a).powf(gamma); + super::Rgba::from_white_alpha(a).into() + }) + .collect(); self.pixels .iter() .map(move |&l| srgba_from_luminance_lut[l as usize])