From 473d13d0719c060e28e00f27a44a3d90d2ca019a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 25 Dec 2021 20:03:34 +0100 Subject: [PATCH 1/8] Make epi::Frame cloneable so you can allocate textures in other threads Closes https://github.com/emilk/egui/issues/673 --- Cargo.lock | 1 + eframe/CHANGELOG.md | 3 + eframe/examples/file_dialog.rs | 2 +- eframe/examples/hello_world.rs | 2 +- eframe/examples/image.rs | 6 +- eframe/src/lib.rs | 4 +- egui-winit/src/epi.rs | 106 ++++----- egui_demo_lib/src/apps/color_test.rs | 31 ++- egui_demo_lib/src/apps/demo/app.rs | 4 +- egui_demo_lib/src/apps/fractal_clock.rs | 2 +- egui_demo_lib/src/apps/http_app.rs | 24 +- egui_demo_lib/src/backend_panel.rs | 10 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- egui_demo_lib/src/wrap_app.rs | 6 +- egui_glium/src/epi_backend.rs | 34 ++- egui_glium/src/painter.rs | 121 +++------- egui_glow/src/epi_backend.rs | 17 +- egui_glow/src/lib.rs | 2 +- egui_glow/src/painter.rs | 213 +++++------------- egui_web/src/backend.rs | 85 ++++--- egui_web/src/painter.rs | 4 +- egui_web/src/webgl1.rs | 203 +++++------------ egui_web/src/webgl2.rs | 202 +++++------------ epi/Cargo.toml | 1 + epi/src/lib.rs | 183 ++++++++++----- 25 files changed, 490 insertions(+), 778 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e5a8a4f357..91a9aba8ef4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1002,6 +1002,7 @@ version = "0.15.0" dependencies = [ "directories-next", "egui", + "parking_lot", "ron", "serde", ] diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index a49b7349cc6..5b9c9eb96d7 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -5,6 +5,9 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m ## Unreleased +* `Frame` can now be cloned, saved, and passed to background threads. +* Added `Frame::request_repaint` to replace `repaint_signal`. +* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator`. ## 0.15.0 - 2021-10-24 diff --git a/eframe/examples/file_dialog.rs b/eframe/examples/file_dialog.rs index 2de6e3f4d9d..37cb4c7dec5 100644 --- a/eframe/examples/file_dialog.rs +++ b/eframe/examples/file_dialog.rs @@ -11,7 +11,7 @@ impl epi::App for MyApp { "Native file dialogs and drag-and-drop files" } - fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.label("Drag-and-drop files onto the window!"); diff --git a/eframe/examples/hello_world.rs b/eframe/examples/hello_world.rs index 4437f1a33ba..49a07d501df 100644 --- a/eframe/examples/hello_world.rs +++ b/eframe/examples/hello_world.rs @@ -19,7 +19,7 @@ impl epi::App for MyApp { "My egui App" } - fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { let Self { name, age } = self; egui::CentralPanel::default().show(ctx, |ui| { diff --git a/eframe/examples/image.rs b/eframe/examples/image.rs index ebf5850e8f1..50b0e19c9a2 100644 --- a/eframe/examples/image.rs +++ b/eframe/examples/image.rs @@ -10,7 +10,7 @@ impl epi::App for MyApp { "Show an image with eframe/egui" } - fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { if self.texture.is_none() { // Load the image: let image_data = include_bytes!("rust-logo-256x256.png"); @@ -26,9 +26,7 @@ impl epi::App for MyApp { .collect(); // Allocate a texture: - let texture = frame - .tex_allocator() - .alloc_srgba_premultiplied(size, &pixels); + let texture = frame.tex_allocator().alloc(size, &pixels); let size = egui::Vec2::new(size.0 as f32, size.1 as f32); self.texture = Some((size, texture)); } diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index c63386ca7aa..ba4bda8774b 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -24,7 +24,7 @@ //! "My egui App" //! } //! -//! fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { +//! fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { //! egui::CentralPanel::default().show(ctx, |ui| { //! ui.heading("Hello World!"); //! }); @@ -127,7 +127,7 @@ pub fn start_web(canvas_id: &str, app: Box) -> Result<(), wasm_bin /// "My egui App" /// } /// -/// fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { +/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { /// egui::CentralPanel::default().show(ctx, |ui| { /// ui.heading("Hello World!"); /// }); diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index 9daa2f98d04..e41cec0ccab 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -51,13 +51,15 @@ fn window_builder_drag_and_drop( window_builder } +#[must_use] pub fn handle_app_output( window: &winit::window::Window, current_pixels_per_point: f32, app_output: epi::backend::AppOutput, -) { +) -> epi::backend::TexAllocationData { let epi::backend::AppOutput { quit: _, + tex_allocation_data, window_size, window_title, decorated, @@ -85,6 +87,8 @@ pub fn handle_app_output( if drag_window { let _ = window.drag_window(); } + + tex_allocation_data } // ---------------------------------------------------------------------------- @@ -186,13 +190,11 @@ impl Persistence { /// Everything needed to make a winit-based integration for [`epi`]. pub struct EpiIntegration { - integration_name: &'static str, + frame: epi::Frame, persistence: crate::epi::Persistence, - repaint_signal: std::sync::Arc, pub egui_ctx: egui::CtxRef, egui_winit: crate::State, pub app: Box, - latest_frame_time: Option, /// When set, it is time to quit quit: bool, } @@ -201,8 +203,7 @@ impl EpiIntegration { pub fn new( integration_name: &'static str, window: &winit::window::Window, - tex_allocator: &mut dyn epi::TextureAllocator, - repaint_signal: std::sync::Arc, + repaint_signal: std::sync::Arc, persistence: crate::epi::Persistence, app: Box, ) -> Self { @@ -210,54 +211,49 @@ impl EpiIntegration { *egui_ctx.memory() = persistence.load_memory().unwrap_or_default(); + let frame = epi::Frame::new(epi::backend::FrameData { + info: epi::IntegrationInfo { + name: integration_name, + web_info: None, + prefer_dark_mode: None, // TODO: figure out system default + cpu_usage: None, + native_pixels_per_point: Some(crate::native_pixels_per_point(window)), + }, + output: Default::default(), + repaint_signal, + }); + let mut slf = Self { - integration_name, + frame, persistence, - repaint_signal, egui_ctx, egui_winit: crate::State::new(window), app, - latest_frame_time: None, quit: false, }; - slf.setup(window, tex_allocator); + slf.setup(window); if slf.app.warm_up_enabled() { - slf.warm_up(window, tex_allocator); + slf.warm_up(window); } slf } - fn setup( - &mut self, - window: &winit::window::Window, - tex_allocator: &mut dyn epi::TextureAllocator, - ) { - let mut app_output = epi::backend::AppOutput::default(); - let mut frame = epi::backend::FrameBuilder { - info: integration_info(self.integration_name, window, None), - tex_allocator, - output: &mut app_output, - repaint_signal: self.repaint_signal.clone(), - } - .build(); + fn setup(&mut self, window: &winit::window::Window) { self.app - .setup(&self.egui_ctx, &mut frame, self.persistence.storage()); - + .setup(&self.egui_ctx, &self.frame, self.persistence.storage()); + let app_output = self.frame.take_app_output(); self.quit |= app_output.quit; - - crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); + let tex_alloc_data = + crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); + self.frame.0.lock().output.tex_allocation_data = tex_alloc_data; // Do it later } - fn warm_up( - &mut self, - window: &winit::window::Window, - tex_allocator: &mut dyn epi::TextureAllocator, - ) { + fn warm_up(&mut self, window: &winit::window::Window) { let saved_memory = self.egui_ctx.memory().clone(); self.egui_ctx.memory().set_everything_is_visible(true); - self.update(window, tex_allocator); + self.update(window); *self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. self.egui_ctx.clear_animations(); } @@ -277,37 +273,31 @@ impl EpiIntegration { pub fn update( &mut self, window: &winit::window::Window, - tex_allocator: &mut dyn epi::TextureAllocator, - ) -> (bool, Vec) { + ) -> ( + bool, + epi::backend::TexAllocationData, + Vec, + ) { let frame_start = std::time::Instant::now(); let raw_input = self.egui_winit.take_egui_input(window); - - let mut app_output = epi::backend::AppOutput::default(); - let mut frame = epi::backend::FrameBuilder { - info: integration_info(self.integration_name, window, self.latest_frame_time), - tex_allocator, - output: &mut app_output, - repaint_signal: self.repaint_signal.clone(), - } - .build(); - let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| { - self.app.update(egui_ctx, &mut frame); + self.app.update(egui_ctx, &self.frame); }); let needs_repaint = egui_output.needs_repaint; self.egui_winit .handle_output(window, &self.egui_ctx, egui_output); + let app_output = self.frame.take_app_output(); self.quit |= app_output.quit; - - crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); + let tex_allocation_data = + crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32; - self.latest_frame_time = Some(frame_time); + self.frame.0.lock().info.cpu_usage = Some(frame_time); - (needs_repaint, shapes) + (needs_repaint, tex_allocation_data, shapes) } pub fn maybe_autosave(&mut self, window: &winit::window::Window) { @@ -321,17 +311,3 @@ impl EpiIntegration { .save(&mut *self.app, &self.egui_ctx, window); } } - -fn integration_info( - integration_name: &'static str, - window: &winit::window::Window, - previous_frame_time: Option, -) -> epi::IntegrationInfo { - epi::IntegrationInfo { - name: integration_name, - web_info: None, - prefer_dark_mode: None, // TODO: figure out system default - cpu_usage: previous_frame_time, - native_pixels_per_point: Some(crate::native_pixels_per_point(window)), - } -} diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index c1af72559bc..231e68ca0d2 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -34,7 +34,7 @@ impl epi::App for ColorTest { "🎨 Color test" } - fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { egui::CentralPanel::default().show(ctx, |ui| { if frame.is_web() { ui.label( @@ -43,18 +43,14 @@ impl epi::App for ColorTest { ui.separator(); } ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| { - self.ui(ui, &mut Some(frame.tex_allocator())); + self.ui(ui, Some(frame)); }); }); } } impl ColorTest { - pub fn ui( - &mut self, - ui: &mut Ui, - mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>, - ) { + pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) { ui.set_max_width(680.0); ui.vertical_centered(|ui| { @@ -105,10 +101,10 @@ impl ColorTest { self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g); self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", WHITE, &g); } - if let Some(tex_allocator) = &mut tex_allocator { + if let Some(tex_allocator) = tex_allocator { ui.horizontal(|ui| { let g = Gradient::one_color(Color32::from(tex_color)); - let tex = self.tex_mngr.get(*tex_allocator, &g); + let tex = self.tex_mngr.get(tex_allocator, &g); let texel_offset = 0.5 / (g.0.len() as f32); let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); @@ -167,7 +163,7 @@ impl ColorTest { fn show_gradients( &mut self, ui: &mut Ui, - tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>, + tex_allocator: Option<&dyn epi::TextureAllocator>, bg_fill: Color32, (left, right): (Color32, Color32), ) { @@ -261,7 +257,7 @@ impl ColorTest { fn tex_gradient( &mut self, ui: &mut Ui, - tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>, + tex_allocator: Option<&dyn epi::TextureAllocator>, label: &str, bg_fill: Color32, gradient: &Gradient, @@ -271,7 +267,7 @@ impl ColorTest { } if let Some(tex_allocator) = tex_allocator { ui.horizontal(|ui| { - let tex = self.tex_mngr.get(*tex_allocator, gradient); + let tex = self.tex_mngr.get(tex_allocator, gradient); let texel_offset = 0.5 / (gradient.0.len() as f32); let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv)) @@ -391,16 +387,15 @@ impl Gradient { struct TextureManager(HashMap); impl TextureManager { - fn get( - &mut self, - tex_allocator: &mut dyn epi::TextureAllocator, - gradient: &Gradient, - ) -> TextureId { + fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId { *self.0.entry(gradient.clone()).or_insert_with(|| { let pixels = gradient.to_pixel_row(); let width = pixels.len(); let height = 1; - tex_allocator.alloc_srgba_premultiplied((width, height), &pixels) + tex_allocator.alloc(epi::Image { + size: [width, height], + pixels, + }) }) } } diff --git a/egui_demo_lib/src/apps/demo/app.rs b/egui_demo_lib/src/apps/demo/app.rs index 7cd2ba38513..2ba8e34be80 100644 --- a/egui_demo_lib/src/apps/demo/app.rs +++ b/egui_demo_lib/src/apps/demo/app.rs @@ -17,7 +17,7 @@ impl epi::App for DemoApp { fn setup( &mut self, _ctx: &egui::CtxRef, - _frame: &mut epi::Frame<'_>, + _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>, ) { #[cfg(feature = "persistence")] @@ -31,7 +31,7 @@ impl epi::App for DemoApp { epi::set_value(storage, epi::APP_KEY, self); } - fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) { self.demo_windows.ui(ctx); } } diff --git a/egui_demo_lib/src/apps/fractal_clock.rs b/egui_demo_lib/src/apps/fractal_clock.rs index 2bf040b9cf1..813ab34306c 100644 --- a/egui_demo_lib/src/apps/fractal_clock.rs +++ b/egui_demo_lib/src/apps/fractal_clock.rs @@ -37,7 +37,7 @@ impl epi::App for FractalClock { "🕑 Fractal Clock" } - fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) { egui::CentralPanel::default() .frame(Frame::dark_canvas(&ctx.style())) .show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight())); diff --git a/egui_demo_lib/src/apps/http_app.rs b/egui_demo_lib/src/apps/http_app.rs index 7605f6de9bd..e34b7a5d83e 100644 --- a/egui_demo_lib/src/apps/http_app.rs +++ b/egui_demo_lib/src/apps/http_app.rs @@ -67,7 +67,7 @@ impl epi::App for HttpApp { "⬇ HTTP" } - fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { if let Some(receiver) = &mut self.in_progress { // Are we there yet? if let Ok(result) = receiver.try_recv() { @@ -127,7 +127,7 @@ impl epi::App for HttpApp { } } -fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bool { +fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool { let mut trigger_fetch = false; ui.horizontal(|ui| { @@ -160,12 +160,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bo trigger_fetch } -fn ui_resource( - ui: &mut egui::Ui, - frame: &mut epi::Frame<'_>, - tex_mngr: &mut TexMngr, - resource: &Resource, -) { +fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) { let Resource { response, text, @@ -302,22 +297,13 @@ struct TexMngr { } impl TexMngr { - fn texture( - &mut self, - frame: &mut epi::Frame<'_>, - url: &str, - image: &Image, - ) -> Option { + fn texture(&mut self, frame: &epi::Frame, url: &str, image: &Image) -> Option { if self.loaded_url != url { if let Some(texture_id) = self.texture_id.take() { frame.tex_allocator().free(texture_id); } - self.texture_id = Some( - frame - .tex_allocator() - .alloc_srgba_premultiplied(image.size, &image.pixels), - ); + self.texture_id = Some(frame.tex_allocator().alloc(image.size, &image.pixels)); self.loaded_url = url.to_owned(); } self.texture_id diff --git a/egui_demo_lib/src/backend_panel.rs b/egui_demo_lib/src/backend_panel.rs index 478979ae707..170d7081baf 100644 --- a/egui_demo_lib/src/backend_panel.rs +++ b/egui_demo_lib/src/backend_panel.rs @@ -78,7 +78,7 @@ impl Default for BackendPanel { } impl BackendPanel { - pub fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { + pub fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { self.frame_history .on_new_frame(ctx.input().time, frame.info().cpu_usage); @@ -92,7 +92,7 @@ impl BackendPanel { self.egui_windows.windows(ctx); } - pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) { + pub fn ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) { egui::trace!(ui); ui.vertical_centered(|ui| { ui.heading("💻 Backend"); @@ -147,7 +147,7 @@ impl BackendPanel { } } - fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) { + fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) { if frame.is_web() { ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); ui.label( @@ -170,13 +170,13 @@ impl BackendPanel { } } - show_integration_name(ui, frame.info()); + show_integration_name(ui, &frame.info()); // For instance: `egui_web` sets `pixels_per_point` every frame to force // egui to use the same scale as the web zoom factor. let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some(); if !integration_controls_pixels_per_point { - if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, frame.info()) { + if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, &frame.info()) { ui.ctx().set_pixels_per_point(new_pixels_per_point); } } diff --git a/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 50360d94a8e..729d1a5f331 100644 --- a/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor { "🖹 EasyMark editor" } - fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) { egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index c9a04f6b2b0..5eed1b0bb52 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -45,7 +45,7 @@ impl epi::App for WrapApp { fn setup( &mut self, _ctx: &egui::CtxRef, - _frame: &mut epi::Frame<'_>, + _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>, ) { #[cfg(feature = "persistence")] @@ -72,7 +72,7 @@ impl epi::App for WrapApp { cfg!(not(debug_assertions)) } - fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { + fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { if let Some(web_info) = frame.info().web_info.as_ref() { if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') { self.selected_anchor = anchor.to_owned(); @@ -126,7 +126,7 @@ impl epi::App for WrapApp { } impl WrapApp { - fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) { + fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) { // A menu-bar is a horizontal layout with some special styles applied. // egui::menu::bar(ui, |ui| { ui.horizontal_wrapped(|ui| { diff --git a/egui_glium/src/epi_backend.rs b/egui_glium/src/epi_backend.rs index ea17bd76bea..e57f876cdaa 100644 --- a/egui_glium/src/epi_backend.rs +++ b/egui_glium/src/epi_backend.rs @@ -1,22 +1,6 @@ -use crate::*; -use egui::Color32; use glium::glutin; -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); - } -} +use crate::*; struct RequestRepaintEvent; @@ -24,7 +8,7 @@ struct GliumRepaintSignal( std::sync::Mutex>, ); -impl epi::RepaintSignal for GliumRepaintSignal { +impl epi::backend::RepaintSignal for GliumRepaintSignal { fn request_repaint(&self) { self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); } @@ -64,7 +48,6 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { let mut integration = egui_winit::epi::EpiIntegration::new( "egui_glium", display.gl_window().window(), - &mut painter, repaint_signal, persistence, app, @@ -83,10 +66,15 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { std::thread::sleep(std::time::Duration::from_millis(10)); } - let (needs_repaint, shapes) = - integration.update(display.gl_window().window(), &mut painter); + let (needs_repaint, mut tex_allocation_data, shapes) = + integration.update(display.gl_window().window()); let clipped_meshes = integration.egui_ctx.tessellate(shapes); + for (id, image) in tex_allocation_data.creations { + painter.set_user_texture(&display, id, &image); + } + + // paint: { use glium::Surface as _; let mut target = display.draw(); @@ -104,6 +92,10 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { target.finish().unwrap(); } + for id in tex_allocation_data.destructions.drain(..) { + painter.free_user_texture(id); + } + { *control_flow = if integration.should_quit() { glutin::event_loop::ControlFlow::Exit diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 2febdc3978e..4389272f833 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -14,7 +14,7 @@ use { uniform, uniforms::{MagnifySamplerFilter, SamplerWrapFunction}, }, - std::rc::Rc, + std::{collections::HashMap, rc::Rc}, }; pub struct Painter { @@ -22,19 +22,11 @@ pub struct Painter { egui_texture: Option, egui_texture_version: Option, - /// `None` means unallocated (freed) slot. - user_textures: Vec>, -} - -#[derive(Default)] -struct UserTexture { - /// Pending upload (will be emptied later). - /// This is the format glium likes. - pixels: Vec>, + /// Index is the same as in [`egui::TextureId::User`]. + user_textures: HashMap>, - /// Lazily uploaded from [`Self::pixels`], - /// or owned by the user via `register_native_texture`. - gl_texture: Option>, + // TODO: 128-bit texture space? + next_native_tex_id: u64, } impl Painter { @@ -65,6 +57,7 @@ impl Painter { egui_texture: None, egui_texture_version: None, user_textures: Default::default(), + next_native_tex_id: 1 << 32, } } @@ -106,7 +99,6 @@ impl Painter { egui_texture: &egui::Texture, ) { self.upload_egui_texture(display, egui_texture); - self.upload_pending_user_textures(display); for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes { self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh); @@ -229,82 +221,40 @@ impl Painter { } // ------------------------------------------------------------------------ - // user textures: this is an experimental feature. - // No need to implement this in your egui integration! - - pub fn alloc_user_texture(&mut self) -> egui::TextureId { - for (i, tex) in self.user_textures.iter_mut().enumerate() { - if tex.is_none() { - *tex = Some(Default::default()); - return egui::TextureId::User(i as u64); - } - } - let id = egui::TextureId::User(self.user_textures.len() as u64); - self.user_textures.push(Some(Default::default())); - id - } pub fn set_user_texture( &mut self, - id: egui::TextureId, - size: (usize, usize), - pixels: &[Color32], + facade: &dyn glium::backend::Facade, + tex_id: u64, + image: &epi::Image, ) { assert_eq!( - size.0 * size.1, - pixels.len(), + image.size[0] * image.size[1], + image.pixels.len(), "Mismatch between texture size and texel count" ); - if let egui::TextureId::User(id) = id { - if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { - let pixels: Vec> = pixels - .chunks(size.0 as usize) - .map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect()) - .collect(); - - *user_texture = UserTexture { - pixels, - gl_texture: None, - }; - } - } + let pixels: Vec> = image + .pixels + .chunks(image.size[0] as usize) + .map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect()) + .collect(); + + let format = texture::SrgbFormat::U8U8U8U8; + let mipmaps = texture::MipmapsOption::NoMipmap; + let gl_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap(); + + self.user_textures.insert(tex_id, gl_texture.into()); } - pub fn free_user_texture(&mut self, id: egui::TextureId) { - if let egui::TextureId::User(id) = id { - let index = id as usize; - if index < self.user_textures.len() { - self.user_textures[index] = None; - } - } + pub fn free_user_texture(&mut self, tex_id: u64) { + self.user_textures.remove(&tex_id); } pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> { match texture_id { egui::TextureId::Egui => self.egui_texture.as_ref(), - egui::TextureId::User(id) => self - .user_textures - .get(id as usize)? - .as_ref()? - .gl_texture - .as_ref() - .map(|rc| rc.as_ref()), - } - } - - pub fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) { - for user_texture in self.user_textures.iter_mut().flatten() { - if user_texture.gl_texture.is_none() { - let pixels = std::mem::take(&mut user_texture.pixels); - let format = texture::SrgbFormat::U8U8U8U8; - let mipmaps = texture::MipmapsOption::NoMipmap; - user_texture.gl_texture = Some( - SrgbTexture2d::with_format(facade, pixels, format, mipmaps) - .unwrap() - .into(), - ); - } + egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()), } } } @@ -314,26 +264,15 @@ impl epi::NativeTexture for Painter { type Texture = Rc; fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { - let id = self.alloc_user_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 { - pixels: vec![], - gl_texture: Some(native), - } - } - } - id + let id = self.next_native_tex_id; + self.next_native_tex_id += 1; + self.user_textures.insert(id, native); + egui::TextureId::User(id as u64) } fn replace_native_texture(&mut self, id: egui::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 { - pixels: vec![], - gl_texture: Some(replacing), - }; - } + self.user_textures.insert(id, replacing); } } } diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 8f38d8547bb..a7c3cb1be11 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -4,7 +4,7 @@ struct RequestRepaintEvent; struct GlowRepaintSignal(std::sync::Mutex>); -impl epi::RepaintSignal for GlowRepaintSignal { +impl epi::backend::RepaintSignal for GlowRepaintSignal { fn request_repaint(&self) { self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); } @@ -64,7 +64,6 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { let mut integration = egui_winit::epi::EpiIntegration::new( "egui_glow", gl_window.window(), - &mut painter, repaint_signal, persistence, app, @@ -83,9 +82,15 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { std::thread::sleep(std::time::Duration::from_millis(10)); } - let (needs_repaint, shapes) = integration.update(gl_window.window(), &mut painter); + let (needs_repaint, mut tex_allocation_data, shapes) = + integration.update(gl_window.window()); let clipped_meshes = integration.egui_ctx.tessellate(shapes); + for (id, image) in tex_allocation_data.creations { + painter.set_user_texture(&gl, id, &image); + } + + // paint: { let color = integration.app.clear_color(); unsafe { @@ -96,8 +101,8 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { } painter.upload_egui_texture(&gl, &integration.egui_ctx.texture()); painter.paint_meshes( - gl_window.window().inner_size().into(), &gl, + gl_window.window().inner_size().into(), integration.egui_ctx.pixels_per_point(), clipped_meshes, ); @@ -105,6 +110,10 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { gl_window.swap_buffers().unwrap(); } + for id in tex_allocation_data.destructions.drain(..) { + painter.free_user_texture(id); + } + { *control_flow = if integration.should_quit() { glutin::event_loop::ControlFlow::Exit diff --git a/egui_glow/src/lib.rs b/egui_glow/src/lib.rs index ce1d745d240..fcf821794aa 100644 --- a/egui_glow/src/lib.rs +++ b/egui_glow/src/lib.rs @@ -166,8 +166,8 @@ impl EguiGlow { self.painter .upload_egui_texture(gl, &self.egui_ctx.texture()); self.painter.paint_meshes( - dimensions, gl, + dimensions, self.egui_ctx.pixels_per_point(), clipped_meshes, ); diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 5fb62c3e026..bb6fa3818cc 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -1,14 +1,13 @@ #![allow(unsafe_code)] +use std::collections::HashMap; + use egui::{ emath::Rect, epaint::{Color32, Mesh, Vertex}, }; -pub use glow::Context; - -use memoffset::offset_of; - use glow::HasContext; +use memoffset::offset_of; use crate::misc_util::{ as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d, @@ -17,6 +16,8 @@ use crate::post_process::PostProcess; use crate::shader_version::ShaderVersion; use crate::vao_emulate; +pub use glow::Context; + const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); @@ -34,27 +35,19 @@ pub struct Painter { is_embedded: bool, vertex_array: crate::misc_util::VAO, srgb_support: bool, - /// `None` means unallocated (freed) slot. - 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, - // Only used in debug builds, to make sure we are destroyed correctly. - destroyed: bool, -} + /// Index is the same as in [`egui::TextureId::User`]. + user_textures: HashMap, + // TODO: 128-bit texture space? + next_native_tex_id: u64, + /// Stores outdated OpenGL textures that are yet to be deleted + textures_to_destroy: Vec, -#[derive(Default)] -pub(crate) struct UserTexture { - /// Pending upload (will be emptied later). - /// This is the format glow likes. - pub(crate) data: Vec, - pub(crate) size: (usize, usize), - - /// Lazily uploaded - pub(crate) gl_texture: Option, + /// Only used in debug builds, to make sure we are destroyed correctly. + destroyed: bool, } impl Painter { @@ -195,11 +188,12 @@ impl Painter { 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(), + user_textures: Default::default(), + next_native_tex_id: 1 << 32, + textures_to_destroy: Vec::new(), destroyed: false, }) } @@ -298,15 +292,14 @@ 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, - inner_size: [u32; 2], gl: &glow::Context, + inner_size: [u32; 2], pixels_per_point: f32, clipped_meshes: Vec, ) { //chimera of egui_glow and egui_web self.assert_not_destroyed(); - self.upload_pending_user_textures(gl); 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); @@ -393,96 +386,39 @@ impl Painter { } // ------------------------------------------------------------------------ - // user textures: this is an experimental feature. - // No need to implement this in your egui integration! - pub fn alloc_user_texture(&mut self) -> egui::TextureId { + pub fn set_user_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) { self.assert_not_destroyed(); - for (i, tex) in self.user_textures.iter_mut().enumerate() { - if tex.is_none() { - *tex = Some(Default::default()); - return egui::TextureId::User(i as u64); - } - } - let id = egui::TextureId::User(self.user_textures.len() as u64); - self.user_textures.push(Some(Default::default())); - id - } - - /// register glow texture as egui texture - /// Usable for render to image rectangle - #[allow(clippy::needless_pass_by_value)] - pub fn register_glow_texture(&mut self, texture: glow::Texture) -> egui::TextureId { - self.assert_not_destroyed(); + assert_eq!( + image.size[0] * image.size[1], + image.pixels.len(), + "Mismatch between texture size and texel count" + ); - let id = self.alloc_user_texture(); - if let egui::TextureId::User(id) = id { - if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { - if let UserTexture { - gl_texture: Some(old_tex), - .. - } = std::mem::replace( - user_texture, - UserTexture { - data: vec![], - size: (0, 0), - gl_texture: Some(texture), - }, - ) { - self.old_textures.push(old_tex); - } - } - } - id - } + // TODO: optimize + let pixels: Vec = image + .pixels + .iter() + .flat_map(|srgba| Vec::from(srgba.to_array())) + .collect(); - pub fn set_user_texture( - &mut self, - id: egui::TextureId, - size: (usize, usize), - pixels: &[Color32], - ) { - self.assert_not_destroyed(); - assert_eq!( - size.0 * size.1, - pixels.len(), - "Mismatch between size and texel count" + let gl_texture = srgbtexture2d( + gl, + self.is_webgl_1, + self.srgb_support, + &pixels, + image.size[0], + image.size[1], ); - if let egui::TextureId::User(id) = id { - if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { - let data: Vec = pixels - .iter() - .flat_map(|srgba| Vec::from(srgba.to_array())) - .collect(); - - if let UserTexture { - gl_texture: Some(old_tex), - .. - } = std::mem::replace( - user_texture, - UserTexture { - data, - size, - gl_texture: None, - }, - ) { - self.old_textures.push(old_tex); - } - } + if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) { + self.textures_to_destroy.push(old_tex); } } - pub fn free_user_texture(&mut self, id: egui::TextureId) { - self.assert_not_destroyed(); - - if let egui::TextureId::User(id) = id { - let index = id as usize; - if index < self.user_textures.len() { - self.user_textures[index] = None; - } - } + pub fn free_user_texture(&mut self, tex_id: u64) { + self.user_textures.remove(&tex_id); } pub fn get_texture(&self, texture_id: egui::TextureId) -> Option { @@ -490,31 +426,7 @@ impl Painter { match texture_id { egui::TextureId::Egui => self.egui_texture, - egui::TextureId::User(id) => self.user_textures.get(id as usize)?.as_ref()?.gl_texture, - } - } - - pub fn upload_pending_user_textures(&mut self, gl: &glow::Context) { - self.assert_not_destroyed(); - - for user_texture in self.user_textures.iter_mut().flatten() { - if user_texture.gl_texture.is_none() { - 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, - )); - user_texture.size = (0, 0); - } - } - for t in self.old_textures.drain(..) { - unsafe { - gl.delete_texture(t); - } + egui::TextureId::User(id) => self.user_textures.get(&id).copied(), } } @@ -523,14 +435,12 @@ impl Painter { if let Some(tex) = self.egui_texture { gl.delete_texture(tex); } - for tex in self.user_textures.iter().flatten() { - if let Some(t) = tex.gl_texture { - gl.delete_texture(t); - } + for tex in self.user_textures.values() { + gl.delete_texture(*tex); } gl.delete_buffer(self.vertex_buffer); gl.delete_buffer(self.element_array_buffer); - for t in &self.old_textures { + for t in &self.textures_to_destroy { gl.delete_texture(*t); } } @@ -580,39 +490,26 @@ impl Drop for Painter { } } -#[cfg(feature = "epi")] -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); - } -} - #[cfg(feature = "epi")] impl epi::NativeTexture for Painter { type Texture = glow::Texture; fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { - self.register_glow_texture(native) + self.assert_not_destroyed(); + + let id = self.next_native_tex_id; + self.next_native_tex_id += 1; + + if let Some(old_tex) = self.user_textures.insert(id, native) { + self.textures_to_destroy.push(old_tex); + } + egui::TextureId::User(id as u64) } fn replace_native_texture(&mut self, id: egui::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), - }; + if let Some(old_tex) = self.user_textures.insert(id, replacing) { + self.textures_to_destroy.push(old_tex); } } } diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index c8c2efe50bd..62fe593d1dc 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -67,7 +67,7 @@ impl NeedRepaint { } } -impl epi::RepaintSignal for NeedRepaint { +impl epi::backend::RepaintSignal for NeedRepaint { fn request_repaint(&self) { self.0.store(true, SeqCst); } @@ -76,28 +76,44 @@ impl epi::RepaintSignal for NeedRepaint { // ---------------------------------------------------------------------------- pub struct AppRunner { + frame: epi::Frame, egui_ctx: egui::CtxRef, painter: Box, - previous_frame_time: Option, pub(crate) input: WebInput, app: Box, pub(crate) needs_repaint: std::sync::Arc, storage: LocalStorage, - prefer_dark_mode: Option, last_save_time: f64, screen_reader: crate::screen_reader::ScreenReader, pub(crate) text_cursor_pos: Option, pub(crate) mutable_text_under_cursor: bool, + pending_texture_destructions: Vec, } impl AppRunner { pub fn new(canvas_id: &str, app: Box) -> Result { - let egui_ctx = egui::CtxRef::default(); - - load_memory(&egui_ctx); + let painter = create_painter(canvas_id)?; let prefer_dark_mode = crate::prefer_dark_mode(); + let needs_repaint: std::sync::Arc = Default::default(); + + let frame = epi::Frame::new(epi::backend::FrameData { + info: epi::IntegrationInfo { + name: painter.name(), + web_info: Some(epi::WebInfo { + web_location_hash: location_hash().unwrap_or_default(), + }), + prefer_dark_mode, + cpu_usage: None, + native_pixels_per_point: Some(native_pixels_per_point()), + }, + output: Default::default(), + repaint_signal: needs_repaint.clone(), + }); + + let egui_ctx = egui::CtxRef::default(); + load_memory(&egui_ctx); if prefer_dark_mode == Some(true) { egui_ctx.set_visuals(egui::Visuals::dark()); } else { @@ -107,32 +123,25 @@ impl AppRunner { let storage = LocalStorage::default(); let mut runner = Self { + frame, egui_ctx, - painter: create_painter(canvas_id)?, - previous_frame_time: None, + painter, input: Default::default(), app, - needs_repaint: Default::default(), + needs_repaint, storage, - prefer_dark_mode, last_save_time: now_sec(), screen_reader: Default::default(), text_cursor_pos: None, mutable_text_under_cursor: false, + pending_texture_destructions: Default::default(), }; { - let mut app_output = epi::backend::AppOutput::default(); - let mut frame = epi::backend::FrameBuilder { - info: runner.integration_info(), - tex_allocator: runner.painter.as_tex_allocator(), - output: &mut app_output, - repaint_signal: runner.needs_repaint.clone(), - } - .build(); runner .app - .setup(&runner.egui_ctx, &mut frame, Some(&runner.storage)); + .setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage)); + // TODO: handle app output } Ok(runner) @@ -170,18 +179,6 @@ impl AppRunner { Ok(()) } - fn integration_info(&self) -> epi::IntegrationInfo { - epi::IntegrationInfo { - name: self.painter.name(), - web_info: Some(epi::WebInfo { - web_location_hash: location_hash().unwrap_or_default(), - }), - prefer_dark_mode: self.prefer_dark_mode, - cpu_usage: self.previous_frame_time, - native_pixels_per_point: Some(native_pixels_per_point()), - } - } - pub fn logic(&mut self) -> Result<(egui::Output, Vec), JsValue> { let frame_start = now_sec(); @@ -189,33 +186,31 @@ impl AppRunner { let canvas_size = canvas_size_in_points(self.canvas_id()); let raw_input = self.input.new_frame(canvas_size); - let mut app_output = epi::backend::AppOutput::default(); - let mut frame = epi::backend::FrameBuilder { - info: self.integration_info(), - tex_allocator: self.painter.as_tex_allocator(), - output: &mut app_output, - repaint_signal: self.needs_repaint.clone(), - } - .build(); - let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| { - self.app.update(egui_ctx, &mut frame); + self.app.update(egui_ctx, &self.frame); }); let clipped_meshes = self.egui_ctx.tessellate(shapes); self.handle_egui_output(&egui_output); { + let app_output = self.frame.take_app_output(); let epi::backend::AppOutput { quit: _, // Can't quit a web page window_size: _, // Can't resize a web page window_title: _, // TODO: change title of window decorated: _, // Can't toggle decorations drag_window: _, // Can't be dragged + tex_allocation_data, } = app_output; + + for (id, image) in tex_allocation_data.creations { + self.painter.set_user_texture(id, image); + } + self.pending_texture_destructions = tex_allocation_data.destructions; } - self.previous_frame_time = Some((now_sec() - frame_start) as f32); + self.frame.0.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32); Ok((egui_output, clipped_meshes)) } @@ -223,7 +218,11 @@ impl AppRunner { self.painter.upload_egui_texture(&self.egui_ctx.texture()); self.painter.clear(self.app.clear_color()); self.painter - .paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point()) + .paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())?; + for id in self.pending_texture_destructions.drain(..) { + self.painter.free_user_texture(id); + } + Ok(()) } fn handle_egui_output(&mut self, output: &egui::Output) { diff --git a/egui_web/src/painter.rs b/egui_web/src/painter.rs index 0a414b949c5..7a3d424bf63 100644 --- a/egui_web/src/painter.rs +++ b/egui_web/src/painter.rs @@ -1,7 +1,9 @@ use wasm_bindgen::prelude::JsValue; pub trait Painter { - fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator; + fn set_user_texture(&mut self, tex_id: u64, image: epi::Image); + + fn free_user_texture(&mut self, tex_id: u64); fn debug_info(&self) -> String; diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index 73856838cf0..d329fd58b11 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use { js_sys::WebAssembly, wasm_bindgen::{prelude::*, JsCast}, @@ -29,19 +31,11 @@ pub struct WebGlPainter { egui_texture: WebGlTexture, egui_texture_version: Option, - /// `None` means unallocated (freed) slot. - user_textures: Vec>, -} - -#[derive(Default)] -struct UserTexture { - size: (usize, usize), + /// Index is the same as in [`egui::TextureId::User`]. + user_textures: HashMap, - /// Pending upload (will be emptied later). - pixels: Vec, - - /// Lazily uploaded - gl_texture: Option, + // TODO: 128-bit texture space? + next_native_tex_id: u64, } impl WebGlPainter { @@ -111,109 +105,14 @@ impl WebGlPainter { egui_texture, egui_texture_version: None, user_textures: Default::default(), + next_native_tex_id: 1 << 32, }) } - fn alloc_user_texture_index(&mut self) -> usize { - for (index, tex) in self.user_textures.iter_mut().enumerate() { - if tex.is_none() { - *tex = Some(Default::default()); - return index; - } - } - let index = self.user_textures.len(); - self.user_textures.push(Some(Default::default())); - index - } - - fn alloc_user_texture( - &mut self, - size: (usize, usize), - srgba_pixels: &[Color32], - ) -> egui::TextureId { - let index = self.alloc_user_texture_index(); - assert_eq!( - size.0 * size.1, - srgba_pixels.len(), - "Mismatch between texture size and texel count" - ); - - if let Some(Some(user_texture)) = self.user_textures.get_mut(index) { - let mut pixels: Vec = Vec::with_capacity(srgba_pixels.len() * 4); - for srgba in srgba_pixels { - pixels.push(srgba.r()); - pixels.push(srgba.g()); - pixels.push(srgba.b()); - pixels.push(srgba.a()); - } - - *user_texture = UserTexture { - size, - pixels, - gl_texture: None, - }; - } - - egui::TextureId::User(index as u64) - } - - fn free_user_texture(&mut self, id: egui::TextureId) { - if let egui::TextureId::User(id) = id { - let index = id as usize; - if index < self.user_textures.len() { - self.user_textures[index] = None; - } - } - } - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { match texture_id { egui::TextureId::Egui => Some(&self.egui_texture), - egui::TextureId::User(id) => self - .user_textures - .get(id as usize)? - .as_ref()? - .gl_texture - .as_ref(), - } - } - - fn upload_user_textures(&mut self) { - let gl = &self.gl; - - for user_texture in self.user_textures.iter_mut().flatten() { - if user_texture.gl_texture.is_none() { - let pixels = std::mem::take(&mut user_texture.pixels); - - let gl_texture = gl.create_texture().unwrap(); - gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_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::LINEAR as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32); - - gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); - - let level = 0; - let internal_format = self.texture_format; - let border = 0; - let src_format = self.texture_format; - let src_type = Gl::UNSIGNED_BYTE; - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - level, - internal_format as i32, - user_texture.size.0 as i32, - user_texture.size.1 as i32, - border, - src_format, - src_type, - Some(&pixels), - ) - .unwrap(); - - user_texture.gl_texture = Some(gl_texture); - } + egui::TextureId::User(id) => self.user_textures.get(&id), } } @@ -338,51 +237,75 @@ impl WebGlPainter { } } -impl epi::TextureAllocator for WebGlPainter { - fn alloc_srgba_premultiplied( - &mut self, - size: (usize, usize), - srgba_pixels: &[egui::Color32], - ) -> egui::TextureId { - self.alloc_user_texture(size, srgba_pixels) - } - - fn free(&mut self, id: egui::TextureId) { - self.free_user_texture(id) - } -} - impl epi::NativeTexture for WebGlPainter { type Texture = WebGlTexture; fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { - let id = self.alloc_user_texture_index(); - if let Some(Some(user_texture)) = self.user_textures.get_mut(id) { - *user_texture = UserTexture { - size: (0, 0), - pixels: vec![], - gl_texture: Some(native), - } - } + let id = self.next_native_tex_id; + self.next_native_tex_id += 1; + self.user_textures.insert(id, native); egui::TextureId::User(id as u64) } fn replace_native_texture(&mut self, id: egui::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 { - size: (0, 0), - pixels: vec![], - gl_texture: Some(replacing), - } + if let Some(user_texture) = self.user_textures.get_mut(&id) { + *user_texture = replacing; } } } } impl crate::Painter for WebGlPainter { - fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator { - self + fn set_user_texture(&mut self, tex_id: u64, image: epi::Image) { + assert_eq!( + image.size[0] * image.size[1], + image.pixels.len(), + "Mismatch between texture size and texel count" + ); + + // TODO: optimize + let mut pixels: Vec = Vec::with_capacity(image.pixels.len() * 4); + for srgba in image.pixels { + pixels.push(srgba.r()); + pixels.push(srgba.g()); + pixels.push(srgba.b()); + pixels.push(srgba.a()); + } + + let gl = &self.gl; + let gl_texture = gl.create_texture().unwrap(); + gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _); + + gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); + + let level = 0; + let internal_format = self.texture_format; + let border = 0; + let src_format = self.texture_format; + let src_type = Gl::UNSIGNED_BYTE; + gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + Gl::TEXTURE_2D, + level, + internal_format as _, + image.size[0] as _, + image.size[1] as _, + border, + src_format, + src_type, + Some(&pixels), + ) + .unwrap(); + + self.user_textures.insert(tex_id, gl_texture); + } + + fn free_user_texture(&mut self, tex_id: u64) { + self.user_textures.remove(&tex_id); } fn debug_info(&self) -> String { @@ -467,8 +390,6 @@ impl crate::Painter for WebGlPainter { clipped_meshes: Vec, pixels_per_point: f32, ) -> Result<(), JsValue> { - self.upload_user_textures(); - let gl = &self.gl; if let Some(ref mut post_process) = self.post_process { diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index de3a5090543..53aedc6080a 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -1,4 +1,5 @@ //! Mostly a carbon-copy of `webgl1.rs`. +use std::collections::HashMap; use { js_sys::WebAssembly, @@ -30,19 +31,11 @@ pub struct WebGl2Painter { egui_texture: WebGlTexture, egui_texture_version: Option, - /// `None` means unallocated (freed) slot. - user_textures: Vec>, -} - -#[derive(Default)] -struct UserTexture { - size: (usize, usize), - - /// Pending upload (will be emptied later). - pixels: Vec, + /// Index is the same as in [`egui::TextureId::User`]. + user_textures: HashMap, - /// Lazily uploaded - gl_texture: Option, + // TODO: 128-bit texture space? + next_native_tex_id: u64, } impl WebGl2Painter { @@ -96,109 +89,14 @@ impl WebGl2Painter { egui_texture, egui_texture_version: None, user_textures: Default::default(), + next_native_tex_id: 1 << 32, }) } - fn alloc_user_texture_index(&mut self) -> usize { - for (index, tex) in self.user_textures.iter_mut().enumerate() { - if tex.is_none() { - *tex = Some(Default::default()); - return index; - } - } - let index = self.user_textures.len(); - self.user_textures.push(Some(Default::default())); - index - } - - fn alloc_user_texture( - &mut self, - size: (usize, usize), - srgba_pixels: &[Color32], - ) -> egui::TextureId { - let index = self.alloc_user_texture_index(); - assert_eq!( - size.0 * size.1, - srgba_pixels.len(), - "Mismatch between texture size and texel count" - ); - - if let Some(Some(user_texture)) = self.user_textures.get_mut(index) { - let mut pixels: Vec = Vec::with_capacity(srgba_pixels.len() * 4); - for srgba in srgba_pixels { - pixels.push(srgba.r()); - pixels.push(srgba.g()); - pixels.push(srgba.b()); - pixels.push(srgba.a()); - } - - *user_texture = UserTexture { - size, - pixels, - gl_texture: None, - }; - } - - egui::TextureId::User(index as u64) - } - - fn free_user_texture(&mut self, id: egui::TextureId) { - if let egui::TextureId::User(id) = id { - let index = id as usize; - if index < self.user_textures.len() { - self.user_textures[index] = None; - } - } - } - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { match texture_id { egui::TextureId::Egui => Some(&self.egui_texture), - egui::TextureId::User(id) => self - .user_textures - .get(id as usize)? - .as_ref()? - .gl_texture - .as_ref(), - } - } - - fn upload_user_textures(&mut self) { - let gl = &self.gl; - for user_texture in self.user_textures.iter_mut().flatten() { - if user_texture.gl_texture.is_none() { - let pixels = std::mem::take(&mut user_texture.pixels); - - let gl_texture = gl.create_texture().unwrap(); - gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_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::LINEAR as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32); - - gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); - - let level = 0; - let internal_format = Gl::SRGB8_ALPHA8; - let border = 0; - let src_format = Gl::RGBA; - let src_type = Gl::UNSIGNED_BYTE; - 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, - level, - internal_format as i32, - user_texture.size.0 as i32, - user_texture.size.1 as i32, - border, - src_format, - src_type, - Some(&pixels), - ) - .unwrap(); - - user_texture.gl_texture = Some(gl_texture); - } + egui::TextureId::User(id) => self.user_textures.get(&id), } } @@ -323,51 +221,75 @@ impl WebGl2Painter { } } -impl epi::TextureAllocator for WebGl2Painter { - fn alloc_srgba_premultiplied( - &mut self, - size: (usize, usize), - srgba_pixels: &[egui::Color32], - ) -> egui::TextureId { - self.alloc_user_texture(size, srgba_pixels) - } - - fn free(&mut self, id: egui::TextureId) { - self.free_user_texture(id) - } -} - impl epi::NativeTexture for WebGl2Painter { type Texture = WebGlTexture; fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { - let id = self.alloc_user_texture_index(); - if let Some(Some(user_texture)) = self.user_textures.get_mut(id) { - *user_texture = UserTexture { - size: (0, 0), - pixels: vec![], - gl_texture: Some(native), - } - } + let id = self.next_native_tex_id; + self.next_native_tex_id += 1; + self.user_textures.insert(id, native); egui::TextureId::User(id as u64) } fn replace_native_texture(&mut self, id: egui::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 { - size: (0, 0), - pixels: vec![], - gl_texture: Some(replacing), - } + if let Some(user_texture) = self.user_textures.get_mut(&id) { + *user_texture = replacing; } } } } impl crate::Painter for WebGl2Painter { - fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator { - self + fn set_user_texture(&mut self, tex_id: u64, image: epi::Image) { + assert_eq!( + image.size[0] * image.size[1], + image.pixels.len(), + "Mismatch between texture size and texel count" + ); + + // TODO: optimize + let mut pixels: Vec = Vec::with_capacity(image.pixels.len() * 4); + for srgba in image.pixels { + pixels.push(srgba.r()); + pixels.push(srgba.g()); + pixels.push(srgba.b()); + pixels.push(srgba.a()); + } + + let gl = &self.gl; + let gl_texture = gl.create_texture().unwrap(); + gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _); + gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _); + + gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); + + let level = 0; + let internal_format = Gl::SRGB8_ALPHA8; + let border = 0; + let src_format = Gl::RGBA; + let src_type = Gl::UNSIGNED_BYTE; + gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + Gl::TEXTURE_2D, + level, + internal_format as _, + image.size[0] as _, + image.size[1] as _, + border, + src_format, + src_type, + Some(&pixels), + ) + .unwrap(); + + self.user_textures.insert(tex_id, gl_texture); + } + + fn free_user_texture(&mut self, tex_id: u64) { + self.user_textures.remove(&tex_id); } fn debug_info(&self) -> String { @@ -448,8 +370,6 @@ impl crate::Painter for WebGl2Painter { clipped_meshes: Vec, pixels_per_point: f32, ) -> Result<(), JsValue> { - self.upload_user_textures(); - let gl = &self.gl; self.post_process diff --git a/epi/Cargo.toml b/epi/Cargo.toml index 83d0f76220f..00870eb7769 100644 --- a/epi/Cargo.toml +++ b/epi/Cargo.toml @@ -25,6 +25,7 @@ all-features = true [dependencies] egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] } +parking_lot = "0.11" directories-next = { version = "2", optional = true } ron = { version = "0.7", optional = true } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 4629c0bbb89..4df92a5ce4a 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -95,6 +95,9 @@ pub mod file_storage; pub use egui; // Re-export for user convenience +use parking_lot::Mutex; +use std::sync::Arc; + // ---------------------------------------------------------------------------- /// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate, @@ -104,8 +107,11 @@ pub trait App { /// /// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. /// - /// To force a repaint, call either [`egui::Context::request_repaint`] or use [`Frame::repaint_signal`]. - fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>); + /// The given [`egui::CtxRef`] is only valid for the duration of this call. + /// The [`Frame`] however can be cloned and saved. + /// + /// To force a repaint, call either [`egui::Context::request_repaint`] or [`Frame::request_repaint`]. + fn update(&mut self, ctx: &egui::CtxRef, frame: &Frame); /// Called once before the first frame. /// @@ -113,13 +119,7 @@ pub trait App { /// [`egui::Context::set_visuals`] etc. /// /// Also allows you to restore state, if there is a storage (required the "persistence" feature). - fn setup( - &mut self, - _ctx: &egui::CtxRef, - _frame: &mut Frame<'_>, - _storage: Option<&dyn Storage>, - ) { - } + fn setup(&mut self, _ctx: &egui::CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {} /// If `true` a warm-up call to [`Self::update`] will be issued where /// `ctx.memory().everything_is_visible()` will be set to `true`. @@ -254,57 +254,92 @@ pub struct IconData { /// /// It provides methods to inspect the surroundings (are we on the web?), /// allocate textures, and change settings (e.g. window size). -pub struct Frame<'a>(backend::FrameBuilder<'a>); +/// +/// [`Frame`] is cheap to clone and is safe to pass to other threads. +#[derive(Clone)] +pub struct Frame(pub Arc>); + +impl Frame { + /// Create a `Frame` - called by the integration. + pub fn new(frame_data: backend::FrameData) -> Self { + Self(Arc::new(Mutex::new(frame_data))) + } -impl<'a> Frame<'a> { /// True if you are in a web environment. pub fn is_web(&self) -> bool { - self.info().web_info.is_some() + self.0.lock().info.web_info.is_some() } /// Information about the integration. - pub fn info(&self) -> &IntegrationInfo { - &self.0.info - } - - /// A way to allocate textures. - pub fn tex_allocator(&mut self) -> &mut dyn TextureAllocator { - self.0.tex_allocator + pub fn info(&self) -> IntegrationInfo { + self.0.lock().info.clone() } /// 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) { - self.0.output.quit = true; + pub fn quit(&self) { + self.0.lock().output.quit = true; } /// Set the desired inner size of the window (in egui points). - pub fn set_window_size(&mut self, size: egui::Vec2) { - self.0.output.window_size = Some(size); + pub fn set_window_size(&self, size: egui::Vec2) { + self.0.lock().output.window_size = Some(size); } /// Set the desired title of the window. - pub fn set_window_title(&mut self, title: &str) { - self.0.output.window_title = Some(title.to_owned()); + pub fn set_window_title(&self, title: &str) { + self.0.lock().output.window_title = Some(title.to_owned()); } /// Set whether to show window decorations (i.e. a frame around you app). /// If false it will be difficult to move and resize the app. - pub fn set_decorations(&mut self, decorated: bool) { - self.0.output.decorated = Some(decorated); + pub fn set_decorations(&self, decorated: bool) { + self.0.lock().output.decorated = Some(decorated); } /// When called, the native window will follow the /// movement of the cursor while the primary mouse button is down. /// - /// Does not work on the web, and works badly on Mac. - pub fn drag_window(&mut self) { - self.0.output.drag_window = true; + /// Does not work on the web. + pub fn drag_window(&self) { + self.0.lock().output.drag_window = true; + } + + /// This signals the [`egui`] integration that a repaint is required. + /// + /// Call this e.g. when a background process finishes in an async context and/or background thread. + pub fn request_repaint(&self) { + self.0.lock().repaint_signal.request_repaint(); + } + + /// for integrations only: call once per frame + #[must_use] + pub fn take_app_output(&self) -> crate::backend::AppOutput { + let mut lock = self.0.lock(); + let next_id = lock.output.tex_allocation_data.next_id; + let app_output = std::mem::take(&mut lock.output); + lock.output.tex_allocation_data.next_id = next_id; + app_output } - /// If you need to request a repaint from another thread, clone this and send it to that other thread. - pub fn repaint_signal(&self) -> std::sync::Arc { - self.0.repaint_signal.clone() + /// Allocate a texture. Free it again with [`Self::free_texture`]. + pub fn alloc_texture(&self, image: Image) -> egui::TextureId { + self.0.lock().output.tex_allocation_data.alloc(image) + } + + /// Free a texture that has been previously allocated with [`Self::alloc_texture`]. Idempotent. + pub fn free_texture(&self, id: egui::TextureId) { + self.0.lock().output.tex_allocation_data.free(id); + } +} + +impl TextureAllocator for Frame { + fn alloc(&self, image: Image) -> egui::TextureId { + self.0.lock().output.tex_allocation_data.alloc(image) + } + + fn free(&self, id: egui::TextureId) { + self.0.lock().output.tex_allocation_data.free(id); } } @@ -344,14 +379,19 @@ pub trait TextureAllocator { /// /// There is no way to change a texture. /// Instead allocate a new texture and free the previous one with [`Self::free`]. - fn alloc_srgba_premultiplied( - &mut self, - size: (usize, usize), - srgba_pixels: &[egui::Color32], - ) -> egui::TextureId; + fn alloc(&self, image: Image) -> egui::TextureId; /// Free the given texture. - fn free(&mut self, id: egui::TextureId); + fn free(&self, id: egui::TextureId); +} + +/// A 2D color image in RAM. +#[derive(Default)] +pub struct Image { + /// width, height + pub size: [usize; 2], + /// The pixels, row by row, from top to bottom. + pub pixels: Vec, } /// Abstraction for platform dependent texture reference @@ -367,13 +407,6 @@ pub trait NativeTexture { fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture); } -/// How to signal the [`egui`] integration that a repaint is required. -pub trait RepaintSignal: Send + Sync { - /// This signals the [`egui`] integration that a repaint is required. - /// This is meant to be called when a background process finishes in an async context and/or background thread. - fn request_repaint(&self); -} - // ---------------------------------------------------------------------------- /// A place where you can store custom data in a way that persists when you restart the app. @@ -423,29 +456,66 @@ pub const APP_KEY: &str = "app"; /// You only need to look here if you are writing a backend for `epi`. pub mod backend { + use std::collections::HashMap; + use super::*; + /// How to signal the [`egui`] integration that a repaint is required. + pub trait RepaintSignal: Send + Sync { + /// This signals the [`egui`] integration that a repaint is required. + /// + /// Call this e.g. when a background process finishes in an async context and/or background thread. + fn request_repaint(&self); + } + /// The data required by [`Frame`] each frame. - pub struct FrameBuilder<'a> { + pub struct FrameData { /// Information about the integration. pub info: IntegrationInfo, - /// A way to allocate textures (on integrations that support it). - pub tex_allocator: &'a mut dyn TextureAllocator, /// Where the app can issue commands back to the integration. - pub output: &'a mut AppOutput, + pub output: AppOutput, /// If you need to request a repaint from another thread, clone this and send it to that other thread. pub repaint_signal: std::sync::Arc, } - impl<'a> FrameBuilder<'a> { - /// Wrap us in a [`Frame`] to send to [`App::update`]. - pub fn build(self) -> Frame<'a> { - Frame(self) + /// The data needed in order to allocate and free textures/images. + #[derive(Default)] + pub struct TexAllocationData { + /// We allocate texture id linearly. + pub(crate) next_id: u64, + /// New creations this frame + pub creations: HashMap, + /// destructions this frame. + pub destructions: Vec, + } + + impl TexAllocationData { + /// Should only be used by integrations + pub fn take(&mut self) -> Self { + let next_id = self.next_id; + let ret = std::mem::take(self); + self.next_id = next_id; + ret + } + + /// Allocate a new texture. + pub fn alloc(&mut self, image: Image) -> egui::TextureId { + let id = self.next_id; + self.next_id += 1; + self.creations.insert(id, image); + egui::TextureId::User(id) + } + + /// Free an existing texture. + pub fn free(&mut self, id: egui::TextureId) { + if let egui::TextureId::User(id) = id { + self.destructions.push(id); + } } } /// Action that can be taken by the user app. - #[derive(Clone, Debug, Default, PartialEq)] + #[derive(Default)] pub struct AppOutput { /// Set to `true` to stop the app. /// This does nothing for web apps. @@ -462,5 +532,8 @@ pub mod backend { /// Set to true to drap window pub drag_window: bool, + + /// A way to allocate textures (on integrations that support it). + pub tex_allocation_data: TexAllocationData, } } From 25a2141225e6d49672590fd09329350e1d02bf33 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 25 Dec 2021 20:12:16 +0100 Subject: [PATCH 2/8] Add Image::from_rgba_unmultiplied --- eframe/examples/image.rs | 12 ++++-------- epi/src/lib.rs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/eframe/examples/image.rs b/eframe/examples/image.rs index 50b0e19c9a2..dda07f360dc 100644 --- a/eframe/examples/image.rs +++ b/eframe/examples/image.rs @@ -17,17 +17,13 @@ impl epi::App for MyApp { use image::GenericImageView; let image = image::load_from_memory(image_data).expect("Failed to load image"); let image_buffer = image.to_rgba8(); - let size = (image.width() as usize, image.height() as usize); + let size = [image.width() as usize, image.height() as usize]; let pixels = image_buffer.into_vec(); - assert_eq!(size.0 * size.1 * 4, pixels.len()); - let pixels: Vec<_> = pixels - .chunks_exact(4) - .map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3])) - .collect(); + let image = epi::Image::from_rgba_unmultiplied(size, &pixels); // Allocate a texture: - let texture = frame.tex_allocator().alloc(size, &pixels); - let size = egui::Vec2::new(size.0 as f32, size.1 as f32); + let texture = frame.alloc_texture(image); + let size = egui::Vec2::new(size[0] as f32, size[1] as f32); self.texture = Some((size, texture)); } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 4df92a5ce4a..e7be730bdd5 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -394,6 +394,20 @@ pub struct Image { pub pixels: Vec, } +impl Image { + /// Create an `Image` from flat RGBA data. + /// Panics unless `size[0] * size[1] * 4 == rgba.len()`. + /// This is usually what you want to use after having loaded an image. + pub fn from_rgba_unmultiplied(size: [usize; 2], rgba: &[u8]) -> Self { + assert_eq!(size[0] * size[1] * 4, rgba.len()); + let pixels = rgba + .chunks_exact(4) + .map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3])) + .collect(); + Self { size, pixels } + } +} + /// Abstraction for platform dependent texture reference pub trait NativeTexture { /// The native texture type. From 9e7c0ccfc514470a66255f0fbf2ebb83557eac96 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 25 Dec 2021 23:39:16 +0100 Subject: [PATCH 3/8] fix http_app and glow_wrapping --- egui_demo_lib/src/apps/http_app.rs | 48 +++++++++++++----------------- egui_web/src/glow_wrapping.rs | 17 ++++++----- epi/src/lib.rs | 4 +-- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/egui_demo_lib/src/apps/http_app.rs b/egui_demo_lib/src/apps/http_app.rs index e34b7a5d83e..c7b823d85e8 100644 --- a/egui_demo_lib/src/apps/http_app.rs +++ b/egui_demo_lib/src/apps/http_app.rs @@ -7,7 +7,7 @@ struct Resource { text: Option, /// If set, the response was an image. - image: Option, + image: Option, /// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md"). colored_text: Option, @@ -17,7 +17,7 @@ impl Resource { fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self { let content_type = response.content_type().unwrap_or_default(); let image = if content_type.starts_with("image/") { - Image::decode(&response.bytes) + decode_image(&response.bytes) } else { None }; @@ -95,13 +95,13 @@ impl epi::App for HttpApp { if trigger_fetch { let request = ehttp::Request::get(&self.url); - let repaint_signal = frame.repaint_signal(); + let frame = frame.clone(); let (sender, receiver) = std::sync::mpsc::channel(); self.in_progress = Some(receiver); ehttp::fetch(request, move |response| { sender.send(response).ok(); - repaint_signal.request_repaint(); + frame.request_repaint(); }); } @@ -213,7 +213,7 @@ fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, re if let Some(image) = image { if let Some(texture_id) = tex_mngr.texture(frame, &response.url, image) { - let mut size = egui::Vec2::new(image.size.0 as f32, image.size.1 as f32); + let mut size = egui::Vec2::new(image.size[0] as f32, image.size[1] as f32); size *= (ui.available_width() / size.x).min(1.0); ui.image(texture_id, size); } @@ -297,37 +297,29 @@ struct TexMngr { } impl TexMngr { - fn texture(&mut self, frame: &epi::Frame, url: &str, image: &Image) -> Option { + fn texture( + &mut self, + frame: &epi::Frame, + url: &str, + image: &epi::Image, + ) -> Option { if self.loaded_url != url { if let Some(texture_id) = self.texture_id.take() { - frame.tex_allocator().free(texture_id); + frame.free_texture(texture_id); } - self.texture_id = Some(frame.tex_allocator().alloc(image.size, &image.pixels)); + self.texture_id = Some(frame.alloc_texture(image.clone())); self.loaded_url = url.to_owned(); } self.texture_id } } -struct Image { - size: (usize, usize), - pixels: Vec, -} - -impl Image { - fn decode(bytes: &[u8]) -> Option { - use image::GenericImageView; - let image = image::load_from_memory(bytes).ok()?; - let image_buffer = image.to_rgba8(); - let size = (image.width() as usize, image.height() as usize); - let pixels = image_buffer.into_vec(); - assert_eq!(size.0 * size.1 * 4, pixels.len()); - let pixels = pixels - .chunks(4) - .map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3])) - .collect(); - - Some(Image { size, pixels }) - } +fn decode_image(bytes: &[u8]) -> Option { + use image::GenericImageView; + let image = image::load_from_memory(bytes).ok()?; + let image_buffer = image.to_rgba8(); + let size = [image.width() as usize, image.height() as usize]; + let pixels = image_buffer.into_vec(); + Some(epi::Image::from_rgba_unmultiplied(size, &pixels)) } diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index 2411ed8088d..858ec2524c8 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -1,13 +1,12 @@ -#[cfg(not(target_arch = "wasm32"))] -use crate::web_sys::WebGl2RenderingContext; -use crate::web_sys::WebGlRenderingContext; use crate::{canvas_element_or_die, console_error}; use egui::{ClippedMesh, Rgba, Texture}; use egui_glow::glow; -use epi::TextureAllocator; use wasm_bindgen::JsCast; use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; +#[cfg(not(target_arch = "wasm32"))] +use web_sys::WebGl2RenderingContext; +use web_sys::WebGlRenderingContext; pub(crate) struct WrappedGlowPainter { pub(crate) gl_ctx: glow::Context, @@ -67,8 +66,12 @@ fn requires_brightening(canvas: &web_sys::HtmlCanvasElement) -> bool { } impl crate::Painter for WrappedGlowPainter { - fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator { - &mut self.painter + fn set_user_texture(&mut self, tex_id: u64, image: epi::Image) { + self.painter.set_user_texture(&self.gl_ctx, tex_id, &image); + } + + fn free_user_texture(&mut self, tex_id: u64) { + self.painter.free_user_texture(tex_id); } fn debug_info(&self) -> String { @@ -99,8 +102,8 @@ impl crate::Painter for WrappedGlowPainter { ) -> Result<(), JsValue> { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; self.painter.paint_meshes( - canvas_dimension, &self.gl_ctx, + canvas_dimension, pixels_per_point, clipped_meshes, ); diff --git a/epi/src/lib.rs b/epi/src/lib.rs index e7be730bdd5..25b3aa7ca3a 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -240,7 +240,7 @@ impl Default for NativeOptions { /// Image data for the icon. #[derive(Clone)] pub struct IconData { - /// RGBA pixels. + /// RGBA pixels, unmultiplied. pub rgba: Vec, /// Image width. This should be a multiple of 4. @@ -386,7 +386,7 @@ pub trait TextureAllocator { } /// A 2D color image in RAM. -#[derive(Default)] +#[derive(Clone, Default)] pub struct Image { /// width, height pub size: [usize; 2], From 0e2b0009a087b8aeec850c724f70392bad8cd7e9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 26 Dec 2021 09:45:11 +0100 Subject: [PATCH 4/8] free/set_user_texture -> free/set_texture --- egui_glium/src/epi_backend.rs | 4 ++-- egui_glium/src/painter.rs | 4 ++-- egui_glow/src/epi_backend.rs | 4 ++-- egui_glow/src/painter.rs | 4 ++-- egui_web/src/backend.rs | 4 ++-- egui_web/src/glow_wrapping.rs | 8 ++++---- egui_web/src/painter.rs | 4 ++-- egui_web/src/webgl1.rs | 4 ++-- egui_web/src/webgl2.rs | 4 ++-- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/egui_glium/src/epi_backend.rs b/egui_glium/src/epi_backend.rs index e57f876cdaa..1dd52810c19 100644 --- a/egui_glium/src/epi_backend.rs +++ b/egui_glium/src/epi_backend.rs @@ -71,7 +71,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { let clipped_meshes = integration.egui_ctx.tessellate(shapes); for (id, image) in tex_allocation_data.creations { - painter.set_user_texture(&display, id, &image); + painter.set_texture(&display, id, &image); } // paint: @@ -93,7 +93,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { } for id in tex_allocation_data.destructions.drain(..) { - painter.free_user_texture(id); + painter.free_texture(id); } { diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 4389272f833..d48305202e9 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -222,7 +222,7 @@ impl Painter { // ------------------------------------------------------------------------ - pub fn set_user_texture( + pub fn set_texture( &mut self, facade: &dyn glium::backend::Facade, tex_id: u64, @@ -247,7 +247,7 @@ impl Painter { self.user_textures.insert(tex_id, gl_texture.into()); } - pub fn free_user_texture(&mut self, tex_id: u64) { + pub fn free_texture(&mut self, tex_id: u64) { self.user_textures.remove(&tex_id); } diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index a7c3cb1be11..73afcde6330 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -87,7 +87,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { let clipped_meshes = integration.egui_ctx.tessellate(shapes); for (id, image) in tex_allocation_data.creations { - painter.set_user_texture(&gl, id, &image); + painter.set_texture(&gl, id, &image); } // paint: @@ -111,7 +111,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { } for id in tex_allocation_data.destructions.drain(..) { - painter.free_user_texture(id); + painter.free_texture(id); } { diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index bb6fa3818cc..07652dd6f3d 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -387,7 +387,7 @@ impl Painter { // ------------------------------------------------------------------------ - pub fn set_user_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) { + pub fn set_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) { self.assert_not_destroyed(); assert_eq!( @@ -417,7 +417,7 @@ impl Painter { } } - pub fn free_user_texture(&mut self, tex_id: u64) { + pub fn free_texture(&mut self, tex_id: u64) { self.user_textures.remove(&tex_id); } diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 62fe593d1dc..58a8b1c651a 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -205,7 +205,7 @@ impl AppRunner { } = app_output; for (id, image) in tex_allocation_data.creations { - self.painter.set_user_texture(id, image); + self.painter.set_texture(id, image); } self.pending_texture_destructions = tex_allocation_data.destructions; } @@ -220,7 +220,7 @@ impl AppRunner { self.painter .paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())?; for id in self.pending_texture_destructions.drain(..) { - self.painter.free_user_texture(id); + self.painter.free_texture(id); } Ok(()) } diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index 858ec2524c8..5709774de34 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -66,12 +66,12 @@ fn requires_brightening(canvas: &web_sys::HtmlCanvasElement) -> bool { } impl crate::Painter for WrappedGlowPainter { - fn set_user_texture(&mut self, tex_id: u64, image: epi::Image) { - self.painter.set_user_texture(&self.gl_ctx, tex_id, &image); + fn set_texture(&mut self, tex_id: u64, image: epi::Image) { + self.painter.set_texture(&self.gl_ctx, tex_id, &image); } - fn free_user_texture(&mut self, tex_id: u64) { - self.painter.free_user_texture(tex_id); + fn free_texture(&mut self, tex_id: u64) { + self.painter.free_texture(tex_id); } fn debug_info(&self) -> String { diff --git a/egui_web/src/painter.rs b/egui_web/src/painter.rs index 7a3d424bf63..079b9fd57be 100644 --- a/egui_web/src/painter.rs +++ b/egui_web/src/painter.rs @@ -1,9 +1,9 @@ use wasm_bindgen::prelude::JsValue; pub trait Painter { - fn set_user_texture(&mut self, tex_id: u64, image: epi::Image); + fn set_texture(&mut self, tex_id: u64, image: epi::Image); - fn free_user_texture(&mut self, tex_id: u64); + fn free_texture(&mut self, tex_id: u64); fn debug_info(&self) -> String; diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index d329fd58b11..a2c8b893c68 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -257,7 +257,7 @@ impl epi::NativeTexture for WebGlPainter { } impl crate::Painter for WebGlPainter { - fn set_user_texture(&mut self, tex_id: u64, image: epi::Image) { + fn set_texture(&mut self, tex_id: u64, image: epi::Image) { assert_eq!( image.size[0] * image.size[1], image.pixels.len(), @@ -304,7 +304,7 @@ impl crate::Painter for WebGlPainter { self.user_textures.insert(tex_id, gl_texture); } - fn free_user_texture(&mut self, tex_id: u64) { + fn free_texture(&mut self, tex_id: u64) { self.user_textures.remove(&tex_id); } diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index 53aedc6080a..f6d66de435f 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -241,7 +241,7 @@ impl epi::NativeTexture for WebGl2Painter { } impl crate::Painter for WebGl2Painter { - fn set_user_texture(&mut self, tex_id: u64, image: epi::Image) { + fn set_texture(&mut self, tex_id: u64, image: epi::Image) { assert_eq!( image.size[0] * image.size[1], image.pixels.len(), @@ -288,7 +288,7 @@ impl crate::Painter for WebGl2Painter { self.user_textures.insert(tex_id, gl_texture); } - fn free_user_texture(&mut self, tex_id: u64) { + fn free_texture(&mut self, tex_id: u64) { self.user_textures.remove(&tex_id); } From 2b2b2da5169c5adf41ed5d1aaabb1acfec57421b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 26 Dec 2021 10:19:33 +0100 Subject: [PATCH 5/8] Update changelogs --- eframe/CHANGELOG.md | 6 +++--- egui-winit/CHANGELOG.md | 1 + egui_glium/CHANGELOG.md | 1 + egui_glium/src/painter.rs | 4 ++-- egui_glow/CHANGELOG.md | 1 + egui_glow/src/painter.rs | 7 +++---- egui_web/CHANGELOG.md | 1 + egui_web/src/webgl1.rs | 2 +- egui_web/src/webgl2.rs | 2 +- 9 files changed, 14 insertions(+), 11 deletions(-) diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index 5b9c9eb96d7..97debfae6a7 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -5,9 +5,9 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m ## Unreleased -* `Frame` can now be cloned, saved, and passed to background threads. -* Added `Frame::request_repaint` to replace `repaint_signal`. -* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator`. +* `Frame` can now be cloned, saved, and passed to background threads ([#999](https://github.com/emilk/egui/pull/999)). +* Added `Frame::request_repaint` to replace `repaint_signal` ([#999](https://github.com/emilk/egui/pull/999)). +* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator` ([#999](https://github.com/emilk/egui/pull/999)). ## 0.15.0 - 2021-10-24 diff --git a/egui-winit/CHANGELOG.md b/egui-winit/CHANGELOG.md index 757a92cbc0a..35d68f329b4 100644 --- a/egui-winit/CHANGELOG.md +++ b/egui-winit/CHANGELOG.md @@ -9,5 +9,6 @@ All notable changes to the `egui-winit` integration will be noted in this file. * Remove `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)). * Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)). + ## 0.15.0 - 2021-10-24 First stand-alone release. Previously part of `egui_glium`. diff --git a/egui_glium/CHANGELOG.md b/egui_glium/CHANGELOG.md index 3ca59c0420d..02302c7269d 100644 --- a/egui_glium/CHANGELOG.md +++ b/egui_glium/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the `egui_glium` integration will be noted in this file. * Simplify `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)). * Remove `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)). * Updated `glium` to 0.31 ([#930](https://github.com/emilk/egui/pull/930)). +* Changed the `Painter` inteface slightly ([#999](https://github.com/emilk/egui/pull/999)). ## 0.15.0 - 2021-10-24 diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index d48305202e9..e5dc378d17c 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -106,7 +106,7 @@ impl Painter { } #[inline(never)] // Easier profiling - pub fn paint_mesh( + fn paint_mesh( &mut self, target: &mut T, display: &glium::Display, @@ -251,7 +251,7 @@ impl Painter { self.user_textures.remove(&tex_id); } - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> { + fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> { match texture_id { egui::TextureId::Egui => self.egui_texture.as_ref(), egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()), diff --git a/egui_glow/CHANGELOG.md b/egui_glow/CHANGELOG.md index 21b5b4e8411..19c13f4713b 100644 --- a/egui_glow/CHANGELOG.md +++ b/egui_glow/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the `egui_glow` integration will be noted in this file. * Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)). * Remove `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)). * Updated `glutin` to 0.28 ([#930](https://github.com/emilk/egui/pull/930)). +* Changed the `Painter` inteface slightly ([#999](https://github.com/emilk/egui/pull/999)). ## 0.15.0 - 2021-10-24 diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 07652dd6f3d..8f6a3d1632e 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -421,7 +421,7 @@ impl Painter { self.user_textures.remove(&tex_id); } - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option { + fn get_texture(&self, texture_id: egui::TextureId) -> Option { self.assert_not_destroyed(); match texture_id { @@ -500,9 +500,8 @@ impl epi::NativeTexture for Painter { let id = self.next_native_tex_id; self.next_native_tex_id += 1; - if let Some(old_tex) = self.user_textures.insert(id, native) { - self.textures_to_destroy.push(old_tex); - } + self.user_textures.insert(id, native); + egui::TextureId::User(id as u64) } diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index 45db3ee9aee..b1583810049 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the `egui_web` integration will be noted in this file. * Fix [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)). * 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 * Remove "http" feature (use https://github.com/emilk/ehttp instead!). diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index a2c8b893c68..db7751fc2db 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -109,7 +109,7 @@ impl WebGlPainter { }) } - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { + fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { match texture_id { egui::TextureId::Egui => Some(&self.egui_texture), egui::TextureId::User(id) => self.user_textures.get(&id), diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index f6d66de435f..e774f984973 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -93,7 +93,7 @@ impl WebGl2Painter { }) } - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { + fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { match texture_id { egui::TextureId::Egui => Some(&self.egui_texture), egui::TextureId::User(id) => self.user_textures.get(&id), From 22abad2d7f0e6114b073ecce228d584744b19ca1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 26 Dec 2021 11:19:45 +0100 Subject: [PATCH 6/8] Remember to handle texture allocations from warm-up pass --- egui-winit/src/epi.rs | 4 ++-- epi/src/lib.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index e41cec0ccab..b9fef659e6d 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -51,7 +51,6 @@ fn window_builder_drag_and_drop( window_builder } -#[must_use] pub fn handle_app_output( window: &winit::window::Window, current_pixels_per_point: f32, @@ -253,7 +252,8 @@ impl EpiIntegration { fn warm_up(&mut self, window: &winit::window::Window) { let saved_memory = self.egui_ctx.memory().clone(); self.egui_ctx.memory().set_everything_is_visible(true); - self.update(window); + let (_, tex_alloc_data, _) = self.update(window); + self.frame.0.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame *self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. self.egui_ctx.clear_animations(); } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 25b3aa7ca3a..929f2dee278 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -313,7 +313,6 @@ impl Frame { } /// for integrations only: call once per frame - #[must_use] pub fn take_app_output(&self) -> crate::backend::AppOutput { let mut lock = self.0.lock(); let next_id = lock.output.tex_allocation_data.next_id; @@ -494,6 +493,7 @@ pub mod backend { /// The data needed in order to allocate and free textures/images. #[derive(Default)] + #[must_use] pub struct TexAllocationData { /// We allocate texture id linearly. pub(crate) next_id: u64, @@ -530,6 +530,7 @@ pub mod backend { /// Action that can be taken by the user app. #[derive(Default)] + #[must_use] pub struct AppOutput { /// Set to `true` to stop the app. /// This does nothing for web apps. From 6907040e0a79c3f9acb8bd5136a95e1594f33f42 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 26 Dec 2021 15:39:34 +0100 Subject: [PATCH 7/8] tweak --- egui_glow/src/painter.rs | 1 - egui_web/src/backend.rs | 1 - epi/src/lib.rs | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 8f6a3d1632e..0c848094cef 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -297,7 +297,6 @@ impl Painter { pixels_per_point: f32, clipped_meshes: Vec, ) { - //chimera of egui_glow and egui_web self.assert_not_destroyed(); if let Some(ref mut post_process) = self.post_process { diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 58a8b1c651a..fa965d0521c 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -141,7 +141,6 @@ impl AppRunner { runner .app .setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage)); - // TODO: handle app output } Ok(runner) diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 929f2dee278..d6dd51a1806 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -542,10 +542,10 @@ pub mod backend { /// Set to some string to rename the outer window (e.g. glium window) to this title. pub window_title: Option, - /// Set to some bool to change window decorations + /// Set to some bool to change window decorations. pub decorated: Option, - /// Set to true to drap window + /// Set to true to drag window while primary mouse button is down. pub drag_window: bool, /// A way to allocate textures (on integrations that support it). From 2a67652c0be145e6a491a0250527222fed60a184 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 26 Dec 2021 15:52:36 +0100 Subject: [PATCH 8/8] Fix egui_web build --- Cargo.lock | 1 - egui-winit/src/epi.rs | 6 +++--- egui_web/src/backend.rs | 2 +- epi/Cargo.toml | 1 - epi/src/lib.rs | 37 ++++++++++++++++++++++--------------- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91a9aba8ef4..0e5a8a4f357 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1002,7 +1002,6 @@ version = "0.15.0" dependencies = [ "directories-next", "egui", - "parking_lot", "ron", "serde", ] diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index b9fef659e6d..bbd82d3d51c 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -246,14 +246,14 @@ impl EpiIntegration { self.quit |= app_output.quit; let tex_alloc_data = crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); - self.frame.0.lock().output.tex_allocation_data = tex_alloc_data; // Do it later + self.frame.lock().output.tex_allocation_data = tex_alloc_data; // Do it later } fn warm_up(&mut self, window: &winit::window::Window) { let saved_memory = self.egui_ctx.memory().clone(); self.egui_ctx.memory().set_everything_is_visible(true); let (_, tex_alloc_data, _) = self.update(window); - self.frame.0.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame + self.frame.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame *self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. self.egui_ctx.clear_animations(); } @@ -295,7 +295,7 @@ impl EpiIntegration { crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32; - self.frame.0.lock().info.cpu_usage = Some(frame_time); + self.frame.lock().info.cpu_usage = Some(frame_time); (needs_repaint, tex_allocation_data, shapes) } diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index fa965d0521c..eda65ee63bb 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -209,7 +209,7 @@ impl AppRunner { self.pending_texture_destructions = tex_allocation_data.destructions; } - self.frame.0.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32); + self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32); Ok((egui_output, clipped_meshes)) } diff --git a/epi/Cargo.toml b/epi/Cargo.toml index 00870eb7769..83d0f76220f 100644 --- a/epi/Cargo.toml +++ b/epi/Cargo.toml @@ -25,7 +25,6 @@ all-features = true [dependencies] egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] } -parking_lot = "0.11" directories-next = { version = "2", optional = true } ron = { version = "0.7", optional = true } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index d6dd51a1806..3dbcd86f903 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -95,8 +95,7 @@ pub mod file_storage; pub use egui; // Re-export for user convenience -use parking_lot::Mutex; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; // ---------------------------------------------------------------------------- @@ -261,40 +260,48 @@ pub struct Frame(pub Arc>); impl Frame { /// Create a `Frame` - called by the integration. + #[doc(hidden)] pub fn new(frame_data: backend::FrameData) -> Self { Self(Arc::new(Mutex::new(frame_data))) } + /// Convenience to access the underlying `backend::FrameData`. + #[doc(hidden)] + #[inline] + pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> { + self.0.lock().unwrap() + } + /// True if you are in a web environment. pub fn is_web(&self) -> bool { - self.0.lock().info.web_info.is_some() + self.lock().info.web_info.is_some() } /// Information about the integration. pub fn info(&self) -> IntegrationInfo { - self.0.lock().info.clone() + self.lock().info.clone() } /// 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(&self) { - self.0.lock().output.quit = true; + self.lock().output.quit = true; } /// Set the desired inner size of the window (in egui points). pub fn set_window_size(&self, size: egui::Vec2) { - self.0.lock().output.window_size = Some(size); + self.lock().output.window_size = Some(size); } /// Set the desired title of the window. pub fn set_window_title(&self, title: &str) { - self.0.lock().output.window_title = Some(title.to_owned()); + self.lock().output.window_title = Some(title.to_owned()); } /// Set whether to show window decorations (i.e. a frame around you app). /// If false it will be difficult to move and resize the app. pub fn set_decorations(&self, decorated: bool) { - self.0.lock().output.decorated = Some(decorated); + self.lock().output.decorated = Some(decorated); } /// When called, the native window will follow the @@ -302,19 +309,19 @@ impl Frame { /// /// Does not work on the web. pub fn drag_window(&self) { - self.0.lock().output.drag_window = true; + self.lock().output.drag_window = true; } /// This signals the [`egui`] integration that a repaint is required. /// /// Call this e.g. when a background process finishes in an async context and/or background thread. pub fn request_repaint(&self) { - self.0.lock().repaint_signal.request_repaint(); + self.lock().repaint_signal.request_repaint(); } /// for integrations only: call once per frame pub fn take_app_output(&self) -> crate::backend::AppOutput { - let mut lock = self.0.lock(); + let mut lock = self.lock(); let next_id = lock.output.tex_allocation_data.next_id; let app_output = std::mem::take(&mut lock.output); lock.output.tex_allocation_data.next_id = next_id; @@ -323,22 +330,22 @@ impl Frame { /// Allocate a texture. Free it again with [`Self::free_texture`]. pub fn alloc_texture(&self, image: Image) -> egui::TextureId { - self.0.lock().output.tex_allocation_data.alloc(image) + self.lock().output.tex_allocation_data.alloc(image) } /// Free a texture that has been previously allocated with [`Self::alloc_texture`]. Idempotent. pub fn free_texture(&self, id: egui::TextureId) { - self.0.lock().output.tex_allocation_data.free(id); + self.lock().output.tex_allocation_data.free(id); } } impl TextureAllocator for Frame { fn alloc(&self, image: Image) -> egui::TextureId { - self.0.lock().output.tex_allocation_data.alloc(image) + self.lock().output.tex_allocation_data.alloc(image) } fn free(&self, id: egui::TextureId) { - self.0.lock().output.tex_allocation_data.free(id); + self.lock().output.tex_allocation_data.free(id); } }