From 8f178fa4e0830b7a1644e441b8d762f00d5c9efd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 27 Mar 2022 15:20:45 +0200 Subject: [PATCH] Add glow::Context to epi::Frame (#1425) This can be used, for instance, to: * Render things to offscreen buffers. * Read the pixel buffer from the previous frame (glow::Context::read_pixels). * Render things behind the egui windows. --- eframe/CHANGELOG.md | 1 + egui-winit/src/epi.rs | 2 ++ egui/src/ui.rs | 2 +- egui_demo_lib/src/backend_panel.rs | 2 +- egui_glow/src/epi_backend.rs | 30 ++++++++++++------------------ egui_glow/src/painter.rs | 9 +++++++-- egui_web/src/backend.rs | 7 +++++-- egui_web/src/glow_wrapping.rs | 6 +++++- egui_web/src/lib.rs | 1 + epi/src/lib.rs | 19 +++++++++++++++++-- 10 files changed, 52 insertions(+), 27 deletions(-) diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index ae83aa92..bf100752 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -18,6 +18,7 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG * You can now load/save state in `App::update` * Changed `App::update` to take `&mut Frame` instead of `&Frame`. * `Frame` is no longer `Clone` or `Sync`. +* Add `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)). ## 0.17.0 - 2022-02-22 diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index e3533e82..b34d3a5d 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -149,6 +149,7 @@ pub struct EpiIntegration { impl EpiIntegration { pub fn new( integration_name: &'static str, + gl: std::rc::Rc, max_texture_side: usize, window: &winit::window::Window, storage: Option>, @@ -169,6 +170,7 @@ impl EpiIntegration { }, output: Default::default(), storage, + gl, }; if prefer_dark_mode == Some(true) { diff --git a/egui/src/ui.rs b/egui/src/ui.rs index b958855c..17db4ecb 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -792,7 +792,7 @@ impl Ui { /// So one can think of `cursor` as a constraint on the available region. /// /// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child. - /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the min_rect. + /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the `min_rect`. pub fn cursor(&self) -> Rect { self.placer.cursor() } diff --git a/egui_demo_lib/src/backend_panel.rs b/egui_demo_lib/src/backend_panel.rs index 1b5bc1bd..ae47b9ac 100644 --- a/egui_demo_lib/src/backend_panel.rs +++ b/egui_demo_lib/src/backend_panel.rs @@ -12,7 +12,7 @@ enum RunMode { /// For instance, a GUI for a thermostat need to repaint each time the temperature changes. /// To ensure the UI is up to date you need to call `egui::Context::request_repaint()` each /// time such an event happens. You can also chose to call `request_repaint()` once every second - /// or after every single frame - this is called `Continuous` mode, + /// or after every single frame - this is called "Continuous" mode, /// and for games and interactive tools that need repainting every frame anyway, this should be the default. Reactive, diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 8b919ea9..fde2bb30 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -1,4 +1,3 @@ -use crate::*; use egui_winit::winit; struct RequestRepaintEvent; @@ -50,6 +49,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi let mut integration = egui_winit::epi::EpiIntegration::new( "egui_glow", + gl.clone(), painter.max_texture_side(), gl_window.window(), storage, @@ -86,6 +86,10 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi std::thread::sleep(std::time::Duration::from_millis(10)); } + let screen_size_in_pixels: [u32; 2] = gl_window.window().inner_size().into(); + + crate::painter::clear(&gl, screen_size_in_pixels, app.clear_color()); + let egui::FullOutput { platform_output, needs_repaint, @@ -97,24 +101,14 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi let clipped_primitives = integration.egui_ctx.tessellate(shapes); - // paint: - { - let color = app.clear_color(); - unsafe { - use glow::HasContext as _; - gl.disable(glow::SCISSOR_TEST); - gl.clear_color(color[0], color[1], color[2], color[3]); - gl.clear(glow::COLOR_BUFFER_BIT); - } - painter.paint_and_update_textures( - gl_window.window().inner_size().into(), - integration.egui_ctx.pixels_per_point(), - &clipped_primitives, - &textures_delta, - ); + painter.paint_and_update_textures( + screen_size_in_pixels, + integration.egui_ctx.pixels_per_point(), + &clipped_primitives, + &textures_delta, + ); - gl_window.swap_buffers().unwrap(); - } + gl_window.swap_buffers().unwrap(); { *control_flow = if integration.should_quit() { diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index e26e86a1..35b2ff40 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -627,11 +627,16 @@ impl Painter { } } -pub fn clear(gl: &glow::Context, dimension: [u32; 2], clear_color: egui::Rgba) { +pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: egui::Rgba) { unsafe { gl.disable(glow::SCISSOR_TEST); - gl.viewport(0, 0, dimension[0] as i32, dimension[1] as i32); + gl.viewport( + 0, + 0, + screen_size_in_pixels[0] as i32, + screen_size_in_pixels[1] as i32, + ); let clear_color: Color32 = clear_color.into(); gl.clear_color( diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 29915523..13ae4f38 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -155,6 +155,7 @@ impl AppRunner { }, output: Default::default(), storage: Some(Box::new(LocalStorage::default())), + gl: painter.gl().clone(), }; let needs_repaint: std::sync::Arc = Default::default(); @@ -274,12 +275,14 @@ impl AppRunner { Ok((needs_repaint, clipped_primitives)) } + pub fn clear_color_buffer(&self) { + self.painter.clear(self.app.clear_color()); + } + /// Paint the results of the last call to [`Self::logic`]. pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> { let textures_delta = std::mem::take(&mut self.textures_delta); - self.painter.clear(self.app.clear_color()); - self.painter.paint_and_update_textures( clipped_primitives, self.egui_ctx.pixels_per_point(), diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index a66cf5d4..80d6fab5 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -32,6 +32,10 @@ impl WrappedGlowPainter { } impl WrappedGlowPainter { + pub fn gl(&self) -> &std::rc::Rc { + self.painter.gl() + } + pub fn max_texture_side(&self) -> usize { self.painter.max_texture_side() } @@ -48,7 +52,7 @@ impl WrappedGlowPainter { self.painter.free_texture(tex_id); } - pub fn clear(&mut self, clear_color: Rgba) { + pub fn clear(&self, clear_color: Rgba) { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color); } diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 1143bbba..d9dd522e 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -339,6 +339,7 @@ fn paint_and_schedule(runner_ref: &AppRunnerRef, panicked: Arc) -> R fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.lock(); if runner_lock.needs_repaint.fetch_and_clear() { + runner_lock.clear_color_buffer(); let (needs_repaint, clipped_primitives) = runner_lock.logic()?; runner_lock.paint(&clipped_primitives)?; if needs_repaint { diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 4a8ea636..45ce2036 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -248,8 +248,6 @@ pub struct IconData { /// /// It provides methods to inspect the surroundings (are we on the web?), /// allocate textures, and change settings (e.g. window size). -/// -/// [`Frame`] is cheap to clone and is safe to pass to other threads. pub struct Frame { /// Information about the integration. #[doc(hidden)] @@ -262,6 +260,10 @@ pub struct Frame { /// A place where you can store custom data in a way that persists when you restart the app. #[doc(hidden)] pub storage: Option>, + + /// A reference to the underlying [`glow`] (OpenGL) context. + #[doc(hidden)] + pub gl: std::rc::Rc, } impl Frame { @@ -285,6 +287,19 @@ impl Frame { self.storage.as_deref_mut() } + /// A reference to the underlying [`glow`] (OpenGL) context. + /// + /// This can be used, for instance, to: + /// * Render things to offscreen buffers. + /// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`). + /// * Render things behind the egui windows. + /// + /// Note that all egui painting is deferred to after the call to [`App::update`] + /// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on). + pub fn gl(&self) -> &std::rc::Rc { + &self.gl + } + /// Signal the app to stop/exit/quit the app (only works for native apps, not web apps). /// The framework will not quit immediately, but at the end of the this frame. pub fn quit(&mut self) {