Reintroduce color fixes to WebGL1 backend, but only if sRGB textures are supported (#652)

Also removed the 300es shaders, as they'd be identical to the 100es ones.
This commit is contained in:
AsmPrgmC3 2021-08-26 18:40:35 +02:00 committed by GitHub
parent c510899019
commit 3a634ff46e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 224 additions and 27 deletions

View file

@ -39,7 +39,7 @@ impl epi::App for ColorTest {
if frame.is_web() { if frame.is_web() {
ui.colored_label( ui.colored_label(
RED, 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(); ui.separator();
} }

View file

@ -6,7 +6,7 @@ All notable changes to the `egui_web` integration will be noted in this file.
## Unreleased ## Unreleased
### Fixed 🐛 ### 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 ## 0.14.0 - 2021-08-24

View file

@ -73,6 +73,7 @@ features = [
"Event", "Event",
"EventListener", "EventListener",
"EventTarget", "EventTarget",
"ExtSRgb",
"File", "File",
"FileList", "FileList",
"FocusEvent", "FocusEvent",

View file

@ -1,7 +1,10 @@
use { use {
js_sys::WebAssembly, js_sys::WebAssembly,
wasm_bindgen::{prelude::*, JsCast}, wasm_bindgen::{prelude::*, JsCast},
web_sys::{WebGlBuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlTexture}, web_sys::{
ExtSRgb, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader,
WebGlTexture,
},
}; };
use egui::{ use egui::{
@ -20,6 +23,8 @@ pub struct WebGlPainter {
pos_buffer: WebGlBuffer, pos_buffer: WebGlBuffer,
tc_buffer: WebGlBuffer, tc_buffer: WebGlBuffer,
color_buffer: WebGlBuffer, color_buffer: WebGlBuffer,
texture_format: u32,
post_process: Option<PostProcess>,
egui_texture: WebGlTexture, egui_texture: WebGlTexture,
egui_texture_version: Option<u64>, egui_texture_version: Option<u64>,
@ -57,6 +62,26 @@ impl WebGlPainter {
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32); 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); gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
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( let vert_shader = compile_shader(
&gl, &gl,
Gl::VERTEX_SHADER, Gl::VERTEX_SHADER,
@ -67,8 +92,11 @@ impl WebGlPainter {
Gl::FRAGMENT_SHADER, Gl::FRAGMENT_SHADER,
include_str!("shader/fragment_100es.glsl"), include_str!("shader/fragment_100es.glsl"),
)?; )?;
let program = link_program(&gl, [vert_shader, frag_shader].iter())?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
(Gl::RGBA, program, None)
};
let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; 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 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")?; let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?;
@ -83,6 +111,8 @@ impl WebGlPainter {
pos_buffer, pos_buffer,
tc_buffer, tc_buffer,
color_buffer, color_buffer,
texture_format,
post_process,
egui_texture, egui_texture,
egui_texture_version: None, egui_texture_version: None,
user_textures: Default::default(), user_textures: Default::default(),
@ -169,11 +199,10 @@ impl WebGlPainter {
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); 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 level = 0;
let internal_format = Gl::RGBA; let internal_format = self.texture_format;
let border = 0; let border = 0;
let src_format = Gl::RGBA; let src_format = self.texture_format;
let src_type = Gl::UNSIGNED_BYTE; 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.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D, Gl::TEXTURE_2D,
@ -366,9 +395,13 @@ impl crate::Painter for WebGlPainter {
return; // No change 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<u8> = Vec::with_capacity(texture.pixels.len() * 4); let mut pixels: Vec<u8> = 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(gamma) {
for srgba in texture.srgba_pixels(font_gamma) {
pixels.push(srgba.r()); pixels.push(srgba.r());
pixels.push(srgba.g()); pixels.push(srgba.g());
pixels.push(srgba.b()); pixels.push(srgba.b());
@ -378,12 +411,10 @@ impl crate::Painter for WebGlPainter {
let gl = &self.gl; let gl = &self.gl;
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_texture)); 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 level = 0;
let internal_format = Gl::RGBA; let internal_format = self.texture_format;
let border = 0; let border = 0;
let src_format = Gl::RGBA; let src_format = self.texture_format;
let src_type = Gl::UNSIGNED_BYTE; 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.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
Gl::TEXTURE_2D, Gl::TEXTURE_2D,
@ -429,6 +460,10 @@ impl crate::Painter for WebGlPainter {
let gl = &self.gl; 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.enable(Gl::SCISSOR_TEST);
gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. gl.disable(Gl::CULL_FACE); // egui is not strict about winding order.
gl.enable(Gl::BLEND); 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(()) 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<PostProcess, JsValue> {
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( fn compile_shader(
gl: &WebGlRenderingContext, gl: &WebGlRenderingContext,
shader_type: u32, shader_type: u32,

View file

@ -66,12 +66,12 @@ impl WebGl2Painter {
let vert_shader = compile_shader( let vert_shader = compile_shader(
&gl, &gl,
Gl::VERTEX_SHADER, Gl::VERTEX_SHADER,
include_str!("shader/main_vertex_300es.glsl"), include_str!("shader/main_vertex_100es.glsl"),
)?; )?;
let frag_shader = compile_shader( let frag_shader = compile_shader(
&gl, &gl,
Gl::FRAGMENT_SHADER, 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())?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?;
@ -555,12 +555,12 @@ impl PostProcess {
let vert_shader = compile_shader( let vert_shader = compile_shader(
&gl, &gl,
Gl::VERTEX_SHADER, Gl::VERTEX_SHADER,
include_str!("shader/post_vertex_300es.glsl"), include_str!("shader/post_vertex_100es.glsl"),
)?; )?;
let frag_shader = compile_shader( let frag_shader = compile_shader(
&gl, &gl,
Gl::FRAGMENT_SHADER, 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())?; let program = link_program(&gl, [vert_shader, frag_shader].iter())?;