diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index a8c9c3eb..5eb7e498 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -39,7 +39,7 @@ impl epi::App for ColorTest { if frame.is_web() { ui.colored_label( RED, - "NOTE: The WebGL1 backend does NOT pass the color test. The WebGL2 backend does." + "NOTE: The WebGL1 backend without sRGB support does NOT pass the color test.", ); ui.separator(); } diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index dfe8f977..fcf7241f 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 for WebGL2 backend, now having identical results as egui_glium +* Fix alpha blending for WebGL2 and WebGL1 with sRGB support backends, now having identical results as egui_glium ## 0.14.0 - 2021-08-24 diff --git a/egui_web/Cargo.toml b/egui_web/Cargo.toml index 7f44d612..6b713250 100644 --- a/egui_web/Cargo.toml +++ b/egui_web/Cargo.toml @@ -73,6 +73,7 @@ features = [ "Event", "EventListener", "EventTarget", + "ExtSRgb", "File", "FileList", "FocusEvent", diff --git a/egui_web/src/shader/main_fragment_300es.glsl b/egui_web/src/shader/main_fragment_100es.glsl similarity index 100% rename from egui_web/src/shader/main_fragment_300es.glsl rename to egui_web/src/shader/main_fragment_100es.glsl diff --git a/egui_web/src/shader/main_vertex_300es.glsl b/egui_web/src/shader/main_vertex_100es.glsl similarity index 100% rename from egui_web/src/shader/main_vertex_300es.glsl rename to egui_web/src/shader/main_vertex_100es.glsl diff --git a/egui_web/src/shader/post_fragment_300es.glsl b/egui_web/src/shader/post_fragment_100es.glsl similarity index 100% rename from egui_web/src/shader/post_fragment_300es.glsl rename to egui_web/src/shader/post_fragment_100es.glsl diff --git a/egui_web/src/shader/post_vertex_300es.glsl b/egui_web/src/shader/post_vertex_100es.glsl similarity index 100% rename from egui_web/src/shader/post_vertex_300es.glsl rename to egui_web/src/shader/post_vertex_100es.glsl diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index b5f6b9d6..9beef3e2 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, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlTexture}, + web_sys::{ + ExtSRgb, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, + WebGlTexture, + }, }; use egui::{ @@ -20,6 +23,8 @@ pub struct WebGlPainter { pos_buffer: WebGlBuffer, tc_buffer: WebGlBuffer, color_buffer: WebGlBuffer, + texture_format: u32, + post_process: Option, egui_texture: WebGlTexture, egui_texture_version: Option, @@ -57,18 +62,41 @@ impl WebGlPainter { gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32); gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32); - let vert_shader = compile_shader( - &gl, - Gl::VERTEX_SHADER, - include_str!("shader/vertex_100es.glsl"), - )?; - let frag_shader = compile_shader( - &gl, - Gl::FRAGMENT_SHADER, - include_str!("shader/fragment_100es.glsl"), - )?; + let srgb_supported = matches!(gl.get_extension("EXT_sRGB"), Ok(Some(_))); + + let (texture_format, program, post_process) = if srgb_supported { + let vert_shader = compile_shader( + &gl, + Gl::VERTEX_SHADER, + include_str!("shader/main_vertex_100es.glsl"), + )?; + let frag_shader = compile_shader( + &gl, + Gl::FRAGMENT_SHADER, + include_str!("shader/main_fragment_100es.glsl"), + )?; + let program = link_program(&gl, [vert_shader, frag_shader].iter())?; + + let post_process = + PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?; + + (ExtSRgb::SRGB_ALPHA_EXT, program, Some(post_process)) + } else { + let vert_shader = compile_shader( + &gl, + Gl::VERTEX_SHADER, + include_str!("shader/vertex_100es.glsl"), + )?; + let frag_shader = compile_shader( + &gl, + Gl::FRAGMENT_SHADER, + include_str!("shader/fragment_100es.glsl"), + )?; + let program = link_program(&gl, [vert_shader, frag_shader].iter())?; + + (Gl::RGBA, program, None) + }; - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?; @@ -83,6 +111,8 @@ impl WebGlPainter { pos_buffer, tc_buffer, color_buffer, + texture_format, + post_process, egui_texture, egui_texture_version: None, user_textures: Default::default(), @@ -169,11 +199,10 @@ impl WebGlPainter { gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); - // TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB let level = 0; - let internal_format = Gl::RGBA; + let internal_format = self.texture_format; let border = 0; - let src_format = Gl::RGBA; + let src_format = self.texture_format; let src_type = Gl::UNSIGNED_BYTE; gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( Gl::TEXTURE_2D, @@ -366,9 +395,13 @@ impl crate::Painter for WebGlPainter { return; // No change } + let gamma = if self.post_process.is_none() { + 1.0 / 2.2 // HACK due to non-linear framebuffer blending. + } else { + 1.0 // post process enables linear blending + }; 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(gamma) { pixels.push(srgba.r()); pixels.push(srgba.g()); pixels.push(srgba.b()); @@ -378,12 +411,10 @@ impl crate::Painter for WebGlPainter { let gl = &self.gl; gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_texture)); - // TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB - // https://www.khronos.org/registry/webgl/extensions/EXT_sRGB/ let level = 0; - let internal_format = Gl::RGBA; + let internal_format = self.texture_format; let border = 0; - let src_format = Gl::RGBA; + let src_format = self.texture_format; let src_type = Gl::UNSIGNED_BYTE; gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( Gl::TEXTURE_2D, @@ -429,6 +460,10 @@ impl crate::Painter for WebGlPainter { let gl = &self.gl; + if let Some(ref mut post_process) = self.post_process { + 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); @@ -485,10 +520,171 @@ impl crate::Painter for WebGlPainter { )); } } + + if let Some(ref post_process) = self.post_process { + 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, + ExtSRgb::SRGB_ALPHA_EXT as i32, + width, + height, + 0, + ExtSRgb::SRGB_ALPHA_EXT, + 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, + ExtSRgb::SRGB_ALPHA_EXT as i32, + width, + height, + 0, + ExtSRgb::SRGB_ALPHA_EXT, + 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 479a03ba..36d80197 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -66,12 +66,12 @@ impl WebGl2Painter { let vert_shader = compile_shader( &gl, Gl::VERTEX_SHADER, - include_str!("shader/main_vertex_300es.glsl"), + include_str!("shader/main_vertex_100es.glsl"), )?; let frag_shader = compile_shader( &gl, Gl::FRAGMENT_SHADER, - include_str!("shader/main_fragment_300es.glsl"), + include_str!("shader/main_fragment_100es.glsl"), )?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?; @@ -555,12 +555,12 @@ impl PostProcess { let vert_shader = compile_shader( &gl, Gl::VERTEX_SHADER, - include_str!("shader/post_vertex_300es.glsl"), + include_str!("shader/post_vertex_100es.glsl"), )?; let frag_shader = compile_shader( &gl, Gl::FRAGMENT_SHADER, - include_str!("shader/post_fragment_300es.glsl"), + include_str!("shader/post_fragment_100es.glsl"), )?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?;