diff --git a/egui_glow/CHANGELOG.md b/egui_glow/CHANGELOG.md index 5cfccf5b5fc..150543b5af7 100644 --- a/egui_glow/CHANGELOG.md +++ b/egui_glow/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the `egui_glow` integration will be noted in this file. ## Unreleased +* Make winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)). * Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)). diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index 6279c884180..6e18afefffd 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -23,18 +23,24 @@ all-features = true [dependencies] egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] } -egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"] } -epi = { version = "0.15.0", path = "../epi", optional = true } +epi = { version = "0.15.0", path = "../epi", optional = true } glow = "0.11" -glutin = "0.27" memoffset = "0.6" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"], optional = true } +glutin = { version = "0.27.0", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +web-sys = { version = "0.3", features=["console"] } +wasm-bindgen = { version = "0.2" } + [dev-dependencies] image = { version = "0.23", default-features = false, features = ["png"] } [features] -default = ["clipboard", "default_fonts", "links", "persistence"] +default = ["clipboard", "default_fonts", "links", "persistence", "winit"] # enable cut/copy/paste to OS clipboard. # if disabled a clipboard will be simulated so you can still copy/paste within the egui app. @@ -58,3 +64,8 @@ persistence = [ # experimental support for a screen reader screen_reader = ["egui-winit/screen_reader"] + +# enable glutin/winit integration. +# if you want to use glow painter on web disable it. +# if disabled reduce crate size and build time. +winit= ["egui-winit","glutin"] \ No newline at end of file diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 9debd933bbb..8191801dbf3 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -1,24 +1,7 @@ use crate::*; -use egui::Color32; #[cfg(target_os = "windows")] use glutin::platform::windows::WindowBuilderExtWindows; -impl epi::TextureAllocator for Painter { - fn alloc_srgba_premultiplied( - &mut self, - size: (usize, usize), - srgba_pixels: &[Color32], - ) -> egui::TextureId { - let id = self.alloc_user_texture(); - self.set_user_texture(id, size, srgba_pixels); - id - } - - fn free(&mut self, id: egui::TextureId) { - self.free_user_texture(id); - } -} - struct RequestRepaintEvent; struct GlowRepaintSignal(std::sync::Mutex>); @@ -77,7 +60,9 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { event_loop.create_proxy(), ))); - let mut painter = crate::Painter::new(&gl); + let mut painter = crate::Painter::new(&gl, None) + .map_err(|error| eprintln!("some OpenGL error occurred {}\n", error)) + .unwrap(); let mut integration = egui_winit::epi::EpiIntegration::new( "egui_glow", gl_window.window(), @@ -111,13 +96,12 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { gl.clear_color(color[0], color[1], color[2], color[3]); gl.clear(glow::COLOR_BUFFER_BIT); } - + painter.upload_egui_texture(&gl, &integration.egui_ctx.texture()); painter.paint_meshes( - &gl_window, + gl_window.window().inner_size().into(), &gl, integration.egui_ctx.pixels_per_point(), clipped_meshes, - &integration.egui_ctx.texture(), ); gl_window.swap_buffers().unwrap(); diff --git a/egui_glow/src/lib.rs b/egui_glow/src/lib.rs index bc03f21e104..4f10e579efb 100644 --- a/egui_glow/src/lib.rs +++ b/egui_glow/src/lib.rs @@ -87,25 +87,32 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -mod painter; +pub mod painter; +pub use glow; pub use painter::Painter; - -#[cfg(feature = "epi")] +#[cfg(feature = "winit")] mod epi_backend; -#[cfg(feature = "epi")] -pub use epi_backend::{run, NativeOptions}; +mod misc_util; +mod post_process; +mod shader_version; +mod vao_emulate; +#[cfg(not(target_arch = "wasm32"))] pub use egui_winit; +#[cfg(all(feature = "epi", feature = "winit"))] +pub use epi_backend::{run, NativeOptions}; // ---------------------------------------------------------------------------- /// Use [`egui`] from a [`glow`] app. +#[cfg(feature = "winit")] pub struct EguiGlow { pub egui_ctx: egui::CtxRef, pub egui_winit: egui_winit::State, pub painter: crate::Painter, } +#[cfg(feature = "winit")] impl EguiGlow { pub fn new( gl_window: &glutin::WindowedContext, @@ -114,7 +121,11 @@ impl EguiGlow { Self { egui_ctx: Default::default(), egui_winit: egui_winit::State::new(gl_window.window()), - painter: crate::Painter::new(gl), + painter: crate::Painter::new(gl, None) + .map_err(|error| { + eprintln!("some error occurred in initializing painter\n{}", error); + }) + .unwrap(), } } @@ -158,12 +169,14 @@ impl EguiGlow { shapes: Vec, ) { let clipped_meshes = self.egui_ctx.tessellate(shapes); + let dimensions: [u32; 2] = gl_window.window().inner_size().into(); + self.painter + .upload_egui_texture(gl, &self.egui_ctx.texture()); self.painter.paint_meshes( - gl_window, + dimensions, gl, self.egui_ctx.pixels_per_point(), clipped_meshes, - &self.egui_ctx.texture(), ); } diff --git a/egui_glow/src/misc_util.rs b/egui_glow/src/misc_util.rs new file mode 100644 index 00000000000..c6e6923c0b1 --- /dev/null +++ b/egui_glow/src/misc_util.rs @@ -0,0 +1,231 @@ +#![allow(unsafe_code)] +use glow::HasContext; +use std::option::Option::Some; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; + +pub(crate) fn srgbtexture2d( + gl: &glow::Context, + is_webgl_1: bool, + srgb_support: bool, + data: &[u8], + w: usize, + h: usize, +) -> glow::Texture { + assert_eq!(data.len(), w * h * 4); + assert!(w >= 1); + assert!(h >= 1); + unsafe { + let tex = gl.create_texture().unwrap(); + gl.bind_texture(glow::TEXTURE_2D, Some(tex)); + + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as i32, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as i32, + ); + 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, + ); + if is_webgl_1 { + let format = if srgb_support { + glow::SRGB_ALPHA + } else { + glow::RGBA + }; + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + format as i32, + w as i32, + h as i32, + 0, + format, + glow::UNSIGNED_BYTE, + Some(data), + ); + } else { + gl.tex_storage_2d(glow::TEXTURE_2D, 1, glow::SRGB8_ALPHA8, w as i32, h as i32); + gl.tex_sub_image_2d( + glow::TEXTURE_2D, + 0, + 0, + 0, + w as i32, + h as i32, + glow::RGBA, + glow::UNSIGNED_BYTE, + glow::PixelUnpackData::Slice(data), + ); + } + assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!"); + tex + } +} + +pub(crate) unsafe fn as_u8_slice(s: &[T]) -> &[u8] { + std::slice::from_raw_parts(s.as_ptr().cast::(), s.len() * std::mem::size_of::()) +} + +#[cfg(target_arch = "wasm32")] +pub(crate) fn glow_debug_print(s: impl Into) { + web_sys::console::log_1(&s.into()); +} +#[cfg(not(target_arch = "wasm32"))] +pub(crate) fn glow_debug_print(s: impl std::fmt::Display) { + println!("{}", s); +} + +pub(crate) unsafe fn compile_shader( + gl: &glow::Context, + shader_type: u32, + source: &str, +) -> Result { + let shader = gl.create_shader(shader_type)?; + + gl.shader_source(shader, source); + + gl.compile_shader(shader); + + if gl.get_shader_compile_status(shader) { + Ok(shader) + } else { + Err(gl.get_shader_info_log(shader)) + } +} + +pub(crate) unsafe fn link_program<'a, T: IntoIterator>( + gl: &glow::Context, + shaders: T, +) -> Result { + let program = gl.create_program()?; + + for shader in shaders { + gl.attach_shader(program, *shader); + } + + gl.link_program(program); + + if gl.get_program_link_status(program) { + Ok(program) + } else { + Err(gl.get_program_info_log(program)) + } +} +///Wrapper around Emulated VAO and GL's VAO +pub(crate) enum VAO { + Emulated(crate::vao_emulate::EmulatedVao), + Native(crate::glow::VertexArray), +} + +impl VAO { + pub(crate) unsafe fn native(gl: &glow::Context) -> Self { + Self::Native(gl.create_vertex_array().unwrap()) + } + + pub(crate) unsafe fn emulated() -> Self { + Self::Emulated(crate::vao_emulate::EmulatedVao::new()) + } + + pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) { + match self { + VAO::Emulated(vao) => vao.bind_vertex_array(gl), + VAO::Native(vao) => gl.bind_vertex_array(Some(*vao)), + } + } + + pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) { + match self { + VAO::Emulated(vao) => vao.bind_buffer(buffer), + VAO::Native(_) => gl.bind_buffer(glow::ARRAY_BUFFER, Some(*buffer)), + } + } + + pub(crate) unsafe fn add_new_attribute( + &mut self, + gl: &glow::Context, + buffer_info: crate::vao_emulate::BufferInfo, + ) { + match self { + VAO::Emulated(vao) => vao.add_new_attribute(buffer_info), + VAO::Native(_) => { + gl.vertex_attrib_pointer_f32( + buffer_info.location, + buffer_info.vector_size, + buffer_info.data_type, + buffer_info.normalized, + buffer_info.stride, + buffer_info.offset, + ); + gl.enable_vertex_attrib_array(buffer_info.location); + } + } + } + + pub(crate) unsafe fn unbind_vertex_array(&self, gl: &glow::Context) { + match self { + VAO::Emulated(vao) => vao.unbind_vertex_array(gl), + VAO::Native(_) => { + gl.bind_vertex_array(None); + } + } + } +} + +pub(crate) unsafe fn need_to_emulate_vao(gl: &glow::Context) -> bool { + let web_sig = "WebGL "; + let es_sig = "OpenGL ES "; + let version_string = gl.get_parameter_string(glow::VERSION); + if let Some(pos) = version_string.rfind(web_sig) { + let version_str = &version_string[pos + web_sig.len()..]; + glow_debug_print(format!( + "detected WebGL prefix at {}:{}", + pos + web_sig.len(), + version_str + )); + if version_str.contains("1.0") { + //need to test OES_vertex_array_object . + gl.supported_extensions() + .contains("OES_vertex_array_object") + } else { + false + } + } else if let Some(pos) = version_string.rfind(es_sig) { + //glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL + glow_debug_print(format!( + "detected OpenGL ES prefix at {}:{}", + pos + es_sig.len(), + &version_string[pos + es_sig.len()..] + )); + if version_string.contains("2.0") { + //need to test OES_vertex_array_object . + gl.supported_extensions() + .contains("OES_vertex_array_object") + } else { + false + } + } else { + glow_debug_print(format!("detected OpenGL:{}", version_string)); + //from OpenGL 3 vao into core + if version_string.starts_with('2') { + // I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object + // but APPLE's and ATI's very old extension. + gl.supported_extensions() + .contains("ARB_vertex_array_object") + } else { + false + } + } +} diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 7ee250aa511..f92be73182a 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -3,262 +3,194 @@ use egui::{ emath::Rect, epaint::{Color32, Mesh, Vertex}, + TextureId, }; +pub use glow::Context; + use memoffset::offset_of; -use std::convert::TryInto; +use glow::HasContext; -use glow::HasContext as _; +use crate::misc_util::{ + as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d, +}; +use crate::post_process::PostProcess; +use crate::shader_version::ShaderVersion; +use crate::vao_emulate; const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); -fn srgbtexture2d(gl: &glow::Context, data: &[u8], w: usize, h: usize) -> glow::NativeTexture { - assert_eq!(data.len(), w * h * 4); - assert!(w >= 1); - assert!(h >= 1); - unsafe { - let tex = gl.create_texture().unwrap(); - gl.bind_texture(glow::TEXTURE_2D, Some(tex)); - - // The texture coordinates for text are so that both nearest and linear should work with the egui font texture. - // For user textures linear sampling is more likely to be the right choice. - - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::LINEAR as i32, - ); - 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_storage_2d(glow::TEXTURE_2D, 1, glow::SRGB8_ALPHA8, w as i32, h as i32); - gl.tex_sub_image_2d( - glow::TEXTURE_2D, - 0, - 0, - 0, - w as i32, - h as i32, - glow::RGBA, - glow::UNSIGNED_BYTE, - glow::PixelUnpackData::Slice(data), - ); - - assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!"); - tex - } -} - -unsafe fn as_u8_slice(s: &[T]) -> &[u8] { - std::slice::from_raw_parts(s.as_ptr().cast::(), s.len() * std::mem::size_of::()) -} - /// OpenGL painter /// /// This struct must be destroyed with [`Painter::destroy`] before dropping, to ensure OpenGL /// objects have been properly deleted and are not leaked. pub struct Painter { - program: glow::NativeProgram, + program: glow::Program, u_screen_size: glow::UniformLocation, u_sampler: glow::UniformLocation, - egui_texture: Option, + egui_texture: Option, egui_texture_version: Option, - + is_webgl_1: bool, + is_embedded: bool, + vertex_array: crate::misc_util::VAO, + srgb_support: bool, /// `None` means unallocated (freed) slot. - user_textures: Vec>, - - vertex_array: glow::NativeVertexArray, - vertex_buffer: glow::NativeBuffer, - element_array_buffer: glow::NativeBuffer, + pub(crate) user_textures: Vec>, + post_process: Option, + vertex_buffer: glow::Buffer, + element_array_buffer: glow::Buffer, // Stores outdated OpenGL textures that are yet to be deleted - old_textures: Vec, + old_textures: Vec, // Only used in debug builds, to make sure we are destroyed correctly. destroyed: bool, } #[derive(Default)] -struct UserTexture { +pub(crate) struct UserTexture { /// Pending upload (will be emptied later). /// This is the format glow likes. - data: Vec, - size: (usize, usize), + pub(crate) data: Vec, + pub(crate) size: (usize, usize), /// Lazily uploaded - gl_texture: Option, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[allow(dead_code)] -enum ShaderVersion { - Gl120, - Gl140, - Es100, - Es300, -} - -impl ShaderVersion { - fn get(gl: &glow::Context) -> Self { - Self::parse(unsafe { &gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }) - } - - #[inline] - fn parse(glsl_ver: &str) -> Self { - let start = glsl_ver.find(|c| char::is_ascii_digit(&c)).unwrap(); - let es = glsl_ver[..start].contains(" ES "); - let ver = glsl_ver[start..].splitn(2, ' ').next().unwrap(); - let [maj, min]: [u8; 2] = ver - .splitn(3, '.') - .take(2) - .map(|x| x.parse().unwrap_or_default()) - .collect::>() - .try_into() - .unwrap(); - if es { - if maj >= 3 { - Self::Es300 - } else { - Self::Es100 - } - } else if maj > 1 || (maj == 1 && min >= 40) { - Self::Gl140 - } else { - Self::Gl120 - } - } - - fn version(&self) -> &'static str { - match self { - Self::Gl120 => "#version 120\n", - Self::Gl140 => "#version 140\n", - Self::Es100 => "#version 100\n", - Self::Es300 => "#version 300 es\n", - } - } -} - -#[test] -fn test_shader_version() { - use ShaderVersion::{Es100, Es300, Gl120, Gl140}; - for (s, v) in [ - ("1.2 OpenGL foo bar", Gl120), - ("3.0", Gl140), - ("0.0", Gl120), - ("OpenGL ES GLSL 3.00 (WebGL2)", Es300), - ("OpenGL ES GLSL 1.00 (WebGL)", Es100), - ("OpenGL ES GLSL ES 1.00 foo bar", Es100), - ("WebGL GLSL ES 3.00 foo bar", Es300), - ("WebGL GLSL ES 3.00", Es300), - ("WebGL GLSL ES 1.0 foo bar", Es100), - ] { - assert_eq!(ShaderVersion::parse(s), v); - } + pub(crate) gl_texture: Option, } impl Painter { - pub fn new(gl: &glow::Context) -> Painter { - let header = ShaderVersion::get(gl).version(); + /// Create painter. + /// + /// Set `pp_fb_extent` to the framebuffer size to enable `sRGB` support on OpenGL ES and WebGL. + /// # Errors + /// will return `Err` below cases + /// * failed to compile shader + /// * failed to create postprocess on webgl with `sRGB` support + /// * failed to create buffer + pub fn new(gl: &glow::Context, pp_fb_extent: Option<[i32; 2]>) -> Result { + let need_to_emulate_vao = unsafe { crate::misc_util::need_to_emulate_vao(gl) }; + let shader_version = ShaderVersion::get(gl); + let is_webgl_1 = shader_version == ShaderVersion::Es100; + let header = shader_version.version(); + glow_debug_print(header); + let srgb_support = gl.supported_extensions().contains("EXT_sRGB"); + let (post_process, srgb_support_define) = match (shader_version, srgb_support) { + //WebGL2 support sRGB default + (ShaderVersion::Es300, _) | (ShaderVersion::Es100, true) => unsafe { + //Add sRGB support marker for fragment shader + if let Some([width, height]) = pp_fb_extent { + glow_debug_print("WebGL with sRGB enabled so turn on post process"); + //install post process to correct sRGB color + ( + Some(PostProcess::new( + gl, + need_to_emulate_vao, + is_webgl_1, + width, + height, + )?), + "#define SRGB_SUPPORTED", + ) + } else { + glow_debug_print("WebGL or OpenGL ES detected but PostProcess disabled because dimension is None"); + (None, "") + } + }, + //WebGL1 without sRGB support disable postprocess and use fallback shader + (ShaderVersion::Es100, false) => (None, ""), + //OpenGL 2.1 or above always support sRGB so add sRGB support marker + _ => (None, "#define SRGB_SUPPORTED"), + }; unsafe { - let vert = gl.create_shader(glow::VERTEX_SHADER).unwrap(); - gl.shader_source(vert, &format!("{}\n{}", header, VERT_SRC)); - gl.compile_shader(vert); - if !gl.get_shader_compile_status(vert) { - panic!( - "Failed to compile vertex shader: {}", - gl.get_shader_info_log(vert) - ); - } - - let frag = gl.create_shader(glow::FRAGMENT_SHADER).unwrap(); - gl.shader_source(frag, &format!("{}\n{}", header, FRAG_SRC)); - gl.compile_shader(frag); - if !gl.get_shader_compile_status(frag) { - panic!( - "Failed to compile fragment shader: {}", - gl.get_shader_info_log(frag) - ); - } - - let program = gl.create_program().unwrap(); - gl.attach_shader(program, vert); - gl.attach_shader(program, frag); - gl.link_program(program); - if !gl.get_program_link_status(program) { - panic!("{}", gl.get_program_info_log(program)); - } + let vert = compile_shader( + gl, + glow::VERTEX_SHADER, + &format!( + "{}\n{}\n{}", + header, + shader_version.is_new_shader_interface(), + VERT_SRC + ), + )?; + let frag = compile_shader( + gl, + glow::FRAGMENT_SHADER, + &format!( + "{}\n{}\n{}\n{}", + header, + srgb_support_define, + shader_version.is_new_shader_interface(), + FRAG_SRC + ), + )?; + let program = link_program(gl, [vert, frag].iter())?; gl.detach_shader(program, vert); gl.detach_shader(program, frag); gl.delete_shader(vert); gl.delete_shader(frag); - let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap(); let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap(); - - let vertex_array = gl.create_vertex_array().unwrap(); - let vertex_buffer = gl.create_buffer().unwrap(); - let element_array_buffer = gl.create_buffer().unwrap(); - - gl.bind_vertex_array(Some(vertex_array)); + let vertex_buffer = gl.create_buffer()?; + let element_array_buffer = gl.create_buffer()?; gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); - let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap(); let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap(); let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap(); - - gl.vertex_attrib_pointer_f32( - a_pos_loc, - 2, - glow::FLOAT, - false, - std::mem::size_of::() as i32, - offset_of!(Vertex, pos) as i32, - ); - gl.enable_vertex_attrib_array(a_pos_loc); - - gl.vertex_attrib_pointer_f32( - a_tc_loc, - 2, - glow::FLOAT, - false, - std::mem::size_of::() as i32, - offset_of!(Vertex, uv) as i32, - ); - gl.enable_vertex_attrib_array(a_tc_loc); - - gl.vertex_attrib_pointer_f32( - a_srgba_loc, - 4, - glow::UNSIGNED_BYTE, - false, - std::mem::size_of::() as i32, - offset_of!(Vertex, color) as i32, - ); - gl.enable_vertex_attrib_array(a_srgba_loc); + let mut vertex_array = if need_to_emulate_vao { + crate::misc_util::VAO::emulated() + } else { + crate::misc_util::VAO::native(gl) + }; + vertex_array.bind_vertex_array(gl); + vertex_array.bind_buffer(gl, &vertex_buffer); + let stride = std::mem::size_of::() as i32; + let position_buffer_info = vao_emulate::BufferInfo { + location: a_pos_loc, + vector_size: 2, + data_type: glow::FLOAT, + normalized: false, + stride, + offset: offset_of!(Vertex, pos) as i32, + }; + let tex_coord_buffer_info = vao_emulate::BufferInfo { + location: a_tc_loc, + vector_size: 2, + data_type: glow::FLOAT, + normalized: false, + stride, + offset: offset_of!(Vertex, uv) as i32, + }; + let color_buffer_info = vao_emulate::BufferInfo { + location: a_srgba_loc, + vector_size: 4, + data_type: glow::UNSIGNED_BYTE, + normalized: false, + stride, + offset: offset_of!(Vertex, color) as i32, + }; + vertex_array.add_new_attribute(gl, position_buffer_info); + vertex_array.add_new_attribute(gl, tex_coord_buffer_info); + vertex_array.add_new_attribute(gl, color_buffer_info); assert_eq!(gl.get_error(), glow::NO_ERROR, "OpenGL error occurred!"); - Painter { + Ok(Painter { program, u_screen_size, u_sampler, egui_texture: None, egui_texture_version: None, - user_textures: Default::default(), + is_webgl_1, + is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300), vertex_array, + srgb_support, + user_textures: Default::default(), + post_process, vertex_buffer, element_array_buffer, old_textures: Vec::new(), destroyed: false, - } + }) } } @@ -268,16 +200,26 @@ impl Painter { if self.egui_texture_version == Some(texture.version) { return; // No change } - + let gamma = if self.is_embedded && self.post_process.is_none() { + 1.0 / 2.2 + } else { + 1.0 + }; let pixels: Vec = texture - .pixels - .iter() - .flat_map(|a| Vec::from(Color32::from_white_alpha(*a).to_array())) + .srgba_pixels(gamma) + .flat_map(|a| Vec::from(a.to_array())) .collect(); if let Some(old_tex) = std::mem::replace( &mut self.egui_texture, - Some(srgbtexture2d(gl, &pixels, texture.width, texture.height)), + Some(srgbtexture2d( + gl, + self.is_webgl_1, + self.srgb_support, + &pixels, + texture.width, + texture.height, + )), ) { unsafe { gl.delete_texture(old_tex); @@ -288,7 +230,7 @@ impl Painter { unsafe fn prepare_painting( &mut self, - gl_window: &glutin::WindowedContext, + [width_in_pixels, height_in_pixels]: [u32; 2], gl: &glow::Context, pixels_per_point: f32, ) -> (u32, u32) { @@ -308,10 +250,6 @@ impl Painter { glow::ONE, ); - let glutin::dpi::PhysicalSize { - width: width_in_pixels, - height: height_in_pixels, - } = gl_window.window().inner_size(); let width_in_points = width_in_pixels as f32 / pixels_per_point; let height_in_points = height_in_pixels as f32 / pixels_per_point; @@ -322,9 +260,8 @@ impl Painter { gl.uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points); gl.uniform_1_i32(Some(&self.u_sampler), 0); gl.active_texture(glow::TEXTURE0); + self.vertex_array.bind_vertex_array(gl); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); (width_in_pixels, height_in_pixels) @@ -341,8 +278,7 @@ impl Painter { /// /// The scissor area and blend parameters will be changed. /// - /// As well as this, the following objects will be rebound: - /// - Vertex Array + /// As well as this, the following objects will be unset: /// - Vertex Buffer /// - Element Buffer /// - Texture (and active texture will be set to 0) @@ -352,27 +288,33 @@ impl Painter { /// of the effects your program might have on this code. Look at the source if in doubt. pub fn paint_meshes( &mut self, - gl_window: &glutin::WindowedContext, + inner_size: [u32; 2], gl: &glow::Context, pixels_per_point: f32, clipped_meshes: Vec, - egui_texture: &egui::Texture, ) { + //chimera of egui_glow and egui_web self.assert_not_destroyed(); - self.upload_egui_texture(gl, egui_texture); self.upload_pending_user_textures(gl); - - let size_in_pixels = unsafe { self.prepare_painting(gl_window, gl, pixels_per_point) }; + if let Some(ref mut post_process) = self.post_process { + unsafe { + post_process.begin(gl, inner_size[0] as i32, inner_size[1] as i32); + } + } + let size_in_pixels = unsafe { self.prepare_painting(inner_size, gl, pixels_per_point) }; for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { self.paint_mesh(gl, size_in_pixels, pixels_per_point, clip_rect, &mesh); } + unsafe { + self.vertex_array.unbind_vertex_array(gl); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - assert_eq!( - unsafe { gl.get_error() }, - glow::NO_ERROR, - "OpenGL error occurred!" - ); + if let Some(ref post_process) = self.post_process { + post_process.end(gl); + } + assert_eq!(glow::NO_ERROR, gl.get_error(), "GL error occurred!"); + } } #[inline(never)] // Easier profiling @@ -385,15 +327,16 @@ impl Painter { mesh: &Mesh, ) { debug_assert!(mesh.is_valid()); - if let Some(texture) = self.get_texture(mesh.texture_id) { unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); gl.buffer_data_u8_slice( glow::ARRAY_BUFFER, as_u8_slice(mesh.vertices.as_slice()), glow::STREAM_DRAW, ); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); gl.buffer_data_u8_slice( glow::ELEMENT_ARRAY_BUFFER, as_u8_slice(mesh.indices.as_slice()), @@ -402,7 +345,6 @@ impl Painter { gl.bind_texture(glow::TEXTURE_2D, Some(texture)); } - // Transform clip rect to physical pixels: let clip_min_x = pixels_per_point * clip_rect.min.x; let clip_min_y = pixels_per_point * clip_rect.min.y; @@ -457,7 +399,8 @@ impl Painter { /// register glow texture as egui texture /// Usable for render to image rectangle - pub fn register_glow_texture(&mut self, texture: glow::NativeTexture) -> egui::TextureId { + #[allow(clippy::needless_pass_by_value)] + pub fn register_glow_texture(&mut self, texture: glow::Texture) -> egui::TextureId { self.assert_not_destroyed(); let id = self.alloc_user_texture(); @@ -488,11 +431,10 @@ impl Painter { pixels: &[Color32], ) { self.assert_not_destroyed(); - assert_eq!( size.0 * size.1, pixels.len(), - "Mismatch between texture size and texel count" + "Mismatch between size and texel count" ); if let egui::TextureId::User(id) = id { @@ -530,7 +472,7 @@ impl Painter { } } - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option { + pub fn get_texture(&self, texture_id: egui::TextureId) -> Option { self.assert_not_destroyed(); match texture_id { @@ -547,6 +489,8 @@ impl Painter { let data = std::mem::take(&mut user_texture.data); user_texture.gl_texture = Some(srgbtexture2d( gl, + self.is_webgl_1, + self.srgb_support, &data, user_texture.size.0, user_texture.size.1, @@ -571,7 +515,6 @@ impl Painter { gl.delete_texture(t); } } - gl.delete_vertex_array(self.vertex_array); gl.delete_buffer(self.vertex_buffer); gl.delete_buffer(self.element_array_buffer); for t in &self.old_textures { @@ -581,24 +524,39 @@ impl Painter { /// This function must be called before Painter is dropped, as Painter has some OpenGL objects /// that should be deleted. - pub fn destroy(&mut self, gl: &glow::Context) { - debug_assert!(!self.destroyed, "Only destroy the egui glow painter once!"); + pub fn destroy(&mut self, gl: &glow::Context) { + debug_assert!(!self.destroyed, "Only destroy once!"); unsafe { self.destroy_gl(gl); + if let Some(ref post_process) = self.post_process { + post_process.destroy(gl); + } } - self.destroyed = true; } fn assert_not_destroyed(&self) { - debug_assert!( - !self.destroyed, - "the egui glow painter has already been destroyed!" - ); + debug_assert!(!self.destroyed, "the egui glow has already been destroyed!"); } } +// ported from egui_web +pub fn clear(gl: &glow::Context, dimension: [u32; 2], clear_color: egui::Rgba) { + unsafe { + gl.disable(glow::SCISSOR_TEST); + + gl.viewport(0, 0, dimension[0] as i32, dimension[1] as i32); + let clear_color: Color32 = clear_color.into(); + gl.clear_color( + clear_color[0] as f32 / 255.0, + clear_color[1] as f32 / 255.0, + clear_color[2] as f32 / 255.0, + clear_color[3] as f32 / 255.0, + ); + gl.clear(glow::COLOR_BUFFER_BIT); + } +} impl Drop for Painter { fn drop(&mut self) { debug_assert!( @@ -607,3 +565,39 @@ impl Drop for Painter { ); } } + +impl epi::TextureAllocator for Painter { + fn alloc_srgba_premultiplied( + &mut self, + size: (usize, usize), + srgba_pixels: &[Color32], + ) -> egui::TextureId { + let id = self.alloc_user_texture(); + self.set_user_texture(id, size, srgba_pixels); + id + } + + fn free(&mut self, id: egui::TextureId) { + self.free_user_texture(id); + } +} + +impl epi::NativeTexture for Painter { + type Texture = glow::Texture; + + fn register_native_texture(&mut self, native: Self::Texture) -> TextureId { + self.register_glow_texture(native) + } + + fn replace_native_texture(&mut self, id: TextureId, replacing: Self::Texture) { + if let egui::TextureId::User(id) = id { + if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { + *user_texture = UserTexture { + data: vec![], + gl_texture: Some(replacing), + size: (0, 0), + }; + } + } + } +} diff --git a/egui_glow/src/post_process.rs b/egui_glow/src/post_process.rs new file mode 100644 index 00000000000..4c85ff9d2b1 --- /dev/null +++ b/egui_glow/src/post_process.rs @@ -0,0 +1,222 @@ +#![allow(unsafe_code)] +use crate::misc_util::{compile_shader, link_program}; +use crate::vao_emulate::BufferInfo; +use glow::HasContext; + +/// 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 { + pos_buffer: glow::Buffer, + index_buffer: glow::Buffer, + vertex_array: crate::misc_util::VAO, + is_webgl_1: bool, + texture: glow::Texture, + texture_size: (i32, i32), + fbo: glow::Framebuffer, + program: glow::Program, +} + +impl PostProcess { + pub(crate) unsafe fn new( + gl: &glow::Context, + need_to_emulate_vao: bool, + is_webgl_1: bool, + width: i32, + height: i32, + ) -> Result { + let fbo = gl.create_framebuffer()?; + + gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); + + let texture = gl.create_texture().unwrap(); + + gl.bind_texture(glow::TEXTURE_2D, Some(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, + ); + + gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); + + 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, + ); + let error_code = gl.get_error(); + assert_eq!( + error_code, + glow::NO_ERROR, + "Error occurred in post process texture initialization. code : 0x{:x}", + error_code + ); + + gl.framebuffer_texture_2d( + glow::FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + glow::TEXTURE_2D, + Some(texture), + 0, + ); + gl.bind_texture(glow::TEXTURE_2D, None); + gl.bind_framebuffer(glow::FRAMEBUFFER, None); + + let vert_shader = compile_shader( + gl, + glow::VERTEX_SHADER, + include_str!("shader/post_vertex_100es.glsl"), + )?; + let frag_shader = compile_shader( + gl, + glow::FRAGMENT_SHADER, + include_str!("shader/post_fragment_100es.glsl"), + )?; + let program = link_program(gl, [vert_shader, frag_shader].iter())?; + + let positions = vec![0.0f32, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]; + + let indices = vec![0u8, 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, + crate::misc_util::as_u8_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_string())?; + let mut vertex_array = if need_to_emulate_vao { + crate::misc_util::VAO::emulated() + } else { + crate::misc_util::VAO::native(gl) + }; + vertex_array.bind_vertex_array(gl); + vertex_array.bind_buffer(gl, &pos_buffer); + let buffer_info_a_pos = BufferInfo { + location: a_pos_loc, + vector_size: 2, + data_type: glow::FLOAT, + normalized: false, + stride: 0, + offset: 0, + }; + vertex_array.add_new_attribute(gl, buffer_info_a_pos); + + 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); + let error_code = gl.get_error(); + assert_eq!( + error_code, + glow::NO_ERROR, + "Error occurred in post process initialization. code : 0x{:x}", + error_code + ); + + Ok(PostProcess { + pos_buffer, + index_buffer, + vertex_array, + is_webgl_1, + texture, + texture_size: (width, height), + fbo, + program, + }) + } + + pub(crate) unsafe fn begin(&mut self, gl: &glow::Context, width: i32, height: i32) { + if (width, height) != self.texture_size { + gl.bind_texture(glow::TEXTURE_2D, Some(self.texture)); + 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) + }; + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + internal_format as i32, + width, + height, + 0, + format, + glow::UNSIGNED_BYTE, + None, + ); + + gl.bind_texture(glow::TEXTURE_2D, None); + self.texture_size = (width, height); + } + + gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); + gl.clear_color(0.0, 0.0, 0.0, 0.0); + gl.clear(glow::COLOR_BUFFER_BIT); + } + + pub(crate) unsafe fn end(&self, gl: &glow::Context) { + gl.bind_framebuffer(glow::FRAMEBUFFER, None); + gl.disable(glow::SCISSOR_TEST); + + gl.use_program(Some(self.program)); + + gl.active_texture(glow::TEXTURE0); + gl.bind_texture(glow::TEXTURE_2D, Some(self.texture)); + let u_sampler_loc = gl.get_uniform_location(self.program, "u_sampler").unwrap(); + gl.uniform_1_i32(Some(&u_sampler_loc), 0); + self.vertex_array.bind_vertex_array(gl); + + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); + gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0); + self.vertex_array.unbind_vertex_array(gl); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); + gl.bind_texture(glow::TEXTURE_2D, None); + gl.use_program(None); + } + pub(crate) unsafe fn destroy(&self, gl: &glow::Context) { + gl.delete_buffer(self.pos_buffer); + gl.delete_buffer(self.index_buffer); + gl.delete_program(self.program); + gl.delete_framebuffer(self.fbo); + gl.delete_texture(self.texture); + } +} diff --git a/egui_glow/src/shader/fragment.glsl b/egui_glow/src/shader/fragment.glsl index e7c3c95d297..288cdf0cda2 100644 --- a/egui_glow/src/shader/fragment.glsl +++ b/egui_glow/src/shader/fragment.glsl @@ -3,71 +3,75 @@ precision mediump float; #endif uniform sampler2D u_sampler; -#if defined(GL_ES) || __VERSION__ < 140 -varying vec4 v_rgba; -varying vec2 v_tc; -#else + +#ifdef NEW_SHADER_INTERFACE in vec4 v_rgba; 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 vec2 v_tc; #endif -#ifdef GL_ES +#ifndef SRGB_SUPPORTED // 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)); + 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); + return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); } - -#if __VERSION__ < 300 // 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)); + 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); + return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); } -#endif -#endif -#ifdef GL_ES void main() { -#if __VERSION__ < 300 - // 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); -#else - // The texture is set up with `SRGB8_ALPHA8`, so no need to decode here! - vec4 texture_rgba = texture2D(u_sampler, v_tc); -#endif + // 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; - /// 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: - // 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; + // Unmultiply alpha: + if (gl_FragColor.a > 0.0) { + gl_FragColor.rgb /= gl_FragColor.a; + } - // 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 + // 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; + } } -#else + #else void main() { - // The texture sampler is sRGB aware, and OpenGL already expects linear rgba output - // so no need for any sRGB conversions here: -#if __VERSION__ < 140 - gl_FragColor = v_rgba * texture2D(u_sampler, v_tc); -#else - f_color = v_rgba * texture(u_sampler, v_tc); -#endif + // 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); + } -#endif + #endif diff --git a/egui_glow/src/shader/post_fragment_100es.glsl b/egui_glow/src/shader/post_fragment_100es.glsl new file mode 100644 index 00000000000..001f8a3f236 --- /dev/null +++ b/egui_glow/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_glow/src/shader/post_vertex_100es.glsl b/egui_glow/src/shader/post_vertex_100es.glsl new file mode 100644 index 00000000000..37280bc1dad --- /dev/null +++ b/egui_glow/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_glow/src/shader/vertex.glsl b/egui_glow/src/shader/vertex.glsl index cf2309a8376..15ea4c26697 100644 --- a/egui_glow/src/shader/vertex.glsl +++ b/egui_glow/src/shader/vertex.glsl @@ -1,4 +1,4 @@ -#if !defined(GL_ES) && __VERSION__ >= 140 +#ifdef NEW_SHADER_INTERFACE #define I in #define O out #define V(x) x diff --git a/egui_glow/src/shader_version.rs b/egui_glow/src/shader_version.rs new file mode 100644 index 00000000000..4e89d54330e --- /dev/null +++ b/egui_glow/src/shader_version.rs @@ -0,0 +1,79 @@ +#![allow(unsafe_code)] +use crate::misc_util::glow_debug_print; +use glow::HasContext; +use std::convert::TryInto; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[allow(dead_code)] +pub(crate) enum ShaderVersion { + Gl120, + Gl140, + Es100, + Es300, +} + +impl ShaderVersion { + pub(crate) fn get(gl: &glow::Context) -> Self { + let shading_lang = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }; + glow_debug_print(&shading_lang); + Self::parse(&shading_lang) + } + + #[inline] + pub(crate) fn parse(glsl_ver: &str) -> Self { + let start = glsl_ver.find(|c| char::is_ascii_digit(&c)).unwrap(); + let es = glsl_ver[..start].contains(" ES "); + let ver = glsl_ver[start..].splitn(2, ' ').next().unwrap(); + let [maj, min]: [u8; 2] = ver + .splitn(3, '.') + .take(2) + .map(|x| x.parse().unwrap_or_default()) + .collect::>() + .try_into() + .unwrap(); + if es { + if maj >= 3 { + Self::Es300 + } else { + Self::Es100 + } + } else if maj > 1 || (maj == 1 && min >= 40) { + Self::Gl140 + } else { + Self::Gl120 + } + } + + pub(crate) fn version(&self) -> &'static str { + match self { + Self::Gl120 => "#version 120\n", + Self::Gl140 => "#version 140\n", + Self::Es100 => "#version 100\n", + Self::Es300 => "#version 300 es\n", + } + } + pub(crate) fn is_new_shader_interface(&self) -> &'static str { + match self { + ShaderVersion::Es300 | ShaderVersion::Gl140 => "#define NEW_SHADER_INTERFACE\n", + _ => "", + } + } +} + +#[test] +fn test_shader_version() { + use ShaderVersion::{Es100, Es300, Gl120, Gl140}; + for (s, v) in [ + ("1.2 OpenGL foo bar", Gl120), + ("3.0", Gl140), + ("0.0", Gl120), + ("OpenGL ES GLSL 3.00 (WebGL2)", Es300), + ("OpenGL ES GLSL 1.00 (WebGL)", Es100), + ("OpenGL ES GLSL ES 1.00 foo bar", Es100), + ("WebGL GLSL ES 3.00 foo bar", Es300), + ("WebGL GLSL ES 3.00", Es300), + ("WebGL GLSL ES 1.0 foo bar", Es100), + ] { + assert_eq!(ShaderVersion::parse(s), v); + } +} diff --git a/egui_glow/src/vao_emulate.rs b/egui_glow/src/vao_emulate.rs new file mode 100644 index 00000000000..590134945e5 --- /dev/null +++ b/egui_glow/src/vao_emulate.rs @@ -0,0 +1,57 @@ +#![allow(unsafe_code)] +use glow::HasContext; + +pub(crate) struct BufferInfo { + pub location: u32, // + pub vector_size: i32, + pub data_type: u32, //GL_FLOAT,GL_UNSIGNED_BYTE + pub normalized: bool, + pub stride: i32, + pub offset: i32, +} +pub struct EmulatedVao { + buffer: Option, + buffer_infos: Vec, +} +impl EmulatedVao { + pub(crate) fn new() -> Self { + Self { + buffer: None, + buffer_infos: vec![], + } + } + pub(crate) fn bind_buffer(&mut self, buffer: &glow::Buffer) { + let _old = self.buffer.replace(*buffer); + } + pub(crate) fn add_new_attribute(&mut self, buffer_info: BufferInfo) { + self.buffer_infos.push(buffer_info); + } + pub(crate) fn bind_vertex_array(&self, gl: &glow::Context) { + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, self.buffer); + } + for attribute in self.buffer_infos.iter() { + unsafe { + gl.vertex_attrib_pointer_f32( + attribute.location, + attribute.vector_size, + attribute.data_type, + attribute.normalized, + attribute.stride, + attribute.offset, + ); + gl.enable_vertex_attrib_array(attribute.location); + } + } + } + pub(crate) fn unbind_vertex_array(&self, gl: &glow::Context) { + for attribute in self.buffer_infos.iter() { + unsafe { + gl.disable_vertex_attrib_array(attribute.location); + } + } + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, None); + } + } +} diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index d43b9beb84c..94773637f52 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the `egui_web` integration will be noted in this file. ## Unreleased - +*Add feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)). ## 0.15.0 - 2021-10-24 ### Added diff --git a/egui_web/Cargo.toml b/egui_web/Cargo.toml index 4781372b29f..3f1322c3d0c 100644 --- a/egui_web/Cargo.toml +++ b/egui_web/Cargo.toml @@ -28,6 +28,7 @@ crate-type = ["cdylib", "rlib"] egui = { version = "0.15.0", path = "../egui", default-features = false, features = [ "single_threaded", ] } +egui_glow = { version = "0.15.0",path = "../egui_glow", default-features = false, optional = true } epi = { version = "0.15.0", path = "../epi" } js-sys = "0.3" ron = { version = "0.7", optional = true } @@ -44,6 +45,7 @@ default = ["default_fonts"] default_fonts = ["egui/default_fonts"] persistence = ["egui/persistence", "ron", "serde"] screen_reader = ["tts"] # experimental +glow =["egui_glow","egui_glow/epi"] [dependencies.web-sys] version = "0.3.52" diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 21f0dea6a0c..cc63f725b02 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -5,6 +5,11 @@ pub use egui::{pos2, Color32}; // ---------------------------------------------------------------------------- fn create_painter(canvas_id: &str) -> Result, JsValue> { + #[cfg(feature = "glow")] + return Ok(Box::new(crate::glow_wrapping::WrappedGlowPainter::new( + canvas_id, + ))); + #[cfg(not(feature = "glow"))] if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { console_log("Using WebGL2 backend"); Ok(Box::new(webgl2_painter)) @@ -167,7 +172,7 @@ impl AppRunner { fn integration_info(&self) -> epi::IntegrationInfo { epi::IntegrationInfo { - name: "egui_web", + name: self.painter.name(), web_info: Some(epi::WebInfo { web_location_hash: location_hash().unwrap_or_default(), }), diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs new file mode 100644 index 00000000000..f9512a5830d --- /dev/null +++ b/egui_web/src/glow_wrapping.rs @@ -0,0 +1,130 @@ +use crate::web_sys::{WebGl2RenderingContext, WebGlRenderingContext}; +use crate::{canvas_element_or_die, console_error}; +use egui::{ClippedMesh, Rgba, Texture}; +use egui_glow::glow; +use epi::TextureAllocator; +use wasm_bindgen::JsValue; +use web_sys::HtmlCanvasElement; + +pub(crate) struct WrappedGlowPainter { + pub(crate) gl_ctx: glow::Context, + pub(crate) canvas: HtmlCanvasElement, + pub(crate) canvas_id: String, + pub(crate) painter: egui_glow::Painter, +} + +impl WrappedGlowPainter { + pub fn new(canvas_id: &str) -> Self { + let canvas = canvas_element_or_die(canvas_id); + let gl_ctx = init_glow_context_from_canvas(&canvas); + let dimension = [canvas.width() as i32, canvas.height() as i32]; + let painter = egui_glow::Painter::new(&gl_ctx, Some(dimension)) + .map_err(|error| { + console_error(format!( + "some error occurred in initializing glow painter\n {}", + error + )) + }) + .unwrap(); + + Self { + gl_ctx, + canvas, + canvas_id: canvas_id.to_owned(), + painter, + } + } +} +impl crate::Painter for WrappedGlowPainter { + fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator { + &mut self.painter + } + + fn debug_info(&self) -> String { + format!( + "Stored canvas size: {} x {}", + self.canvas.width(), + self.canvas.height(), + ) + } + + fn canvas_id(&self) -> &str { + &self.canvas_id + } + + fn upload_egui_texture(&mut self, texture: &Texture) { + self.painter.upload_egui_texture(&self.gl_ctx, texture) + } + + fn clear(&mut self, clear_color: Rgba) { + let canvas_dimension = [self.canvas.width(), self.canvas.height()]; + egui_glow::painter::clear(&self.gl_ctx, canvas_dimension, clear_color) + } + + fn paint_meshes( + &mut self, + clipped_meshes: Vec, + pixels_per_point: f32, + ) -> Result<(), JsValue> { + let canvas_dimension = [self.canvas.width(), self.canvas.height()]; + self.painter.paint_meshes( + canvas_dimension, + &self.gl_ctx, + pixels_per_point, + clipped_meshes, + ); + Ok(()) + } + + fn name(&self) -> &'static str { + "egui_web(glow)" + } +} + +pub fn init_glow_context_from_canvas(canvas: &HtmlCanvasElement) -> glow::Context { + use wasm_bindgen::JsCast; + let ctx = canvas.get_context("webgl2"); + if let Ok(ctx) = ctx { + crate::console_log("webgl found"); + if let Some(ctx) = ctx { + crate::console_log("webgl 2 selected"); + let gl_ctx = ctx.dyn_into::().unwrap(); + glow::Context::from_webgl2_context(gl_ctx) + } else { + let ctx = canvas.get_context("webgl"); + if let Ok(ctx) = ctx { + crate::console_log("falling back to webgl1"); + if let Some(ctx) = ctx { + crate::console_log("webgl1 selected"); + + let gl_ctx = ctx.dyn_into::().unwrap(); + crate::console_log("success"); + glow::Context::from_webgl1_context(gl_ctx) + } else { + panic!("tried webgl1 but can't get context"); + } + } else { + panic!("tried webgl1 but can't get context"); + } + } + } else { + panic!("tried webgl2 but something went wrong"); + } +} + +trait DummyWebGLConstructor { + fn from_webgl1_context(context: web_sys::WebGlRenderingContext) -> Self; + + fn from_webgl2_context(context: web_sys::WebGl2RenderingContext) -> Self; +} + +#[cfg(not(target_arch = "wasm32"))] +impl DummyWebGLConstructor for glow::Context { + fn from_webgl1_context(_context: WebGlRenderingContext) -> Self { + panic!("you cant use egui_web(glow) on native") + } + + fn from_webgl2_context(_context: WebGl2RenderingContext) -> Self { + panic!("you cant use egui_web(glow) on native") + } +} diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 7f2c76a16bb..c5c9ecf1142 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -15,6 +15,8 @@ #![warn(clippy::all, missing_crate_level_docs, rust_2018_idioms)] pub mod backend; +#[cfg(feature = "glow")] +mod glow_wrapping; mod painter; pub mod screen_reader; pub mod webgl1; diff --git a/egui_web/src/painter.rs b/egui_web/src/painter.rs index fa1d84b2d50..0a414b949c5 100644 --- a/egui_web/src/painter.rs +++ b/egui_web/src/painter.rs @@ -17,4 +17,6 @@ pub trait Painter { clipped_meshes: Vec, pixels_per_point: f32, ) -> Result<(), JsValue>; + + fn name(&self) -> &'static str; } diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index aee2fadaf7a..91343dc29a7 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -538,6 +538,10 @@ impl crate::Painter for WebGlPainter { Ok(()) } + + fn name(&self) -> &'static str { + "egui_web(webgl1)" + } } struct PostProcess { diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index 4d76cc05f9c..d32c9bc2da6 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -516,6 +516,10 @@ impl crate::Painter for WebGl2Painter { Ok(()) } + + fn name(&self) -> &'static str { + "egui_web(webgl2)" + } } /// Uses a framebuffer to render everything in linear color space and convert it back to sRGB