From 5a857ff395d6c687613d541130ae2001d624a25e Mon Sep 17 00:00:00 2001 From: Asger Nyman Christiansen Date: Wed, 3 Aug 2022 18:13:54 +0200 Subject: [PATCH] Update egui + disable egui by default (#270) * Almost working * Use released egui (ContextRef + fix fonts) * Revert example changes * Fix examples * Fixes * Fix web * egui-gui feature is no longer default * Update rust.yml * Avoid ContextRef (Context again impl Send+Sync) * Avoid changes to canvas * docs * Remove unused shaders * gui module + Readme description --- .github/workflows/rust.yml | 2 + Cargo.toml | 15 ++- README.md | 3 +- examples/environment/Cargo.toml | 2 +- examples/headless/Cargo.toml | 2 +- examples/image/Cargo.toml | 2 +- examples/lighting/Cargo.toml | 2 +- examples/lights/Cargo.toml | 2 +- examples/pbr/Cargo.toml | 2 +- examples/screen/Cargo.toml | 2 +- examples/shapes/Cargo.toml | 2 +- examples/shapes2d/Cargo.toml | 2 +- examples/statues/Cargo.toml | 2 +- examples/triangle/Cargo.toml | 2 +- examples/volume/Cargo.toml | 2 +- src/core/context.rs | 2 +- src/gui/egui_gui.rs | 158 ++++++++------------------------ src/gui/shaders/egui.frag | 20 ---- src/gui/shaders/egui.vert | 20 ---- src/lib.rs | 3 +- 20 files changed, 66 insertions(+), 181 deletions(-) delete mode 100644 src/gui/shaders/egui.frag delete mode 100644 src/gui/shaders/egui.vert diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ec6047d74..d749914da 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,6 +2,8 @@ name: build on: push: + branches: + - master pull_request: branches: - master diff --git a/Cargo.toml b/Cargo.toml index 23e49f1bd..28a855e3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,16 +17,17 @@ rustdoc-args = ["--cfg", "docsrs"] targets = ["x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "wasm32-unknown-unknown"] [features] -default = ["window", "egui-gui"] +default = ["window"] window = ["glutin"] # Window module -egui-gui = ["egui"] # Additional GUI features +egui-gui = ["egui_glow", "egui"] # Additional GUI features [dependencies] glow = "0.11" cgmath = "0.18" three-d-asset = "0.3" thiserror = "1" -egui = { version = "0.13", optional = true } +egui = { version = "0.18", optional = true } +egui_glow = { version = "0.18", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] glutin = { version = "0.29", optional = true } @@ -51,6 +52,7 @@ path = "examples/triangle/src/main.rs" [[example]] name = "screen" path = "examples/screen/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "mandelbrot" @@ -75,6 +77,7 @@ path = "examples/texture/src/main.rs" [[example]] name = "volume" path = "examples/volume/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "picking" @@ -83,22 +86,27 @@ path = "examples/picking/src/main.rs" [[example]] name = "environment" path = "examples/environment/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "pbr" path = "examples/pbr/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "lighting" path = "examples/lighting/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "lights" path = "examples/lights/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "image" path = "examples/image/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "fog" @@ -111,6 +119,7 @@ path = "examples/fireworks/src/main.rs" [[example]] name = "statues" path = "examples/statues/src/main.rs" +required-features = ["egui-gui"] [[example]] name = "wireframe" diff --git a/README.md b/README.md index 008c5eaaa..3ab4c804e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ A OpenGL/WebGL/OpenGL ES renderer which seeks to make graphics simple but still - tools (2D or 3D) - games (2D or 3D) -The crate consist of three main modules for drawing, `context`, `core` and `renderer`, and an optional `window` module for easy setup: +The crate consist of three main modules for drawing, `context`, `core` and `renderer`, and an optional `window` module for easy setup and an optional `gui` integration module: | Module | Description | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -35,6 +35,7 @@ The crate consist of three main modules for drawing, `context`, `core` and `rend | [`core`](https://docs.rs/three-d/0/three_d/core/) | Mid-level rendering module - requires at least some knowledge about graphics concepts. Use this if you want to write your own shaders and but don't want to spend time on setup and error handling. Can be combined with low-level functionality in the `context` module. | [`renderer`](https://docs.rs/three-d/0/three_d/renderer/) | High-level rendering module - requires no knowledge about graphics concepts. Use this if you just want to draw something on the screen. Features include methods for rendering different types of standard objects with different types of shading. Can be combined seamlessly with the mid-level features in the `core` module as well as functionality in the `context` module. | | [`window`](https://docs.rs/three-d/0/three_d/window/) (requires the `"window"` feature) | Contains functionality for creating a window on both cross-platform native and web. Also contain render loop, event handling and camera control functionality. Can be replaced by anything that provides an OpenGL or WebGL2 graphics context, for example [eframe](https://github.com/emilk/egui/tree/master/eframe) as shown in [this](https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_three-d.rs) example. +| [`gui`](https://docs.rs/three-d/0/three_d/gui/) | Contains functionality for using the immediate mode GUI [egui](https://crates.io/crates/egui) (requires the `"egui-gui"` feature). In addition, the [three-d-asset](https://github.com/asny/three-d-asset) crate enables loading, deserializing, serializing and saving 3D assets, for example 3D models, textures etc. diff --git a/examples/environment/Cargo.toml b/examples/environment/Cargo.toml index c15d10de3..233db8f07 100644 --- a/examples/environment/Cargo.toml +++ b/examples/environment/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../" } +three-d = { path = "../../", features=["egui-gui"] } three-d-asset = {version = "0.3", features = ["hdr", "http"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/headless/Cargo.toml b/examples/headless/Cargo.toml index 1a8e04a61..ac3bf705e 100644 --- a/examples/headless/Cargo.toml +++ b/examples/headless/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../", default-features = false, features=["window"] } +three-d = { path = "../../" } [target.'cfg(target_arch = "wasm32")'.dependencies] log = "0.4" diff --git a/examples/image/Cargo.toml b/examples/image/Cargo.toml index 74086cd5f..18d5d96c5 100644 --- a/examples/image/Cargo.toml +++ b/examples/image/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../" } +three-d = { path = "../../", features=["egui-gui"] } three-d-asset = {version = "0.3", features = ["hdr", "http"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/lighting/Cargo.toml b/examples/lighting/Cargo.toml index 04eb3107a..2fd8f9b3b 100644 --- a/examples/lighting/Cargo.toml +++ b/examples/lighting/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../" } +three-d = { path = "../../", features=["egui-gui"] } three-d-asset = {version = "0.3", features = ["gltf", "jpeg", "http"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/lights/Cargo.toml b/examples/lights/Cargo.toml index 0a62e2dfa..6bfdf9160 100644 --- a/examples/lights/Cargo.toml +++ b/examples/lights/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../" } +three-d = { path = "../../", features=["egui-gui"] } three-d-asset = {version = "0.3", features = ["gltf", "png", "jpeg", "http"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/pbr/Cargo.toml b/examples/pbr/Cargo.toml index 162dd4458..4dcec2ea2 100644 --- a/examples/pbr/Cargo.toml +++ b/examples/pbr/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../" } +three-d = { path = "../../", features=["egui-gui"] } three-d-asset = {version = "0.3", features = ["gltf", "hdr", "jpeg", "http"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/screen/Cargo.toml b/examples/screen/Cargo.toml index ec77db1ce..42dd79d3e 100644 --- a/examples/screen/Cargo.toml +++ b/examples/screen/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../", default-features = false, features=["window", "egui-gui"] } +three-d = { path = "../../", features=["egui-gui"] } [target.'cfg(target_arch = "wasm32")'.dependencies] log = "0.4" diff --git a/examples/shapes/Cargo.toml b/examples/shapes/Cargo.toml index e11f70108..b5dc7e45a 100644 --- a/examples/shapes/Cargo.toml +++ b/examples/shapes/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../", default-features = false, features=["window"] } +three-d = { path = "../../" } [target.'cfg(target_arch = "wasm32")'.dependencies] log = "0.4" diff --git a/examples/shapes2d/Cargo.toml b/examples/shapes2d/Cargo.toml index a38b0d4cf..38fe2696c 100644 --- a/examples/shapes2d/Cargo.toml +++ b/examples/shapes2d/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../", default-features = false, features=["window"] } +three-d = { path = "../../" } [target.'cfg(target_arch = "wasm32")'.dependencies] log = "0.4" diff --git a/examples/statues/Cargo.toml b/examples/statues/Cargo.toml index 94dff57d9..ae22e16cf 100644 --- a/examples/statues/Cargo.toml +++ b/examples/statues/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../" } +three-d = { path = "../../", features=["egui-gui"] } three-d-asset = {version = "0.3", features = ["obj", "png", "http"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/triangle/Cargo.toml b/examples/triangle/Cargo.toml index f0f5ae0c7..bfa3c5987 100644 --- a/examples/triangle/Cargo.toml +++ b/examples/triangle/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../", default-features = false, features=["window"] } +three-d = { path = "../../" } [target.'cfg(target_arch = "wasm32")'.dependencies] log = "0.4" diff --git a/examples/volume/Cargo.toml b/examples/volume/Cargo.toml index 56fc64b88..16727e4ca 100644 --- a/examples/volume/Cargo.toml +++ b/examples/volume/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -three-d = { path = "../../" } +three-d = { path = "../../", features=["egui-gui"] } three-d-asset = {version = "0.3", features = ["vol", "http"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/core/context.rs b/src/core/context.rs index d9b28ecc6..bcf19ed13 100644 --- a/src/core/context.rs +++ b/src/core/context.rs @@ -381,7 +381,7 @@ impl std::fmt::Debug for Context { } impl std::ops::Deref for Context { - type Target = crate::context::Context; + type Target = Arc; fn deref(&self) -> &Self::Target { &self.context } diff --git a/src/gui/egui_gui.rs b/src/gui/egui_gui.rs index 51b92aa85..4cd331e01 100644 --- a/src/gui/egui_gui.rs +++ b/src/gui/egui_gui.rs @@ -1,47 +1,38 @@ -use crate::core::*; use crate::window::*; #[doc(hidden)] pub use egui; +use egui_glow::Painter; /// /// Integration of [egui](https://crates.io/crates/egui), an immediate mode GUI. /// pub struct GUI { - context: Context, - egui_context: egui::CtxRef, + painter: Painter, + egui_context: egui::Context, width: u32, height: u32, - program: Program, - texture_version: u64, - texture: Option, } impl GUI { /// - /// Creates a new GUI. + /// Creates a new GUI from a mid-level [Context](crate::core::Context). /// - pub fn new(context: &Context) -> Self { + pub fn new(context: &crate::core::Context) -> Self { + use std::ops::Deref; + Self::from_gl_context(context.deref().clone()) + } + + /// + /// Creates a new GUI from a low-level graphics [Context](crate::context::Context). + /// + pub fn from_gl_context(context: std::sync::Arc) -> Self { + #[allow(unsafe_code)] // Temporary until egui takes Arc + let context = unsafe { std::rc::Rc::from_raw(std::sync::Arc::into_raw(context)) }; GUI { - egui_context: egui::CtxRef::default(), - context: context.clone(), + egui_context: egui::Context::default(), + painter: Painter::new(context, None, "").unwrap(), width: 0, height: 0, - texture_version: 0, - texture: None, - program: Program::from_source( - context, - &format!( - "{}{}", - include_str!("../core/shared.frag"), - include_str!("shaders/egui.vert") - ), - &format!( - "{}{}", - include_str!("../core/shared.frag"), - include_str!("shaders/egui.frag") - ), - ) - .unwrap(), } } @@ -50,7 +41,7 @@ impl GUI { /// Construct the GUI (Add panels, widgets etc.) using the [egui::CtxRef](egui::CtxRef) in the callback function. /// This function returns whether or not the GUI has changed, ie. if it consumes any events, and therefore needs to be rendered again. /// - pub fn update( + pub fn update( &mut self, frame_input: &mut FrameInput, callback: F, @@ -115,106 +106,27 @@ impl GUI { /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn render(&mut self) { - let (_, shapes) = self.egui_context.end_frame(); - let clipped_meshes = self.egui_context.tessellate(shapes); - - let egui_texture = self.egui_context.texture(); - - if self.texture.is_none() || self.texture_version != egui_texture.version { - let mut pixels = Vec::new(); - for pixel in egui_texture.srgba_pixels() { - pixels.push([pixel.r(), pixel.g(), pixel.b(), pixel.a()]); - } - self.texture = Some(Texture2D::new( - &self.context, - &CpuTexture { - data: TextureData::RgbaU8(pixels), - width: egui_texture.width as u32, - height: egui_texture.height as u32, - mip_map_filter: None, - wrap_s: Wrapping::ClampToEdge, - wrap_t: Wrapping::ClampToEdge, - ..Default::default() - }, - )); - self.texture_version = egui_texture.version; - }; - + let output = self.egui_context.end_frame(); + let clipped_meshes = self.egui_context.tessellate(output.shapes); let scale = self.egui_context.pixels_per_point(); - let viewport = Viewport::new_at_origo( - (self.width as f32 * scale).round() as u32, - (self.height as f32 * scale).round() as u32, + self.painter.paint_and_update_textures( + [ + (self.width as f32 * scale).round() as u32, + (self.height as f32 * scale).round() as u32, + ], + scale, + &clipped_meshes, + &output.textures_delta, ); - - for egui::ClippedMesh(_, mesh) in clipped_meshes { - self.paint_mesh( - viewport, - self.width, - self.height, - &mesh, - self.texture.as_ref().unwrap(), - ); + #[allow(unsafe_code)] + unsafe { + use glow::HasContext as _; + self.painter.gl().disable(glow::FRAMEBUFFER_SRGB); } } - - fn paint_mesh( - &self, - viewport: Viewport, - width: u32, - height: u32, - mesh: &egui::paint::Mesh, - texture: &Texture2D, - ) { - debug_assert!(mesh.is_valid()); - - let mut positions = Vec::new(); - let mut colors = Vec::new(); - let mut uvs = Vec::new(); - for v in mesh.vertices.iter() { - positions.push(vec2(v.pos.x, v.pos.y)); - uvs.push(vec2(v.uv.x, 1.0 - v.uv.y)); - colors.push(vec4( - v.color[0] as f32, - v.color[1] as f32, - v.color[2] as f32, - v.color[3] as f32, - )); - } - let indices: Vec = mesh.indices.iter().map(|idx| *idx as u32).collect(); - - let position_buffer = VertexBuffer::new_with_data(&self.context, &positions); - let uv_buffer = VertexBuffer::new_with_data(&self.context, &uvs); - let color_buffer = VertexBuffer::new_with_data(&self.context, &colors); - let index_buffer = ElementBuffer::new_with_data(&self.context, &indices); - - let render_states = RenderStates { - blend: Blend::Enabled { - source_rgb_multiplier: BlendMultiplierType::One, - source_alpha_multiplier: BlendMultiplierType::OneMinusDstAlpha, - destination_rgb_multiplier: BlendMultiplierType::OneMinusSrcAlpha, - destination_alpha_multiplier: BlendMultiplierType::One, - rgb_equation: BlendEquationType::Add, - alpha_equation: BlendEquationType::Add, - }, - depth_test: DepthTest::Always, - ..Default::default() - }; - - self.program.use_texture("u_sampler", texture); - self.program - .use_uniform("u_screen_size", vec2(width as f32, height as f32)); - - self.program.use_vertex_attribute("a_pos", &position_buffer); - self.program.use_vertex_attribute("a_srgba", &color_buffer); - self.program.use_vertex_attribute("a_tc", &uv_buffer); - - self.program - .draw_elements(render_states, viewport, &index_buffer) - } } fn construct_input_state(frame_input: &mut FrameInput) -> egui::RawInput { - let mut scroll_delta = egui::Vec2::ZERO; let mut egui_modifiers = egui::Modifiers::default(); let mut egui_events = Vec::new(); for event in frame_input.events.iter() { @@ -307,7 +219,10 @@ fn construct_input_state(frame_input: &mut FrameInput) -> egui::RawInput { } Event::MouseWheel { delta, handled, .. } => { if !handled { - scroll_delta = egui::Vec2::new(delta.0 as f32, delta.1 as f32); + egui_events.push(egui::Event::Scroll(egui::Vec2::new( + delta.0 as f32, + delta.1 as f32, + ))); } } Event::ModifiersChange { modifiers } => egui_modifiers = map_modifiers(modifiers), @@ -316,7 +231,6 @@ fn construct_input_state(frame_input: &mut FrameInput) -> egui::RawInput { } egui::RawInput { - scroll_delta, screen_rect: Some(egui::Rect::from_min_size( Default::default(), egui::Vec2 { diff --git a/src/gui/shaders/egui.frag b/src/gui/shaders/egui.frag deleted file mode 100644 index be5723c26..000000000 --- a/src/gui/shaders/egui.frag +++ /dev/null @@ -1,20 +0,0 @@ - -uniform sampler2D u_sampler; - -in vec4 v_rgba; -in vec2 v_tc; - -layout (location = 0) out vec4 color; - -void main() { - // The texture is in linear space so we need to decode here - vec4 texture_rgba = texture(u_sampler, v_tc); - texture_rgba.rgb = rgb_from_srgb(texture_rgba.rgb); - /// Multiply vertex color with texture color (in linear space). - color = v_rgba * texture_rgba; - // We must gamma-encode again since WebGL doesn't support linear blending in the framebuffer. - color.rgb = srgb_from_rgb(color.rgb); - // 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: - color.a = pow(color.a, 1.6); // Empiric nonsense -} \ No newline at end of file diff --git a/src/gui/shaders/egui.vert b/src/gui/shaders/egui.vert deleted file mode 100644 index cc7669404..000000000 --- a/src/gui/shaders/egui.vert +++ /dev/null @@ -1,20 +0,0 @@ -uniform vec2 u_screen_size; - -in vec2 a_pos; -in vec2 a_tc; -in vec4 a_srgba; - -out vec4 v_rgba; -out vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - // egui encodes vertex colors in gamma spaces, so we must decode the colors here: - v_rgba.rgb = rgb_from_srgb(a_srgba.rgb/255.0); - v_rgba.a = a_srgba.a/255.0; - v_tc = a_tc; -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4dbb1a8e2..c451d04f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,5 @@ pub mod window; #[cfg(feature = "window")] pub use window::*; -mod gui; -#[doc(inline)] +pub mod gui; pub use gui::*;