From d8300037ad9c2de3ec27146b3fa63a23a6198e95 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 8 Mar 2022 08:26:43 +0100 Subject: [PATCH 01/12] Fix `cargo run -p egui_demo_app` (#1343) Broken by https://github.com/emilk/egui/pull/1303 --- .github/workflows/rust.yml | 13 +++++++++++++ egui_glow/src/lib.rs | 13 +++---------- sh/check.sh | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a4a0c779..5865f0bd 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -56,6 +56,19 @@ jobs: command: check args: -p egui_demo_app --lib --target wasm32-unknown-unknown + check_egui_demo_app: + name: cargo check -p egui_demo_app + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.56.0 + override: true + - name: check + run: cargo check -p egui_demo_app + check_wasm_eframe_with_features: name: cargo check wasm eframe runs-on: ubuntu-20.04 diff --git a/egui_glow/src/lib.rs b/egui_glow/src/lib.rs index 6889e9cd..227b6527 100644 --- a/egui_glow/src/lib.rs +++ b/egui_glow/src/lib.rs @@ -100,15 +100,8 @@ pub mod winit; #[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] pub use winit::*; -#[cfg(all( - not(target_arch = "wasm32"), - feature = "persistence", - feature = "winit" -))] +#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] mod epi_backend; -#[cfg(all( - not(target_arch = "wasm32"), - feature = "persistence", - feature = "winit" -))] + +#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] pub use epi_backend::{run, NativeOptions}; diff --git a/sh/check.sh b/sh/check.sh index d60b71b6..86de92f9 100755 --- a/sh/check.sh +++ b/sh/check.sh @@ -37,6 +37,7 @@ cargo doc --document-private-items --no-deps --all-features (cd eframe && cargo check --all-features) (cd egui && cargo check --all-features) +(cd egui_demo_app && cargo check --all-features) (cd egui_extras && cargo check --all-features) (cd egui_glium && cargo check --all-features) (cd egui_glow && cargo check --all-features) From 0e7a4fdbfd0b2456307de1bb3ed689d582f395a3 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 8 Mar 2022 09:50:53 -0500 Subject: [PATCH 02/12] Add method to set a Plot's margin (#1308) --- egui/src/widgets/plot/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index b6f08096..c8fcf2c7 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -287,6 +287,14 @@ impl Plot { self } + /// Set the side margin as a fraction of the plot size. + /// + /// For instance, a value of `0.1` will add 10% space on both sides. + pub fn set_margin_fraction(mut self, margin_fraction: Vec2) -> Self { + self.margin_fraction = margin_fraction; + self + } + /// Whether to allow zooming in the plot by dragging out a box with the secondary mouse button. /// /// Default: `true`. From 30399bf6ff60da658dc7895b39505f0aea8e7a7d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Mar 2022 08:08:46 +0100 Subject: [PATCH 03/12] Update regex crate v1.5.4 -> v1.5.5 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f6d76f4..9338554a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2573,9 +2573,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", From 5d950e1c1564399892230da23ce6ed551c293543 Mon Sep 17 00:00:00 2001 From: Zachary Kohnen Date: Thu, 10 Mar 2022 08:13:32 +0100 Subject: [PATCH 04/12] [egui_web] Prevent event handlers from running if code has panicked (#1306) Closes: #1290 Fix panic reported by @Titaniumtown See https://github.com/emilk/egui/pull/1306#issuecomment-1060775376 --- egui/src/widgets/plot/mod.rs | 10 +- egui_web/CHANGELOG.md | 1 + egui_web/src/backend.rs | 40 +++- egui_web/src/lib.rs | 406 ++++++++++++++++++----------------- egui_web/src/text_agent.rs | 124 ++++++----- 5 files changed, 324 insertions(+), 257 deletions(-) diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index c8fcf2c7..1668d190 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -1,6 +1,6 @@ //! Simple plotting library. -use std::{cell::RefCell, ops::RangeInclusive, rc::Rc}; +use std::{cell::Cell, ops::RangeInclusive, rc::Rc}; use crate::*; use epaint::ahash::AHashSet; @@ -92,7 +92,7 @@ impl PlotMemory { pub struct LinkedAxisGroup { pub(crate) link_x: bool, pub(crate) link_y: bool, - pub(crate) bounds: Rc>>, + pub(crate) bounds: Rc>>, } impl LinkedAxisGroup { @@ -100,7 +100,7 @@ impl LinkedAxisGroup { Self { link_x, link_y, - bounds: Rc::new(RefCell::new(None)), + bounds: Rc::new(Cell::new(None)), } } @@ -132,11 +132,11 @@ impl LinkedAxisGroup { } fn get(&self) -> Option { - *self.bounds.borrow() + self.bounds.get() } fn set(&self, bounds: PlotBounds) { - *self.bounds.borrow_mut() = Some(bounds); + self.bounds.set(Some(bounds)); } } diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index 18752976..79754635 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the `egui_web` integration will be noted in this file. ## Unreleased +* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)) ## 0.17.0 - 2022-02-22 diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 56acc266..f3033cac 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -359,11 +359,37 @@ pub fn start(canvas_id: &str, app: Box) -> Result Result { - let runner_ref = AppRunnerRef(Arc::new(Mutex::new(app_runner))); - install_canvas_events(&runner_ref)?; - install_document_events(&runner_ref)?; - text_agent::install_text_agent(&runner_ref)?; - repaint_every_ms(&runner_ref, 1000)?; // just in case. TODO: make it a parameter - paint_and_schedule(runner_ref.clone())?; - Ok(runner_ref) + let runner_container = AppRunnerContainer { + runner: Arc::new(Mutex::new(app_runner)), + panicked: Arc::new(AtomicBool::new(false)), + }; + + install_canvas_events(&runner_container)?; + install_document_events(&runner_container)?; + text_agent::install_text_agent(&runner_container)?; + repaint_every_ms(&runner_container, 1000)?; // just in case. TODO: make it a parameter + + paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?; + + // Disable all event handlers on panic + std::panic::set_hook(Box::new({ + let previous_hook = std::panic::take_hook(); + + let panicked = runner_container.panicked; + + move |panic_info| { + tracing::info_span!("egui_panic_handler").in_scope(|| { + tracing::trace!("setting panicked flag"); + + panicked.store(true, SeqCst); + + tracing::info!("egui disabled all event handlers due to panic"); + }); + + // Propagate panic info to the previously registered panic hook + previous_hook(panic_info); + } + })); + + Ok(runner_container.runner) } diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 04b767fe..1f271ead 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -29,14 +29,16 @@ pub mod webgl2; pub use backend::*; -use egui::mutex::Mutex; +use egui::mutex::{Mutex, MutexGuard}; pub use wasm_bindgen; pub use web_sys; use input::*; pub use painter::Painter; +use web_sys::EventTarget; use std::collections::BTreeMap; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use wasm_bindgen::prelude::*; @@ -302,12 +304,56 @@ pub fn percent_decode(s: &str) -> String { // ---------------------------------------------------------------------------- -#[derive(Clone)] -pub struct AppRunnerRef(Arc>); +pub type AppRunnerRef = Arc>; -fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> { +pub struct AppRunnerContainer { + runner: AppRunnerRef, + /// Set to `true` if there is a panic. + /// Used to ignore callbacks after a panic. + panicked: Arc, +} + +impl AppRunnerContainer { + /// Convenience function to reduce boilerplate and ensure that all event handlers + /// are dealt with in the same way + pub fn add_event_listener( + &self, + target: &EventTarget, + event_name: &'static str, + mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static, + ) -> Result<(), JsValue> { + use wasm_bindgen::JsCast; + + // Create a JS closure based on the FnMut provided + let closure = Closure::wrap({ + // Clone atomics + let runner_ref = self.runner.clone(); + let panicked = self.panicked.clone(); + + Box::new(move |event: web_sys::Event| { + // Only call the wrapped closure if the egui code has not panicked + if !panicked.load(Ordering::SeqCst) { + // Cast the event to the expected event type + let event = event.unchecked_into::(); + + closure(event, runner_ref.lock()); + } + }) as Box + }); + + // Add the event listener to the target + target.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; + + // Bypass closure drop so that event handler can call the closure + closure.forget(); + + Ok(()) + } +} + +fn paint_and_schedule(runner_ref: &AppRunnerRef, panicked: Arc) -> Result<(), JsValue> { fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { - let mut runner_lock = runner_ref.0.lock(); + let mut runner_lock = runner_ref.lock(); if runner_lock.needs_repaint.fetch_and_clear() { let (needs_repaint, clipped_meshes) = runner_lock.logic()?; runner_lock.paint(clipped_meshes)?; @@ -320,34 +366,40 @@ fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> { Ok(()) } - fn request_animation_frame(runner_ref: AppRunnerRef) -> Result<(), JsValue> { + fn request_animation_frame( + runner_ref: AppRunnerRef, + panicked: Arc, + ) -> Result<(), JsValue> { use wasm_bindgen::JsCast; let window = web_sys::window().unwrap(); - let closure = Closure::once(move || paint_and_schedule(runner_ref)); + let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked)); window.request_animation_frame(closure.as_ref().unchecked_ref())?; closure.forget(); // We must forget it, or else the callback is canceled on drop Ok(()) } - paint_if_needed(&runner_ref)?; - request_animation_frame(runner_ref) + // Only paint and schedule if there has been no panic + if !panicked.load(Ordering::SeqCst) { + paint_if_needed(runner_ref)?; + request_animation_frame(runner_ref.clone(), panicked)?; + } + + Ok(()) } -fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { - use wasm_bindgen::JsCast; +fn install_document_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); - { - // keydown - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { + runner_container.add_event_listener( + &document, + "keydown", + |event: web_sys::KeyboardEvent, mut runner_lock| { if event.is_composing() || event.key_code() == 229 { // https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/ return; } - let mut runner_lock = runner_ref.0.lock(); let modifiers = modifiers_from_event(&event); runner_lock.input.raw.modifiers = modifiers; @@ -400,16 +452,13 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { if prevent_default { event.prevent_default(); } - }) as Box); - document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - // keyup - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &document, + "keyup", + |event: web_sys::KeyboardEvent, mut runner_lock| { let modifiers = modifiers_from_event(&event); runner_lock.input.raw.modifiers = modifiers; if let Some(key) = translate_key(&event.key()) { @@ -420,19 +469,16 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { }); } runner_lock.needs_repaint.set_true(); - }) as Box); - document.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; #[cfg(web_sys_unstable_apis)] - { - // paste - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::ClipboardEvent| { + runner_container.add_event_listener( + &document, + "paste", + |event: web_sys::ClipboardEvent, mut runner_lock| { if let Some(data) = event.clipboard_data() { if let Ok(text) = data.get_data("text") { - let mut runner_lock = runner_ref.0.lock(); runner_lock .input .raw @@ -443,85 +489,90 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { event.prevent_default(); } } - }) as Box); - document.add_event_listener_with_callback("paste", closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; #[cfg(web_sys_unstable_apis)] - { - // cut - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |_: web_sys::ClipboardEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &document, + "cut", + |_: web_sys::ClipboardEvent, mut runner_lock| { runner_lock.input.raw.events.push(egui::Event::Cut); runner_lock.needs_repaint.set_true(); - }) as Box); - document.add_event_listener_with_callback("cut", closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; #[cfg(web_sys_unstable_apis)] - { - // copy - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |_: web_sys::ClipboardEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &document, + "copy", + |_: web_sys::ClipboardEvent, mut runner_lock| { runner_lock.input.raw.events.push(egui::Event::Copy); runner_lock.needs_repaint.set_true(); - }) as Box); - document.add_event_listener_with_callback("copy", closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; for event_name in &["load", "pagehide", "pageshow", "resize"] { - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move || { - runner_ref.0.lock().needs_repaint.set_true(); - }) as Box); - window.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); + runner_container.add_event_listener( + &document, + event_name, + |_: web_sys::Event, runner_lock| { + runner_lock.needs_repaint.set_true(); + }, + )?; } - { - // hashchange - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move || { - let runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &document, + "hashchange", + |_: web_sys::Event, runner_lock| { let mut frame_lock = runner_lock.frame.lock(); // `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here if let Some(web_info) = &mut frame_lock.info.web_info { web_info.location.hash = location_hash(); } - }) as Box); - window.add_event_listener_with_callback("hashchange", closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; Ok(()) } /// Repaint at least every `ms` milliseconds. -fn repaint_every_ms(runner_ref: &AppRunnerRef, milliseconds: i32) -> Result<(), JsValue> { +pub fn repaint_every_ms( + runner_container: &AppRunnerContainer, + milliseconds: i32, +) -> Result<(), JsValue> { assert!(milliseconds >= 0); + use wasm_bindgen::JsCast; + let window = web_sys::window().unwrap(); - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move || { - runner_ref.0.lock().needs_repaint.set_true(); + + let closure = Closure::wrap(Box::new({ + let runner = runner_container.runner.clone(); + let panicked = runner_container.panicked.clone(); + + move || { + // Do not lock the runner if the code has panicked + if !panicked.load(Ordering::SeqCst) { + runner.lock().needs_repaint.set_true(); + } + } }) as Box); + window.set_interval_with_callback_and_timeout_and_arguments_0( closure.as_ref().unchecked_ref(), milliseconds, )?; + closure.forget(); Ok(()) } -fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { +fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> { use wasm_bindgen::JsCast; - let canvas = canvas_element(runner_ref.0.lock().canvas_id()).unwrap(); + let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap(); { // By default, right-clicks open a context menu. @@ -534,12 +585,11 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { closure.forget(); } - { - let event_name = "mousedown"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { + runner_container.add_event_listener( + &canvas, + "mousedown", + |event: web_sys::MouseEvent, mut runner_lock| { if let Some(button) = button_from_mouse_event(&event) { - let mut runner_lock = runner_ref.0.lock(); let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event); let modifiers = runner_lock.input.raw.modifiers; runner_lock @@ -556,16 +606,13 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { } event.stop_propagation(); // Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here. - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "mousemove"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &canvas, + "mousemove", + |event: web_sys::MouseEvent, mut runner_lock| { let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event); runner_lock .input @@ -575,17 +622,14 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { runner_lock.needs_repaint.set_true(); event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "mouseup"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { + runner_container.add_event_listener( + &canvas, + "mouseup", + |event: web_sys::MouseEvent, mut runner_lock| { if let Some(button) = button_from_mouse_event(&event) { - let mut runner_lock = runner_ref.0.lock(); let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event); let modifiers = runner_lock.input.raw.modifiers; runner_lock @@ -600,34 +644,28 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { }); runner_lock.needs_repaint.set_true(); - text_agent::update_text_agent(&runner_lock); + text_agent::update_text_agent(runner_lock); } event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "mouseleave"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &canvas, + "mouseleave", + |event: web_sys::MouseEvent, mut runner_lock| { runner_lock.input.raw.events.push(egui::Event::PointerGone); runner_lock.needs_repaint.set_true(); event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "touchstart"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &canvas, + "touchstart", + |event: web_sys::TouchEvent, mut runner_lock| { let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id; let pos = pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id); @@ -649,16 +687,13 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { runner_lock.needs_repaint.set_true(); event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "touchmove"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &canvas, + "touchmove", + |event: web_sys::TouchEvent, mut runner_lock| { let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id; let pos = pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id); @@ -674,17 +709,13 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { runner_lock.needs_repaint.set_true(); event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } - - { - let event_name = "touchend"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - let mut runner_lock = runner_ref.0.lock(); + }, + )?; + runner_container.add_event_listener( + &canvas, + "touchend", + |event: web_sys::TouchEvent, mut runner_lock| { if let Some(pos) = runner_lock.input.latest_touch_pos { let modifiers = runner_lock.input.raw.modifiers; // First release mouse to click: @@ -708,34 +739,27 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { } // Finally, focus or blur text agent to toggle mobile keyboard: - text_agent::update_text_agent(&runner_lock); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + text_agent::update_text_agent(runner_lock); + }, + )?; - { - let event_name = "touchcancel"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - let mut runner_lock = runner_ref.0.lock(); - push_touches(&mut *runner_lock, egui::TouchPhase::Cancel, &event); + runner_container.add_event_listener( + &canvas, + "touchcancel", + |event: web_sys::TouchEvent, mut runner_lock| { + push_touches(&mut runner_lock, egui::TouchPhase::Cancel, &event); event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } - - { - let event_name = "wheel"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::WheelEvent| { - let mut runner_lock = runner_ref.0.lock(); + }, + )?; + runner_container.add_event_listener( + &canvas, + "wheel", + |event: web_sys::WheelEvent, mut runner_lock| { let scroll_multiplier = match event.delta_mode() { web_sys::WheelEvent::DOM_DELTA_PAGE => { - canvas_size_in_points(runner_ref.0.lock().canvas_id()).y + canvas_size_in_points(runner_lock.canvas_id()).y } web_sys::WheelEvent::DOM_DELTA_LINE => { #[allow(clippy::let_and_return)] @@ -771,17 +795,14 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { runner_lock.needs_repaint.set_true(); event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "dragover"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::DragEvent| { + runner_container.add_event_listener( + &canvas, + "dragover", + |event: web_sys::DragEvent, mut runner_lock| { if let Some(data_transfer) = event.data_transfer() { - let mut runner_lock = runner_ref.0.lock(); runner_lock.input.raw.hovered_files.clear(); for i in 0..data_transfer.items().length() { if let Some(item) = data_transfer.items().get(i) { @@ -795,35 +816,29 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { event.stop_propagation(); event.prevent_default(); } - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "dragleave"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::DragEvent| { - let mut runner_lock = runner_ref.0.lock(); + runner_container.add_event_listener( + &canvas, + "dragleave", + |event: web_sys::DragEvent, mut runner_lock| { runner_lock.input.raw.hovered_files.clear(); runner_lock.needs_repaint.set_true(); event.stop_propagation(); event.prevent_default(); - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + }, + )?; - { - let event_name = "drop"; - let runner_ref = runner_ref.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::DragEvent| { + runner_container.add_event_listener(&canvas, "drop", { + let runner_ref = runner_container.runner.clone(); + + move |event: web_sys::DragEvent, mut runner_lock| { if let Some(data_transfer) = event.data_transfer() { - { - let mut runner_lock = runner_ref.0.lock(); - runner_lock.input.raw.hovered_files.clear(); - runner_lock.needs_repaint.set_true(); - } + runner_lock.input.raw.hovered_files.clear(); + runner_lock.needs_repaint.set_true(); + // Unlock the runner so it can be locked after a future await point + drop(runner_lock); if let Some(files) = data_transfer.files() { for i in 0..files.length() { @@ -847,7 +862,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { bytes.len() ); - let mut runner_lock = runner_ref.0.lock(); + // Re-lock the mutex on the other side of the await point + let mut runner_lock = runner_ref.lock(); runner_lock.input.raw.dropped_files.push( egui::DroppedFile { name, @@ -870,10 +886,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { event.stop_propagation(); event.prevent_default(); } - }) as Box); - canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; - closure.forget(); - } + } + })?; Ok(()) } diff --git a/egui_web/src/text_agent.rs b/egui_web/src/text_agent.rs index 2fbaa1b0..ef2af8d8 100644 --- a/egui_web/src/text_agent.rs +++ b/egui_web/src/text_agent.rs @@ -1,7 +1,8 @@ //! The text agent is an `` element used to trigger //! mobile keyboard and IME input. -use crate::{canvas_element, AppRunner, AppRunnerRef}; +use crate::{canvas_element, AppRunner, AppRunnerContainer}; +use egui::mutex::MutexGuard; use std::cell::Cell; use std::rc::Rc; use wasm_bindgen::prelude::*; @@ -21,7 +22,7 @@ pub fn text_agent() -> web_sys::HtmlInputElement { } /// Text event handler, -pub fn install_text_agent(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { +pub fn install_text_agent(runner_container: &AppRunnerContainer) -> Result<(), JsValue> { use wasm_bindgen::JsCast; let window = web_sys::window().unwrap(); let document = window.document().unwrap(); @@ -43,61 +44,73 @@ pub fn install_text_agent(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { input.set_size(1); input.set_autofocus(true); input.set_hidden(true); - { - // When IME is off + + // When IME is off + runner_container.add_event_listener(&input, "input", { let input_clone = input.clone(); - let runner_ref = runner_ref.clone(); let is_composing = is_composing.clone(); - let on_input = Closure::wrap(Box::new(move |_event: web_sys::InputEvent| { + + move |_event: web_sys::InputEvent, mut runner_lock| { let text = input_clone.value(); if !text.is_empty() && !is_composing.get() { input_clone.set_value(""); - let mut runner_lock = runner_ref.0.lock(); runner_lock.input.raw.events.push(egui::Event::Text(text)); runner_lock.needs_repaint.set_true(); } - }) as Box); - input.add_event_listener_with_callback("input", on_input.as_ref().unchecked_ref())?; - on_input.forget(); - } + } + })?; + { // When IME is on, handle composition event - let input_clone = input.clone(); - let runner_ref = runner_ref.clone(); - let on_compositionend = Closure::wrap(Box::new(move |event: web_sys::CompositionEvent| { - let mut runner_lock = runner_ref.0.lock(); - let opt_event = match event.type_().as_ref() { - "compositionstart" => { - is_composing.set(true); - input_clone.set_value(""); - Some(egui::Event::CompositionStart) - } - "compositionend" => { - is_composing.set(false); - input_clone.set_value(""); - event.data().map(egui::Event::CompositionEnd) - } - "compositionupdate" => event.data().map(egui::Event::CompositionUpdate), - s => { - tracing::error!("Unknown composition event type: {:?}", s); - None - } - }; - if let Some(event) = opt_event { - runner_lock.input.raw.events.push(event); + runner_container.add_event_listener(&input, "compositionstart", { + let input_clone = input.clone(); + let is_composing = is_composing.clone(); + + move |_event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| { + is_composing.set(true); + input_clone.set_value(""); + + runner_lock + .input + .raw + .events + .push(egui::Event::CompositionStart); runner_lock.needs_repaint.set_true(); } - }) as Box); - let f = on_compositionend.as_ref().unchecked_ref(); - input.add_event_listener_with_callback("compositionstart", f)?; - input.add_event_listener_with_callback("compositionupdate", f)?; - input.add_event_listener_with_callback("compositionend", f)?; - on_compositionend.forget(); + })?; + + runner_container.add_event_listener( + &input, + "compositionupdate", + move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| { + if let Some(event) = event.data().map(egui::Event::CompositionUpdate) { + runner_lock.input.raw.events.push(event); + runner_lock.needs_repaint.set_true(); + } + }, + )?; + + runner_container.add_event_listener(&input, "compositionend", { + let input_clone = input.clone(); + + move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| { + is_composing.set(false); + input_clone.set_value(""); + + if let Some(event) = event.data().map(egui::Event::CompositionEnd) { + runner_lock.input.raw.events.push(event); + runner_lock.needs_repaint.set_true(); + } + } + })?; } - { - // When input lost focus, focus on it again. - // It is useful when user click somewhere outside canvas. - let on_focusout = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| { + + // When input lost focus, focus on it again. + // It is useful when user click somewhere outside canvas. + runner_container.add_event_listener( + &input, + "focusout", + move |_event: web_sys::MouseEvent, _| { // Delay 10 ms, and focus again. let func = js_sys::Function::new_no_args(&format!( "document.getElementById('{}').focus()", @@ -106,16 +119,16 @@ pub fn install_text_agent(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { window .set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10) .unwrap(); - }) as Box); - input.add_event_listener_with_callback("focusout", on_focusout.as_ref().unchecked_ref())?; - on_focusout.forget(); - } + }, + )?; + body.append_child(&input)?; + Ok(()) } /// Focus or blur text agent to toggle mobile keyboard. -pub fn update_text_agent(runner: &AppRunner) -> Option<()> { +pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> { use wasm_bindgen::JsCast; use web_sys::HtmlInputElement; let window = web_sys::window()?; @@ -156,7 +169,20 @@ pub fn update_text_agent(runner: &AppRunner) -> Option<()> { } } } else { + // Drop runner lock + drop(runner); + + // Holding the runner lock while calling input.blur() causes a panic. + // This is most probably caused by the browser running the event handler + // for the triggered blur event synchronously, meaning that the mutex + // lock does not get dropped by the time another event handler is called. + // + // Why this didn't exist before #1290 is a mystery to me, but it exists now + // and this apparently is the fix for it + // + // ¯\_(ツ)_/¯ - @DusterTheFirst input.blur().ok()?; + input.set_hidden(true); canvas_style.set_property("position", "absolute").ok()?; canvas_style.set_property("top", "0%").ok()?; // move back to normal position From cd555e07b8aa2d2bb92bb24d11795fda62a4be4c Mon Sep 17 00:00:00 2001 From: mbillingr Date: Thu, 10 Mar 2022 08:14:06 +0100 Subject: [PATCH 05/12] Fix typo: Highligher -> Highlighter (#1346) --- egui_demo_lib/src/syntax_highlighting.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/egui_demo_lib/src/syntax_highlighting.rs b/egui_demo_lib/src/syntax_highlighting.rs index b7701e77..765b70b9 100644 --- a/egui_demo_lib/src/syntax_highlighting.rs +++ b/egui_demo_lib/src/syntax_highlighting.rs @@ -1,6 +1,6 @@ use egui::text::LayoutJob; -/// View some code with syntax highlighing and selection. +/// View some code with syntax highlighting and selection. pub fn code_view_ui(ui: &mut egui::Ui, mut code: &str) { let language = "rs"; let theme = CodeTheme::from_memory(ui.ctx()); @@ -23,13 +23,13 @@ pub fn code_view_ui(ui: &mut egui::Ui, mut code: &str) { /// Memoized Code highlighting pub fn highlight(ctx: &egui::Context, theme: &CodeTheme, code: &str, language: &str) -> LayoutJob { - impl egui::util::cache::ComputerMut<(&CodeTheme, &str, &str), LayoutJob> for Highligher { + impl egui::util::cache::ComputerMut<(&CodeTheme, &str, &str), LayoutJob> for Highlighter { fn compute(&mut self, (theme, code, lang): (&CodeTheme, &str, &str)) -> LayoutJob { self.highlight(theme, code, lang) } } - type HighlightCache<'a> = egui::util::cache::FrameCache; + type HighlightCache<'a> = egui::util::cache::FrameCache; let mut memory = ctx.memory(); let highlight_cache = memory.caches.cache::>(); @@ -295,13 +295,13 @@ impl CodeTheme { // ---------------------------------------------------------------------------- #[cfg(feature = "syntect")] -struct Highligher { +struct Highlighter { ps: syntect::parsing::SyntaxSet, ts: syntect::highlighting::ThemeSet, } #[cfg(feature = "syntect")] -impl Default for Highligher { +impl Default for Highlighter { fn default() -> Self { Self { ps: syntect::parsing::SyntaxSet::load_defaults_newlines(), @@ -311,7 +311,7 @@ impl Default for Highligher { } #[cfg(feature = "syntect")] -impl Highligher { +impl Highlighter { #[allow(clippy::unused_self, clippy::unnecessary_wraps)] fn highlight(&self, theme: &CodeTheme, code: &str, lang: &str) -> LayoutJob { self.highlight_impl(theme, code, lang).unwrap_or_else(|| { @@ -392,10 +392,10 @@ fn as_byte_range(whole: &str, range: &str) -> std::ops::Range { #[cfg(not(feature = "syntect"))] #[derive(Default)] -struct Highligher {} +struct Highlighter {} #[cfg(not(feature = "syntect"))] -impl Highligher { +impl Highlighter { #[allow(clippy::unused_self, clippy::unnecessary_wraps)] fn highlight(&self, theme: &CodeTheme, mut text: &str, _language: &str) -> LayoutJob { // Extremely simple syntax highlighter for when we compile without syntect From c19a7ff34f162ee7162de0b11277de42415991ff Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Thu, 10 Mar 2022 02:23:00 -0500 Subject: [PATCH 06/12] remove unnecessary to_string in docs (#1345) --- egui/src/widgets/plot/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 1668d190..c0254c12 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -328,7 +328,7 @@ impl Plot { /// Plot::new("my_plot").view_aspect(2.0) /// .label_formatter(|name, value| { /// if !name.is_empty() { - /// format!("{}: {:.*}%", name, 1, value.y).to_string() + /// format!("{}: {:.*}%", name, 1, value.y) /// } else { /// "".to_string() /// } From 510cef02ca9c86cf26525a6acf4f8e4fa14a20f0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Mar 2022 14:25:33 +0100 Subject: [PATCH 07/12] Run a formatter on all toml files --- eframe/Cargo.toml | 29 +++++++++++++++++++---------- egui-winit/Cargo.toml | 19 ++++++++++--------- egui/Cargo.toml | 9 ++------- egui_demo_app/Cargo.toml | 6 ++++-- egui_demo_lib/Cargo.toml | 28 +++++++++++++++------------- egui_extras/Cargo.toml | 11 ++++------- egui_glium/Cargo.toml | 6 ++++-- egui_glow/Cargo.toml | 9 ++++++--- egui_web/Cargo.toml | 2 +- emath/Cargo.toml | 11 +++-------- epaint/Cargo.toml | 10 +++++----- epi/Cargo.toml | 11 ++++------- 12 files changed, 77 insertions(+), 74 deletions(-) diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index d091b12e..32da7b50 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -11,12 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/master/eframe" categories = ["gui", "game-development"] keywords = ["egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [package.metadata.docs.rs] all-features = true @@ -55,18 +50,32 @@ epi = { version = "0.17.0", path = "../epi" } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false } -egui_glium = { version = "0.17.0", path = "../egui_glium", default-features = false, features = ["clipboard", "epi", "links"], optional = true } -egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false, features = ["clipboard", "epi", "links", "winit"], optional = true } +egui_glium = { version = "0.17.0", path = "../egui_glium", optional = true, default-features = false, features = [ + "clipboard", + "epi", + "links", +] } +egui_glow = { version = "0.17.0", path = "../egui_glow", optional = true, default-features = false, features = [ + "clipboard", + "epi", + "links", + "winit", +] } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -egui_web = { version = "0.17.0", path = "../egui_web", default-features = false, features = ["glow"] } +egui_web = { version = "0.17.0", path = "../egui_web", default-features = false, features = [ + "glow", +] } [dev-dependencies] # For examples: egui_extras = { path = "../egui_extras", features = ["image", "svg"] } ehttp = "0.2" -image = { version = "0.24", default-features = false, features = ["jpeg", "png"] } +image = { version = "0.24", default-features = false, features = [ + "jpeg", + "png", +] } poll-promise = "0.1" rfd = "0.8" diff --git a/egui-winit/Cargo.toml b/egui-winit/Cargo.toml index 5410871b..1bf48fda 100644 --- a/egui-winit/Cargo.toml +++ b/egui-winit/Cargo.toml @@ -11,12 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/master/egui-winit" categories = ["gui", "game-development"] keywords = ["winit", "egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [package.metadata.docs.rs] all-features = true @@ -35,7 +30,10 @@ links = ["webbrowser"] # experimental support for a screen reader screen_reader = ["tts"] -persistence = ["egui/serialize", "serde"] # can't add epi/persistence here because of https://github.com/rust-lang/cargo/issues/8832 +persistence = [ + "egui/serialize", + "serde", +] # can't add epi/persistence here because of https://github.com/rust-lang/cargo/issues/8832 serialize = ["egui/serialize", "serde"] # implement bytemuck on most types. @@ -43,7 +41,10 @@ convert_bytemuck = ["egui/convert_bytemuck"] [dependencies] -egui = { version = "0.17.0", path = "../egui", default-features = false, features = ["single_threaded", "tracing"] } +egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ + "single_threaded", + "tracing", +] } instant = { version = "0.1", features = ["wasm-bindgen"] } tracing = "0.1" winit = "0.26.1" @@ -51,7 +52,7 @@ winit = "0.26.1" epi = { version = "0.17.0", path = "../epi", optional = true } copypasta = { version = "0.7", optional = true } -dark-light = { version = "0.2.1", optional = true } # detect dark mode system preference +dark-light = { version = "0.2.1", optional = true } # detect dark mode system preference serde = { version = "1.0", optional = true, features = ["derive"] } webbrowser = { version = "0.6", optional = true } diff --git a/egui/Cargo.toml b/egui/Cargo.toml index 55e34912..b99087a0 100644 --- a/egui/Cargo.toml +++ b/egui/Cargo.toml @@ -11,12 +11,7 @@ readme = "../README.md" repository = "https://github.com/emilk/egui" categories = ["gui", "game-development"] keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [package.metadata.docs.rs] all-features = true @@ -65,6 +60,6 @@ nohash-hasher = "0.2" # Optional: ron = { version = "0.7", optional = true } -serde = { version = "1", features = ["derive", "rc"], optional = true } +serde = { version = "1", optional = true, features = ["derive", "rc"] } # egui doesn't log much, but when it does, it uses `tracing` tracing = { version = "0.1", optional = true } diff --git a/egui_demo_app/Cargo.toml b/egui_demo_app/Cargo.toml index cf273ac3..6bcda140 100644 --- a/egui_demo_app/Cargo.toml +++ b/egui_demo_app/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["cdylib", "rlib"] default = ["persistence"] http = ["egui_demo_lib/http"] persistence = ["eframe/persistence", "egui_demo_lib/persistence"] -screen_reader = ["eframe/screen_reader"] # experimental +screen_reader = ["eframe/screen_reader"] # experimental syntax_highlighting = ["egui_demo_lib/syntax_highlighting"] @@ -28,7 +28,9 @@ eframe = { version = "0.17.0", path = "../eframe" } # To use the old glium backend instead: # eframe = { version = "0.17.0", path = "../eframe", default-features = false, features = ["default_fonts", "egui_glium"] } -egui_demo_lib = { version = "0.17.0", path = "../egui_demo_lib", features = ["extra_debug_asserts"] } +egui_demo_lib = { version = "0.17.0", path = "../egui_demo_lib", features = [ + "extra_debug_asserts", +] } tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/egui_demo_lib/Cargo.toml b/egui_demo_lib/Cargo.toml index c756571c..11ff6764 100644 --- a/egui_demo_lib/Cargo.toml +++ b/egui_demo_lib/Cargo.toml @@ -11,12 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/master/egui_demo_lib" categories = ["gui", "graphics"] keywords = ["glium", "egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [package.metadata.docs.rs] all-features = true @@ -34,7 +29,7 @@ extra_asserts = ["egui/extra_asserts"] http = ["egui_extras", "ehttp", "image", "poll-promise"] persistence = ["egui/persistence", "epi/persistence", "serde"] -serialize = ["egui/serialize", "serde"] +serialize = ["egui/serialize", "serde"] syntax_highlighting = ["syntect"] @@ -42,21 +37,28 @@ syntax_highlighting = ["syntect"] egui = { version = "0.17.0", path = "../egui", default-features = false } epi = { version = "0.17.0", path = "../epi" } -chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true } +chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } enum-map = { version = "2", features = ["serde"] } unicode_names2 = { version = "0.5.0", default-features = false } # feature "http": -egui_extras = { version = "0.17.0", path = "../egui_extras", features = ["image"], optional = true } +egui_extras = { version = "0.17.0", path = "../egui_extras", optional = true, features = [ + "image", +] } ehttp = { version = "0.2.0", optional = true } -image = { version = "0.24", default-features = false, features = ["jpeg", "png"], optional = true } -poll-promise = { version = "0.1", default-features = false, optional = true } +image = { version = "0.24", optional = true, default-features = false, features = [ + "jpeg", + "png", +] } +poll-promise = { version = "0.1", optional = true, default-features = false } # feature "syntax_highlighting": -syntect = { version = "4", default-features = false, features = ["default-fancy"], optional = true } +syntect = { version = "4", optional = true, default-features = false, features = [ + "default-fancy", +] } # feature "persistence": -serde = { version = "1", features = ["derive"], optional = true } +serde = { version = "1", optional = true, features = ["derive"] } [dev-dependencies] criterion = { version = "0.3", default-features = false } diff --git a/egui_extras/Cargo.toml b/egui_extras/Cargo.toml index d418ad56..63c31f96 100644 --- a/egui_extras/Cargo.toml +++ b/egui_extras/Cargo.toml @@ -11,12 +11,7 @@ readme = "../README.md" repository = "https://github.com/emilk/egui" categories = ["gui", "game-development"] keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [package.metadata.docs.rs] all-features = true @@ -32,7 +27,9 @@ svg = ["resvg", "tiny-skia", "usvg"] [dependencies] -egui = { version = "0.17.0", path = "../egui", default-features = false, features = ["single_threaded"] } +egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ + "single_threaded", +] } parking_lot = "0.12" # Optional dependencies: diff --git a/egui_glium/Cargo.toml b/egui_glium/Cargo.toml index 56d03b3c..8dbcfbf7 100644 --- a/egui_glium/Cargo.toml +++ b/egui_glium/Cargo.toml @@ -41,7 +41,7 @@ links = ["egui-winit/links"] persistence = [ "egui-winit/persistence", "egui/persistence", - "epi", # also implied by the lines below, see https://github.com/rust-lang/cargo/issues/8832 + "epi", # also implied by the lines below, see https://github.com/rust-lang/cargo/issues/8832 "epi/file_storage", "epi/persistence", ] @@ -55,7 +55,9 @@ egui = { version = "0.17.0", path = "../egui", default-features = false, feature "convert_bytemuck", "single_threaded", ] } -egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false, features = ["epi"] } +egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false, features = [ + "epi", +] } epi = { version = "0.17.0", path = "../epi", optional = true } ahash = "0.7" diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index 88ecf066..aa6ed648 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -41,7 +41,7 @@ links = ["egui-winit/links"] persistence = [ "egui-winit/persistence", "egui/persistence", - "epi", # also implied by the lines below, see https://github.com/rust-lang/cargo/issues/8832 + "epi", # also implied by the lines below, see https://github.com/rust-lang/cargo/issues/8832 "epi/file_storage", "epi/persistence", ] @@ -67,9 +67,12 @@ memoffset = "0.6" tracing = "0.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false, features = ["dark-light", "epi"], optional = true } +egui-winit = { version = "0.17.0", path = "../egui-winit", optional = true, default-features = false, features = [ + "dark-light", + "epi", +] } glutin = { version = "0.28.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -web-sys = { version = "0.3", features=["console"] } +web-sys = { version = "0.3", features = ["console"] } wasm-bindgen = { version = "0.2" } diff --git a/egui_web/Cargo.toml b/egui_web/Cargo.toml index 2cb6e1ce..6ce254cb 100644 --- a/egui_web/Cargo.toml +++ b/egui_web/Cargo.toml @@ -52,7 +52,7 @@ egui = { version = "0.17.0", path = "../egui", default-features = false, feature "single_threaded", "tracing", ] } -egui_glow = { version = "0.17.0",path = "../egui_glow", default-features = false, optional = true } +egui_glow = { version = "0.17.0", path = "../egui_glow", optional = true, default-features = false } epi = { version = "0.17.0", path = "../epi" } bytemuck = "1.7" diff --git a/emath/Cargo.toml b/emath/Cargo.toml index 8e2c1c35..4fd3afc7 100644 --- a/emath/Cargo.toml +++ b/emath/Cargo.toml @@ -11,12 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/master/emath" categories = ["mathematics", "gui"] keywords = ["math", "gui"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [package.metadata.docs.rs] all-features = true @@ -34,6 +29,6 @@ extra_asserts = [] [dependencies] -bytemuck = { version = "1.7.2", features = ["derive"], optional = true } +bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } mint = { version = "0.5.6", optional = true } -serde = { version = "1", features = ["derive"], optional = true } +serde = { version = "1", optional = true, features = ["derive"] } diff --git a/epaint/Cargo.toml b/epaint/Cargo.toml index c4348d8b..4c6ef6a2 100644 --- a/epaint/Cargo.toml +++ b/epaint/Cargo.toml @@ -58,13 +58,13 @@ multi_threaded = ["parking_lot"] emath = { version = "0.17.0", path = "../emath" } ab_glyph = "0.2.11" -ahash = { version = "0.7", features = ["std"], default-features = false } -atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use. -bytemuck = { version = "1.7.2", features = ["derive"], optional = true } +ahash = { version = "0.7", default-features = false, features = ["std"] } +atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use. +bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } cint = { version = "^0.2.2", optional = true } nohash-hasher = "0.2" -parking_lot = { version = "0.12", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. -serde = { version = "1", features = ["derive", "rc"], optional = true } +parking_lot = { version = "0.12", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. +serde = { version = "1", optional = true, features = ["derive", "rc"] } [dev-dependencies] criterion = { version = "0.3", default-features = false } diff --git a/epi/Cargo.toml b/epi/Cargo.toml index 3efbd38a..398e426c 100644 --- a/epi/Cargo.toml +++ b/epi/Cargo.toml @@ -11,12 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/master/epi" categories = ["gui", "game-development"] keywords = ["egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", -] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [package.metadata.docs.rs] all-features = true @@ -33,7 +28,9 @@ persistence = ["ron", "serde", "egui/persistence"] [dependencies] -egui = { version = "0.17.0", path = "../egui", default-features = false, features = ["single_threaded"] } +egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ + "single_threaded", +] } tracing = "0.1" directories-next = { version = "2", optional = true } From 52b4ab4e183ce9fb96d0142a9829aae6f78ce410 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Mar 2022 19:14:27 +0100 Subject: [PATCH 08/12] Remove egui_glium as a backend for eframe (#1357) eframe will now always use egui_glow as a native backend. Part of https://github.com/emilk/egui/issues/1198 --- .github/workflows/rust.yml | 2 +- Cargo.lock | 1 - README.md | 6 +++--- eframe/CHANGELOG.md | 3 ++- eframe/Cargo.toml | 11 +++-------- eframe/README.md | 10 ++-------- eframe/src/lib.rs | 40 -------------------------------------- sh/check.sh | 2 +- 8 files changed, 12 insertions(+), 63 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5865f0bd..d5c1b555 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -81,7 +81,7 @@ jobs: override: true - run: rustup target add wasm32-unknown-unknown - name: check - run: cargo check -p eframe --lib --no-default-features --features egui_glow,persistence --target wasm32-unknown-unknown + run: cargo check -p eframe --lib --no-default-features --features persistence --target wasm32-unknown-unknown check_web_all_features: name: cargo check web --all-features diff --git a/Cargo.lock b/Cargo.lock index 9338554a..939fadfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -987,7 +987,6 @@ dependencies = [ "egui", "egui-winit", "egui_extras", - "egui_glium", "egui_glow", "egui_web", "ehttp", diff --git a/README.md b/README.md index 4110d04a..83b4486e 100644 --- a/README.md +++ b/README.md @@ -160,13 +160,13 @@ An integration needs to do the following each frame: ### Official integrations -If you're making an app, your best bet is using [`eframe`](https://github.com/emilk/egui/tree/master/eframe), the official egui framework. It lets you write apps that work on both the web and native. `eframe` is just a thin wrapper over `egui_web` and `egui_glium` (see below). +If you're making an app, your best bet is using [`eframe`](https://github.com/emilk/egui/tree/master/eframe), the official egui framework. It lets you write apps that work on both the web and native. `eframe` is just a thin wrapper over `egui_web` and `egui_glow` (see below). These are the official egui integrations: * [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium). -* [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for compiling native apps with [Glow](https://github.com/grovesNL/glow). -* [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://www.egui.rs/#demo). +* [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for compiling native apps with [glow](https://github.com/grovesNL/glow). +* [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://www.egui.rs/#demo). Uses `egui_glow`. * [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit). `egui-winit` is used by `egui_glium` and `egui_glow`. ### 3rd party integrations diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index b123ac6a..90cb6126 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -5,7 +5,8 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG ## Unreleased -* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)) +* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). +* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)). ## 0.17.0 - 2022-02-22 diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index 32da7b50..8f5c65af 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -20,7 +20,7 @@ all-features = true [features] -default = ["default_fonts", "egui_glow"] +default = ["default_fonts"] # If set, egui will use `include_bytes!` to bundle some fonts. # If you plan on specifying your own fonts you may disable this feature. @@ -49,18 +49,13 @@ epi = { version = "0.17.0", path = "../epi" } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false } -egui_glium = { version = "0.17.0", path = "../egui_glium", optional = true, default-features = false, features = [ - "clipboard", - "epi", - "links", -] } -egui_glow = { version = "0.17.0", path = "../egui_glow", optional = true, default-features = false, features = [ +egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false, features = [ "clipboard", "epi", "links", "winit", ] } +egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/eframe/README.md b/eframe/README.md index 9bdf221c..68581dc9 100644 --- a/eframe/README.md +++ b/eframe/README.md @@ -18,7 +18,7 @@ For how to use `egui`, see [the egui docs](https://docs.rs/egui). `eframe` is a very thin crate that re-exports [`egui`](https://github.com/emilk/egui) and[`epi`](https://github.com/emilk/egui/tree/master/epi) with thin wrappers over the backends. -`eframe` uses [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for web and [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) or [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for native. +`eframe` uses [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for web and [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for native. To use on Linux, first run: @@ -28,13 +28,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib ## Alternatives -The default native backend for `eframe` is currently [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow), but you can switch to the previous [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) backend by putting this in your `Cargo.toml`: - -``` toml -eframe = { version = "*", default-features = false, features = ["default_fonts", "egui_glium"] } -``` - -`eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad) and [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl). +`eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others. ## Companion crates diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 31e9643e..fcb55cec 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -143,46 +143,6 @@ pub fn start_web(canvas_id: &str, app: Box) -> Result<(), wasm_bin /// } /// ``` #[cfg(not(target_arch = "wasm32"))] -#[cfg(feature = "egui_glium")] -pub fn run_native(app: Box, native_options: epi::NativeOptions) -> ! { - egui_glium::run(app, &native_options) -} - -/// Call from `fn main` like this: -/// ``` no_run -/// use eframe::{epi, egui}; -/// -/// #[derive(Default)] -/// struct MyEguiApp {} -/// -/// impl epi::App for MyEguiApp { -/// fn name(&self) -> &str { -/// "My egui App" -/// } -/// -/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { -/// egui::CentralPanel::default().show(ctx, |ui| { -/// ui.heading("Hello World!"); -/// }); -/// } -///} -/// -/// fn main() { -/// let app = MyEguiApp::default(); -/// let native_options = eframe::NativeOptions::default(); -/// eframe::run_native(Box::new(app), native_options); -/// } -/// ``` -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(feature = "egui_glium"))] // make sure we still compile with `--all-features` -#[cfg(feature = "egui_glow")] pub fn run_native(app: Box, native_options: epi::NativeOptions) -> ! { egui_glow::run(app, &native_options) } - -// disabled since we want to be able to compile with `--all-features` -// #[cfg(all(feature = "egui_glium", feature = "egui_glow"))] -// compile_error!("Enable either egui_glium or egui_glow, not both"); - -#[cfg(not(any(feature = "egui_glium", feature = "egui_glow")))] -compile_error!("Enable either egui_glium or egui_glow"); diff --git a/sh/check.sh b/sh/check.sh index 86de92f9..2f5038f0 100755 --- a/sh/check.sh +++ b/sh/check.sh @@ -25,7 +25,7 @@ cargo doc --document-private-items --no-deps --all-features (cd epaint && cargo check --no-default-features --features "single_threaded" --release) (cd epaint && cargo check --no-default-features --features "multi_threaded" --release) (cd egui && cargo check --no-default-features --features "multi_threaded,serialize") -(cd eframe && cargo check --no-default-features --features "egui_glow") +(cd eframe && cargo check --no-default-features) (cd epi && cargo check --no-default-features) (cd egui_demo_lib && cargo check --no-default-features) (cd egui_extras && cargo check --no-default-features) From 50539bd31a57f4a0eb143e41623cc8f0ad4c7569 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Mar 2022 19:15:06 +0100 Subject: [PATCH 09/12] egui_web: always use the glow painter, and remove the old WebGL code. (#1356) * egui_web: always use the glow painter, and remove the old WebGL code. * Clean up the WebPainter trait * Clarify WebGL1 warning text in color test The glow painter became standard in egui 0.17, and I've heard no complaints! So let's simplify and go all in on glow. Part of https://github.com/emilk/egui/issues/1198 --- eframe/Cargo.toml | 4 +- egui_demo_lib/src/apps/color_test.rs | 2 +- egui_demo_lib/src/backend_panel.rs | 3 - egui_web/CHANGELOG.md | 1 + egui_web/Cargo.toml | 17 +- egui_web/README.md | 2 +- egui_web/src/backend.rs | 23 +- egui_web/src/glow_wrapping.rs | 26 +- egui_web/src/lib.rs | 8 +- egui_web/src/painter.rs | 15 +- egui_web/src/shader/fragment_100es.glsl | 58 -- egui_web/src/shader/main_fragment_100es.glsl | 13 - egui_web/src/shader/main_vertex_100es.glsl | 31 - egui_web/src/shader/post_fragment_100es.glsl | 26 - egui_web/src/shader/post_vertex_100es.glsl | 8 - egui_web/src/webgl1.rs | 666 ------------------- egui_web/src/webgl2.rs | 637 ------------------ sh/check.sh | 2 +- 18 files changed, 28 insertions(+), 1514 deletions(-) delete mode 100644 egui_web/src/shader/fragment_100es.glsl delete mode 100644 egui_web/src/shader/main_fragment_100es.glsl delete mode 100644 egui_web/src/shader/main_vertex_100es.glsl delete mode 100644 egui_web/src/shader/post_fragment_100es.glsl delete mode 100644 egui_web/src/shader/post_vertex_100es.glsl delete mode 100644 egui_web/src/webgl1.rs delete mode 100644 egui_web/src/webgl2.rs diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index 8f5c65af..df6cc289 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -59,9 +59,7 @@ egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = fa # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -egui_web = { version = "0.17.0", path = "../egui_web", default-features = false, features = [ - "glow", -] } +egui_web = { version = "0.17.0", path = "../egui_web", default-features = false } [dev-dependencies] diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index f306e11a..ca25fd17 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -38,7 +38,7 @@ impl epi::App for ColorTest { egui::CentralPanel::default().show(ctx, |ui| { if frame.is_web() { ui.label( - "NOTE: The WebGL1 backend without sRGB support does NOT pass the color test.", + "NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.", ); ui.separator(); } diff --git a/egui_demo_lib/src/backend_panel.rs b/egui_demo_lib/src/backend_panel.rs index 5ead1cce..e54b97d1 100644 --- a/egui_demo_lib/src/backend_panel.rs +++ b/egui_demo_lib/src/backend_panel.rs @@ -274,9 +274,6 @@ fn show_integration_name(ui: &mut egui::Ui, integration_info: &epi::IntegrationI format!("https://github.com/emilk/egui/tree/master/{}", name), ); } - name if name.starts_with("egui_web") => { - ui.hyperlink_to(name, "https://github.com/emilk/egui/tree/master/egui_web"); - } name => { ui.label(name); } diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index 79754635..91bee2c7 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the `egui_web` integration will be noted in this file. ## Unreleased * egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)) +* Remove the "webgl" feature. `egui_web` now always use `glow` (which in turn wraps WebGL) ([#1356](https://github.com/emilk/egui/pull/1356)). ## 0.17.0 - 2022-02-22 diff --git a/egui_web/Cargo.toml b/egui_web/Cargo.toml index 6ce254cb..5994f9d1 100644 --- a/egui_web/Cargo.toml +++ b/egui_web/Cargo.toml @@ -27,18 +27,12 @@ crate-type = ["cdylib", "rlib"] [features] -default = ["default_fonts", "glow"] +default = ["default_fonts"] # If set, egui will use `include_bytes!` to bundle some fonts. # If you plan on specifying your own fonts you may disable this feature. default_fonts = ["egui/default_fonts"] -# Use glow as the renderer. -glow = ["egui_glow", "egui_glow/epi"] - -# Alternative to the glow renderer. -webgl = [] - # enable persisting egui memory persistence = ["egui/persistence", "ron", "serde"] @@ -52,7 +46,7 @@ egui = { version = "0.17.0", path = "../egui", default-features = false, feature "single_threaded", "tracing", ] } -egui_glow = { version = "0.17.0", path = "../egui_glow", optional = true, default-features = false } +egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false } epi = { version = "0.17.0", path = "../epi" } bytemuck = "1.7" @@ -106,15 +100,8 @@ features = [ "TouchEvent", "TouchList", "WebGl2RenderingContext", - "WebGlBuffer", "WebglDebugRendererInfo", - "WebGlFramebuffer", - "WebGlProgram", "WebGlRenderingContext", - "WebGlShader", - "WebGlTexture", - "WebGlUniformLocation", - "WebGlVertexArrayObject", "WheelEvent", "Window", ] diff --git a/egui_web/README.md b/egui_web/README.md index 045fd905..5dc5f940 100644 --- a/egui_web/README.md +++ b/egui_web/README.md @@ -14,7 +14,7 @@ Check out [eframe_template](https://github.com/emilk/eframe_template) for an exa ## Downsides with using egui on the web -`egui_web` uses WebGL and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challanges and serious downsides. +`egui_web` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challanges and serious downsides. * Rendering: Getting pixel-perfect rendering right on the web is very difficult. * Search: you cannot search an egui web page like you would a normal web page. diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index f3033cac..ec11a18d 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -5,25 +5,10 @@ pub use egui::{pos2, Color32}; // ---------------------------------------------------------------------------- -fn create_painter(canvas_id: &str) -> Result, JsValue> { - // Glow takes precedence: - #[cfg(all(feature = "glow"))] - return Ok(Box::new( +fn create_painter(canvas_id: &str) -> Result, JsValue> { + Ok(Box::new( crate::glow_wrapping::WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?, - )); - - #[cfg(all(feature = "webgl", not(feature = "glow")))] - if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { - tracing::debug!("Using WebGL2 backend"); - Ok(Box::new(webgl2_painter)) - } else { - tracing::debug!("Falling back to WebGL1 backend"); - let webgl1_painter = webgl1::WebGlPainter::new(canvas_id)?; - Ok(Box::new(webgl1_painter)) - } - - #[cfg(all(not(feature = "webgl"), not(feature = "glow")))] - compile_error!("Either the 'glow' or 'webgl' feature of egui_web must be enabled!"); + )) } // ---------------------------------------------------------------------------- @@ -155,7 +140,7 @@ fn test_parse_query() { pub struct AppRunner { pub(crate) frame: epi::Frame, egui_ctx: egui::Context, - painter: Box, + painter: Box, pub(crate) input: WebInput, app: Box, pub(crate) needs_repaint: std::sync::Arc, diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index 839b23e4..c21a38b7 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -32,11 +32,19 @@ impl WrappedGlowPainter { } } -impl crate::Painter for WrappedGlowPainter { +impl crate::WebPainter for WrappedGlowPainter { + fn name(&self) -> &'static str { + "egui_web" + } + fn max_texture_side(&self) -> usize { self.painter.max_texture_side() } + fn canvas_id(&self) -> &str { + &self.canvas_id + } + fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { self.painter.set_texture(&self.glow_ctx, tex_id, delta); } @@ -45,18 +53,6 @@ impl crate::Painter for WrappedGlowPainter { self.painter.free_texture(&self.glow_ctx, tex_id); } - fn debug_info(&self) -> String { - format!( - "Stored canvas size: {} x {}", - self.canvas.width(), - self.canvas.height(), - ) - } - - fn canvas_id(&self) -> &str { - &self.canvas_id - } - fn clear(&mut self, clear_color: Rgba) { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; egui_glow::painter::clear(&self.glow_ctx, canvas_dimension, clear_color) @@ -76,10 +72,6 @@ impl crate::Painter for WrappedGlowPainter { ); Ok(()) } - - fn name(&self) -> &'static str { - "egui_web (glow)" - } } /// Returns glow context and shader prefix. diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 1f271ead..4177ee8b 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -15,18 +15,12 @@ #![warn(clippy::all, rustdoc::missing_crate_level_docs, rust_2018_idioms)] pub mod backend; -#[cfg(feature = "glow")] mod glow_wrapping; mod input; mod painter; pub mod screen_reader; mod text_agent; -#[cfg(feature = "webgl")] -pub mod webgl1; -#[cfg(feature = "webgl")] -pub mod webgl2; - pub use backend::*; use egui::mutex::{Mutex, MutexGuard}; @@ -34,7 +28,7 @@ pub use wasm_bindgen; pub use web_sys; use input::*; -pub use painter::Painter; +pub use painter::WebPainter; use web_sys::EventTarget; use std::collections::BTreeMap; diff --git a/egui_web/src/painter.rs b/egui_web/src/painter.rs index f0c26e7c..11fcd4dd 100644 --- a/egui_web/src/painter.rs +++ b/egui_web/src/painter.rs @@ -1,18 +1,19 @@ use wasm_bindgen::prelude::JsValue; -pub trait Painter { +/// What is needed to paint egui. +pub trait WebPainter { + fn name(&self) -> &'static str; + /// Max size of one side of a texture. fn max_texture_side(&self) -> usize; + /// id of the canvas html element containing the rendering + fn canvas_id(&self) -> &str; + fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta); fn free_texture(&mut self, tex_id: egui::TextureId); - fn debug_info(&self) -> String; - - /// id of the canvas html element containing the rendering - fn canvas_id(&self) -> &str; - fn clear(&mut self, clear_color: egui::Rgba); fn paint_meshes( @@ -21,8 +22,6 @@ pub trait Painter { pixels_per_point: f32, ) -> Result<(), JsValue>; - fn name(&self) -> &'static str; - fn paint_and_update_textures( &mut self, clipped_meshes: Vec, diff --git a/egui_web/src/shader/fragment_100es.glsl b/egui_web/src/shader/fragment_100es.glsl deleted file mode 100644 index ef6a2870..00000000 --- a/egui_web/src/shader/fragment_100es.glsl +++ /dev/null @@ -1,58 +0,0 @@ -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba; -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGB from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-1 linear from 0-255 sRGBA -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - -void main() { - // We must decode the colors, since WebGL1 doesn't come with sRGBA textures: - vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0); - - /// Multiply vertex color with texture color (in linear space). - gl_FragColor = v_rgba * texture_rgba; - - // WebGL doesn't support linear blending in the framebuffer, - // so we do a hack here where we change the premultiplied alpha - // to do the multiplication in gamma space instead: - - // Unmultiply alpha: - if (gl_FragColor.a > 0.0) { - gl_FragColor.rgb /= gl_FragColor.a; - } - - // Empiric tweak to make e.g. shadows look more like they should: - gl_FragColor.a *= sqrt(gl_FragColor.a); - - // To gamma: - gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0; - - // Premultiply alpha, this time in gamma space: - if (gl_FragColor.a > 0.0) { - gl_FragColor.rgb *= gl_FragColor.a; - } -} diff --git a/egui_web/src/shader/main_fragment_100es.glsl b/egui_web/src/shader/main_fragment_100es.glsl deleted file mode 100644 index d1c45dcf..00000000 --- a/egui_web/src/shader/main_fragment_100es.glsl +++ /dev/null @@ -1,13 +0,0 @@ -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba; -varying vec2 v_tc; - -void main() { - // The texture is set up with `SRGB8_ALPHA8`, so no need to decode here! - vec4 texture_rgba = texture2D(u_sampler, v_tc); - - // Multiply vertex color with texture color (in linear space). - // Linear color is written and blended in Framebuffer and converted to sRGB later - gl_FragColor = v_rgba * texture_rgba; -} diff --git a/egui_web/src/shader/main_vertex_100es.glsl b/egui_web/src/shader/main_vertex_100es.glsl deleted file mode 100644 index 002c06da..00000000 --- a/egui_web/src/shader/main_vertex_100es.glsl +++ /dev/null @@ -1,31 +0,0 @@ -precision mediump float; -uniform vec2 u_screen_size; -attribute vec2 a_pos; -attribute vec2 a_tc; -attribute vec4 a_srgba; -varying vec4 v_rgba; -varying vec2 v_tc; - -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-1 linear from 0-255 sRGBA -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - // egui encodes vertex colors in gamma spaces, so we must decode the colors here: - v_rgba = linear_from_srgba(a_srgba); - v_tc = a_tc; -} diff --git a/egui_web/src/shader/post_fragment_100es.glsl b/egui_web/src/shader/post_fragment_100es.glsl deleted file mode 100644 index bbcaed9e..00000000 --- a/egui_web/src/shader/post_fragment_100es.glsl +++ /dev/null @@ -1,26 +0,0 @@ -precision mediump float; -uniform sampler2D u_sampler; -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -void main() { - gl_FragColor = texture2D(u_sampler, v_tc); - - gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0; - - #ifdef APPLY_BRIGHTENING_GAMMA - gl_FragColor = vec4(pow(gl_FragColor.rgb, vec3(1.0/2.2)), gl_FragColor.a); - #endif -} diff --git a/egui_web/src/shader/post_vertex_100es.glsl b/egui_web/src/shader/post_vertex_100es.glsl deleted file mode 100644 index 37280bc1..00000000 --- a/egui_web/src/shader/post_vertex_100es.glsl +++ /dev/null @@ -1,8 +0,0 @@ -precision mediump float; -attribute vec2 a_pos; -varying vec2 v_tc; - -void main() { - gl_Position = vec4(a_pos * 2. - 1., 0.0, 1.0); - v_tc = a_pos; -} diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs deleted file mode 100644 index bfa39762..00000000 --- a/egui_web/src/webgl1.rs +++ /dev/null @@ -1,666 +0,0 @@ -use std::collections::HashMap; - -use { - js_sys::WebAssembly, - wasm_bindgen::{prelude::*, JsCast}, - web_sys::{ - ExtSRgb, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, - WebGlTexture, - }, -}; - -use egui::{emath::vec2, epaint::Color32}; - -type Gl = WebGlRenderingContext; - -pub struct WebGlPainter { - canvas_id: String, - canvas: web_sys::HtmlCanvasElement, - gl: WebGlRenderingContext, - program: WebGlProgram, - index_buffer: WebGlBuffer, - pos_buffer: WebGlBuffer, - tc_buffer: WebGlBuffer, - color_buffer: WebGlBuffer, - texture_format: u32, - post_process: Option, - - textures: HashMap, - next_native_tex_id: u64, -} - -impl WebGlPainter { - pub fn new(canvas_id: &str) -> Result { - let canvas = crate::canvas_element_or_die(canvas_id); - - let gl = canvas - .get_context("webgl")? - .ok_or_else(|| JsValue::from("Failed to get WebGL context"))? - .dyn_into::()?; - - // -------------------------------------------------------------------- - - let srgb_supported = matches!(gl.get_extension("EXT_sRGB"), Ok(Some(_))); - - let vert_shader = compile_shader( - &gl, - Gl::VERTEX_SHADER, - include_str!("shader/main_vertex_100es.glsl"), - )?; - let (texture_format, program, post_process) = if srgb_supported { - let frag_shader = compile_shader( - &gl, - Gl::FRAGMENT_SHADER, - include_str!("shader/main_fragment_100es.glsl"), - )?; - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - - let post_process = - PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?; - - (ExtSRgb::SRGB_ALPHA_EXT, program, Some(post_process)) - } else { - let frag_shader = compile_shader( - &gl, - Gl::FRAGMENT_SHADER, - include_str!("shader/fragment_100es.glsl"), - )?; - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - - (Gl::RGBA, program, None) - }; - - let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; - let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; - let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?; - let color_buffer = gl.create_buffer().ok_or("failed to create color_buffer")?; - - Ok(WebGlPainter { - canvas_id: canvas_id.to_owned(), - canvas, - gl, - program, - index_buffer, - pos_buffer, - tc_buffer, - color_buffer, - texture_format, - post_process, - textures: Default::default(), - next_native_tex_id: 1 << 32, - }) - } - - fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { - self.textures.get(&texture_id) - } - - fn paint_mesh(&self, mesh: &egui::epaint::Mesh16) -> Result<(), JsValue> { - debug_assert!(mesh.is_valid()); - - let mut positions: Vec = Vec::with_capacity(2 * mesh.vertices.len()); - let mut tex_coords: Vec = Vec::with_capacity(2 * mesh.vertices.len()); - let mut colors: Vec = Vec::with_capacity(4 * mesh.vertices.len()); - for v in &mesh.vertices { - positions.push(v.pos.x); - positions.push(v.pos.y); - tex_coords.push(v.uv.x); - tex_coords.push(v.uv.y); - colors.push(v.color[0]); - colors.push(v.color[1]); - colors.push(v.color[2]); - colors.push(v.color[3]); - } - - // -------------------------------------------------------------------- - - let gl = &self.gl; - - let indices_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let indices_ptr = mesh.indices.as_ptr() as u32 / 2; - let indices_array = js_sys::Int16Array::new(&indices_memory_buffer) - .subarray(indices_ptr, indices_ptr + mesh.indices.len() as u32); - - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer)); - gl.buffer_data_with_array_buffer_view( - Gl::ELEMENT_ARRAY_BUFFER, - &indices_array, - Gl::STREAM_DRAW, - ); - - // -------------------------------------------------------------------- - - let pos_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let pos_ptr = positions.as_ptr() as u32 / 4; - let pos_array = js_sys::Float32Array::new(&pos_memory_buffer) - .subarray(pos_ptr, pos_ptr + positions.len() as u32); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer)); - gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &pos_array, Gl::STREAM_DRAW); - - let a_pos_loc = gl.get_attrib_location(&self.program, "a_pos"); - assert!(a_pos_loc >= 0); - let a_pos_loc = a_pos_loc as u32; - - let normalize = false; - let stride = 0; - let offset = 0; - gl.vertex_attrib_pointer_with_i32(a_pos_loc, 2, Gl::FLOAT, normalize, stride, offset); - gl.enable_vertex_attrib_array(a_pos_loc); - - // -------------------------------------------------------------------- - - let tc_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let tc_ptr = tex_coords.as_ptr() as u32 / 4; - let tc_array = js_sys::Float32Array::new(&tc_memory_buffer) - .subarray(tc_ptr, tc_ptr + tex_coords.len() as u32); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.tc_buffer)); - gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &tc_array, Gl::STREAM_DRAW); - - let a_tc_loc = gl.get_attrib_location(&self.program, "a_tc"); - assert!(a_tc_loc >= 0); - let a_tc_loc = a_tc_loc as u32; - - let normalize = false; - let stride = 0; - let offset = 0; - gl.vertex_attrib_pointer_with_i32(a_tc_loc, 2, Gl::FLOAT, normalize, stride, offset); - gl.enable_vertex_attrib_array(a_tc_loc); - - // -------------------------------------------------------------------- - - let colors_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let colors_ptr = colors.as_ptr() as u32; - let colors_array = js_sys::Uint8Array::new(&colors_memory_buffer) - .subarray(colors_ptr, colors_ptr + colors.len() as u32); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.color_buffer)); - gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &colors_array, Gl::STREAM_DRAW); - - let a_srgba_loc = gl.get_attrib_location(&self.program, "a_srgba"); - assert!(a_srgba_loc >= 0); - let a_srgba_loc = a_srgba_loc as u32; - - let normalize = false; - let stride = 0; - let offset = 0; - gl.vertex_attrib_pointer_with_i32( - a_srgba_loc, - 4, - Gl::UNSIGNED_BYTE, - normalize, - stride, - offset, - ); - gl.enable_vertex_attrib_array(a_srgba_loc); - - // -------------------------------------------------------------------- - - gl.draw_elements_with_i32( - Gl::TRIANGLES, - mesh.indices.len() as i32, - Gl::UNSIGNED_SHORT, - 0, - ); - - Ok(()) - } - - fn set_texture_rgba( - &mut self, - tex_id: egui::TextureId, - pos: Option<[usize; 2]>, - [w, h]: [usize; 2], - pixels: &[u8], - ) { - let gl = &self.gl; - - let gl_texture = self - .textures - .entry(tex_id) - .or_insert_with(|| 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 _); - - 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.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); - - if let Some([x, y]) = pos { - gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - level, - x as _, - y as _, - w as _, - h as _, - src_format, - src_type, - Some(pixels), - ) - .unwrap(); - } else { - 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 _, - w as _, - h as _, - border, - src_format, - src_type, - Some(pixels), - ) - .unwrap(); - } - } -} - -impl epi::NativeTexture for WebGlPainter { - type Texture = WebGlTexture; - - fn register_native_texture(&mut self, texture: Self::Texture) -> egui::TextureId { - let id = egui::TextureId::User(self.next_native_tex_id); - self.next_native_tex_id += 1; - self.textures.insert(id, texture); - id - } - - fn replace_native_texture(&mut self, id: egui::TextureId, texture: Self::Texture) { - self.textures.insert(id, texture); - } -} - -impl crate::Painter for WebGlPainter { - fn max_texture_side(&self) -> usize { - if let Ok(max_texture_side) = self - .gl - .get_parameter(web_sys::WebGlRenderingContext::MAX_TEXTURE_SIZE) - { - if let Some(max_texture_side) = max_texture_side.as_f64() { - return max_texture_side as usize; - } - } - - tracing::error!("Failed to query max texture size"); - - 2048 - } - - fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { - match &delta.image { - egui::ImageData::Color(image) => { - assert_eq!( - image.width() * image.height(), - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - - let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref()); - self.set_texture_rgba(tex_id, delta.pos, image.size, data); - } - egui::ImageData::Alpha(image) => { - let gamma = if self.post_process.is_none() { - 1.0 / 2.2 // HACK due to non-linear framebuffer blending. - } else { - 1.0 // post process enables linear blending - }; - let data: Vec = image - .srgba_pixels(gamma) - .flat_map(|a| a.to_array()) - .collect(); - self.set_texture_rgba(tex_id, delta.pos, image.size, &data); - } - }; - } - - fn free_texture(&mut self, tex_id: egui::TextureId) { - self.textures.remove(&tex_id); - } - - fn debug_info(&self) -> String { - format!( - "Stored canvas size: {} x {}\n\ - gl context size: {} x {}", - self.canvas.width(), - self.canvas.height(), - self.gl.drawing_buffer_width(), - self.gl.drawing_buffer_height(), - ) - } - - /// id of the canvas html element containing the rendering - fn canvas_id(&self) -> &str { - &self.canvas_id - } - - fn clear(&mut self, clear_color: egui::Rgba) { - let gl = &self.gl; - - gl.disable(Gl::SCISSOR_TEST); - - let width = self.canvas.width() as i32; - let height = self.canvas.height() as i32; - gl.viewport(0, 0, width, height); - - let clear_color: Color32 = clear_color.into(); - gl.clear_color( - clear_color[0] as f32 / 255.0, - clear_color[1] as f32 / 255.0, - clear_color[2] as f32 / 255.0, - clear_color[3] as f32 / 255.0, - ); - gl.clear(Gl::COLOR_BUFFER_BIT); - } - - fn paint_meshes( - &mut self, - clipped_meshes: Vec, - pixels_per_point: f32, - ) -> Result<(), JsValue> { - let gl = &self.gl; - - if let Some(ref mut post_process) = self.post_process { - post_process.begin(self.canvas.width() as i32, self.canvas.height() as i32)?; - } - - gl.enable(Gl::SCISSOR_TEST); - gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. - gl.enable(Gl::BLEND); - gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA); // premultiplied alpha - gl.use_program(Some(&self.program)); - gl.active_texture(Gl::TEXTURE0); - - let u_screen_size_loc = gl - .get_uniform_location(&self.program, "u_screen_size") - .unwrap(); - let screen_size_pixels = vec2(self.canvas.width() as f32, self.canvas.height() as f32); - let screen_size_points = screen_size_pixels / pixels_per_point; - gl.uniform2f( - Some(&u_screen_size_loc), - screen_size_points.x, - screen_size_points.y, - ); - - let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); - gl.uniform1i(Some(&u_sampler_loc), 0); - - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - if let Some(gl_texture) = self.get_texture(mesh.texture_id) { - gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); - - let clip_min_x = pixels_per_point * clip_rect.min.x; - let clip_min_y = pixels_per_point * clip_rect.min.y; - let clip_max_x = pixels_per_point * clip_rect.max.x; - let clip_max_y = pixels_per_point * clip_rect.max.y; - let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x); - let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y); - let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x); - let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y); - let clip_min_x = clip_min_x.round() as i32; - let clip_min_y = clip_min_y.round() as i32; - let clip_max_x = clip_max_x.round() as i32; - let clip_max_y = clip_max_y.round() as i32; - - // scissor Y coordinate is from the bottom - gl.scissor( - clip_min_x, - self.canvas.height() as i32 - clip_max_y, - clip_max_x - clip_min_x, - clip_max_y - clip_min_y, - ); - - for mesh in mesh.split_to_u16() { - self.paint_mesh(&mesh)?; - } - } else { - tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id); - } - } - - if let Some(ref post_process) = self.post_process { - post_process.end(); - } - - Ok(()) - } - - fn name(&self) -> &'static str { - "egui_web (WebGL1)" - } -} - -struct PostProcess { - gl: Gl, - pos_buffer: WebGlBuffer, - a_pos_loc: u32, - index_buffer: WebGlBuffer, - texture: WebGlTexture, - texture_size: (i32, i32), - fbo: WebGlFramebuffer, - program: WebGlProgram, -} - -impl PostProcess { - fn new(gl: Gl, width: i32, height: i32) -> Result { - let fbo = gl - .create_framebuffer() - .ok_or("failed to create framebuffer")?; - gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo)); - - let texture = gl.create_texture().unwrap(); - gl.bind_texture(Gl::TEXTURE_2D, Some(&texture)); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32); - gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - 0, - ExtSRgb::SRGB_ALPHA_EXT as i32, - width, - height, - 0, - ExtSRgb::SRGB_ALPHA_EXT, - Gl::UNSIGNED_BYTE, - None, - ) - .unwrap(); - gl.framebuffer_texture_2d( - Gl::FRAMEBUFFER, - Gl::COLOR_ATTACHMENT0, - Gl::TEXTURE_2D, - Some(&texture), - 0, - ); - - gl.bind_texture(Gl::TEXTURE_2D, None); - gl.bind_framebuffer(Gl::FRAMEBUFFER, None); - - let shader_prefix = if crate::webgl1_requires_brightening(&gl) { - tracing::debug!("Enabling webkitGTK brightening workaround"); - "#define APPLY_BRIGHTENING_GAMMA" - } else { - "" - }; - - let vert_shader = compile_shader( - &gl, - Gl::VERTEX_SHADER, - include_str!("shader/post_vertex_100es.glsl"), - )?; - let frag_shader = compile_shader( - &gl, - Gl::FRAGMENT_SHADER, - &format!( - "{}{}", - shader_prefix, - include_str!("shader/post_fragment_100es.glsl") - ), - )?; - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - - let positions = vec![0u8, 0, 1, 0, 0, 1, 1, 1]; - - let indices = vec![0u8, 1, 2, 1, 2, 3]; - - let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&pos_buffer)); - gl.buffer_data_with_u8_array(Gl::ARRAY_BUFFER, &positions, Gl::STATIC_DRAW); - gl.bind_buffer(Gl::ARRAY_BUFFER, None); - - let a_pos_loc = gl.get_attrib_location(&program, "a_pos"); - assert!(a_pos_loc >= 0); - let a_pos_loc = a_pos_loc as u32; - - let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer)); - gl.buffer_data_with_u8_array(Gl::ELEMENT_ARRAY_BUFFER, &indices, Gl::STATIC_DRAW); - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); - - Ok(PostProcess { - gl, - pos_buffer, - a_pos_loc, - index_buffer, - texture, - texture_size: (width, height), - fbo, - program, - }) - } - - fn begin(&mut self, width: i32, height: i32) -> Result<(), JsValue> { - let gl = &self.gl; - - if (width, height) != self.texture_size { - gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); - gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - 0, - ExtSRgb::SRGB_ALPHA_EXT as i32, - width, - height, - 0, - ExtSRgb::SRGB_ALPHA_EXT, - Gl::UNSIGNED_BYTE, - None, - )?; - gl.bind_texture(Gl::TEXTURE_2D, None); - - self.texture_size = (width, height); - } - - gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&self.fbo)); - gl.clear_color(0.0, 0.0, 0.0, 0.0); - gl.clear(Gl::COLOR_BUFFER_BIT); - - Ok(()) - } - - fn end(&self) { - let gl = &self.gl; - - gl.bind_framebuffer(Gl::FRAMEBUFFER, None); - gl.disable(Gl::SCISSOR_TEST); - - gl.use_program(Some(&self.program)); - - gl.active_texture(Gl::TEXTURE0); - gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); - let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); - gl.uniform1i(Some(&u_sampler_loc), 0); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer)); - gl.vertex_attrib_pointer_with_i32(self.a_pos_loc, 2, Gl::UNSIGNED_BYTE, false, 0, 0); - gl.enable_vertex_attrib_array(self.a_pos_loc); - - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer)); - - gl.draw_elements_with_i32(Gl::TRIANGLES, 6, Gl::UNSIGNED_BYTE, 0); - - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); - gl.bind_buffer(Gl::ARRAY_BUFFER, None); - gl.bind_texture(Gl::TEXTURE_2D, None); - gl.use_program(None); - } -} - -impl Drop for PostProcess { - fn drop(&mut self) { - let gl = &self.gl; - gl.delete_buffer(Some(&self.pos_buffer)); - gl.delete_buffer(Some(&self.index_buffer)); - gl.delete_program(Some(&self.program)); - gl.delete_framebuffer(Some(&self.fbo)); - gl.delete_texture(Some(&self.texture)); - } -} - -fn compile_shader( - gl: &WebGlRenderingContext, - shader_type: u32, - source: &str, -) -> Result { - let shader = gl - .create_shader(shader_type) - .ok_or_else(|| String::from("Unable to create shader object"))?; - gl.shader_source(&shader, source); - gl.compile_shader(&shader); - - if gl - .get_shader_parameter(&shader, Gl::COMPILE_STATUS) - .as_bool() - .unwrap_or(false) - { - Ok(shader) - } else { - Err(gl - .get_shader_info_log(&shader) - .unwrap_or_else(|| "Unknown error creating shader".into())) - } -} - -fn link_program<'a, T: IntoIterator>( - gl: &WebGlRenderingContext, - shaders: T, -) -> Result { - let program = gl - .create_program() - .ok_or_else(|| String::from("Unable to create shader object"))?; - for shader in shaders { - gl.attach_shader(&program, shader) - } - gl.link_program(&program); - - if gl - .get_program_parameter(&program, Gl::LINK_STATUS) - .as_bool() - .unwrap_or(false) - { - Ok(program) - } else { - Err(gl - .get_program_info_log(&program) - .unwrap_or_else(|| "Unknown error creating program object".into())) - } -} diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs deleted file mode 100644 index c8ede7fd..00000000 --- a/egui_web/src/webgl2.rs +++ /dev/null @@ -1,637 +0,0 @@ -//! Mostly a carbon-copy of `webgl1.rs`. -use std::collections::HashMap; - -use { - js_sys::WebAssembly, - wasm_bindgen::{prelude::*, JsCast}, - web_sys::{ - WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlShader, - WebGlTexture, WebGlVertexArrayObject, - }, -}; - -use egui::{emath::vec2, epaint::Color32}; - -type Gl = WebGl2RenderingContext; - -pub struct WebGl2Painter { - canvas_id: String, - canvas: web_sys::HtmlCanvasElement, - gl: WebGl2RenderingContext, - program: WebGlProgram, - index_buffer: WebGlBuffer, - pos_buffer: WebGlBuffer, - tc_buffer: WebGlBuffer, - color_buffer: WebGlBuffer, - post_process: PostProcess, - - textures: HashMap, - next_native_tex_id: u64, -} - -impl WebGl2Painter { - pub fn new(canvas_id: &str) -> Result { - let canvas = crate::canvas_element_or_die(canvas_id); - - let gl = canvas - .get_context("webgl2")? - .ok_or_else(|| JsValue::from("Failed to get WebGl2 context"))? - .dyn_into::()?; - - // -------------------------------------------------------------------- - - let vert_shader = compile_shader( - &gl, - Gl::VERTEX_SHADER, - include_str!("shader/main_vertex_100es.glsl"), - )?; - let frag_shader = compile_shader( - &gl, - Gl::FRAGMENT_SHADER, - include_str!("shader/main_fragment_100es.glsl"), - )?; - - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; - let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; - let tc_buffer = gl.create_buffer().ok_or("failed to create tc_buffer")?; - let color_buffer = gl.create_buffer().ok_or("failed to create color_buffer")?; - - let post_process = - PostProcess::new(gl.clone(), canvas.width() as i32, canvas.height() as i32)?; - - Ok(WebGl2Painter { - canvas_id: canvas_id.to_owned(), - canvas, - gl, - program, - index_buffer, - pos_buffer, - tc_buffer, - color_buffer, - post_process, - textures: Default::default(), - next_native_tex_id: 1 << 32, - }) - } - - fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> { - self.textures.get(&texture_id) - } - - fn paint_mesh(&self, mesh: &egui::epaint::Mesh16) -> Result<(), JsValue> { - debug_assert!(mesh.is_valid()); - - let mut positions: Vec = Vec::with_capacity(2 * mesh.vertices.len()); - let mut tex_coords: Vec = Vec::with_capacity(2 * mesh.vertices.len()); - let mut colors: Vec = Vec::with_capacity(4 * mesh.vertices.len()); - for v in &mesh.vertices { - positions.push(v.pos.x); - positions.push(v.pos.y); - tex_coords.push(v.uv.x); - tex_coords.push(v.uv.y); - colors.push(v.color[0]); - colors.push(v.color[1]); - colors.push(v.color[2]); - colors.push(v.color[3]); - } - - // -------------------------------------------------------------------- - - let gl = &self.gl; - - let indices_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let indices_ptr = mesh.indices.as_ptr() as u32 / 2; - let indices_array = js_sys::Int16Array::new(&indices_memory_buffer) - .subarray(indices_ptr, indices_ptr + mesh.indices.len() as u32); - - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&self.index_buffer)); - gl.buffer_data_with_array_buffer_view( - Gl::ELEMENT_ARRAY_BUFFER, - &indices_array, - Gl::STREAM_DRAW, - ); - - // -------------------------------------------------------------------- - - let pos_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let pos_ptr = positions.as_ptr() as u32 / 4; - let pos_array = js_sys::Float32Array::new(&pos_memory_buffer) - .subarray(pos_ptr, pos_ptr + positions.len() as u32); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.pos_buffer)); - gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &pos_array, Gl::STREAM_DRAW); - - let a_pos_loc = gl.get_attrib_location(&self.program, "a_pos"); - assert!(a_pos_loc >= 0); - let a_pos_loc = a_pos_loc as u32; - - let normalize = false; - let stride = 0; - let offset = 0; - gl.vertex_attrib_pointer_with_i32(a_pos_loc, 2, Gl::FLOAT, normalize, stride, offset); - gl.enable_vertex_attrib_array(a_pos_loc); - - // -------------------------------------------------------------------- - - let tc_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let tc_ptr = tex_coords.as_ptr() as u32 / 4; - let tc_array = js_sys::Float32Array::new(&tc_memory_buffer) - .subarray(tc_ptr, tc_ptr + tex_coords.len() as u32); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.tc_buffer)); - gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &tc_array, Gl::STREAM_DRAW); - - let a_tc_loc = gl.get_attrib_location(&self.program, "a_tc"); - assert!(a_tc_loc >= 0); - let a_tc_loc = a_tc_loc as u32; - - let normalize = false; - let stride = 0; - let offset = 0; - gl.vertex_attrib_pointer_with_i32(a_tc_loc, 2, Gl::FLOAT, normalize, stride, offset); - gl.enable_vertex_attrib_array(a_tc_loc); - - // -------------------------------------------------------------------- - - let colors_memory_buffer = wasm_bindgen::memory() - .dyn_into::()? - .buffer(); - let colors_ptr = colors.as_ptr() as u32; - let colors_array = js_sys::Uint8Array::new(&colors_memory_buffer) - .subarray(colors_ptr, colors_ptr + colors.len() as u32); - - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.color_buffer)); - gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &colors_array, Gl::STREAM_DRAW); - - let a_srgba_loc = gl.get_attrib_location(&self.program, "a_srgba"); - assert!(a_srgba_loc >= 0); - let a_srgba_loc = a_srgba_loc as u32; - - let normalize = false; - let stride = 0; - let offset = 0; - gl.vertex_attrib_pointer_with_i32( - a_srgba_loc, - 4, - Gl::UNSIGNED_BYTE, - normalize, - stride, - offset, - ); - gl.enable_vertex_attrib_array(a_srgba_loc); - - // -------------------------------------------------------------------- - - gl.draw_elements_with_i32( - Gl::TRIANGLES, - mesh.indices.len() as i32, - Gl::UNSIGNED_SHORT, - 0, - ); - - Ok(()) - } - - fn set_texture_rgba( - &mut self, - tex_id: egui::TextureId, - pos: Option<[usize; 2]>, - [w, h]: [usize; 2], - pixels: &[u8], - ) { - let gl = &self.gl; - - let gl_texture = self - .textures - .entry(tex_id) - .or_insert_with(|| 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 _); - - 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); - - if let Some([x, y]) = pos { - gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - level, - x as _, - y as _, - w as _, - h as _, - src_format, - src_type, - Some(pixels), - ) - .unwrap(); - } else { - 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 _, - w as _, - h as _, - border, - src_format, - src_type, - Some(pixels), - ) - .unwrap(); - } - } -} - -impl epi::NativeTexture for WebGl2Painter { - type Texture = WebGlTexture; - - fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { - let id = egui::TextureId::User(self.next_native_tex_id); - self.next_native_tex_id += 1; - self.textures.insert(id, native); - id - } - - fn replace_native_texture(&mut self, id: egui::TextureId, native: Self::Texture) { - self.textures.insert(id, native); - } -} - -impl crate::Painter for WebGl2Painter { - fn max_texture_side(&self) -> usize { - if let Ok(max_texture_side) = self - .gl - .get_parameter(web_sys::WebGl2RenderingContext::MAX_TEXTURE_SIZE) - { - if let Some(max_texture_side) = max_texture_side.as_f64() { - return max_texture_side as usize; - } - } - - tracing::error!("Failed to query max texture size"); - - 2048 - } - - fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { - match &delta.image { - egui::ImageData::Color(image) => { - assert_eq!( - image.width() * image.height(), - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - - let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref()); - self.set_texture_rgba(tex_id, delta.pos, image.size, data); - } - egui::ImageData::Alpha(image) => { - let gamma = 1.0; - let data: Vec = image - .srgba_pixels(gamma) - .flat_map(|a| a.to_array()) - .collect(); - self.set_texture_rgba(tex_id, delta.pos, image.size, &data); - } - }; - } - - fn free_texture(&mut self, tex_id: egui::TextureId) { - self.textures.remove(&tex_id); - } - - fn debug_info(&self) -> String { - format!( - "Stored canvas size: {} x {}\n\ - gl context size: {} x {}", - self.canvas.width(), - self.canvas.height(), - self.gl.drawing_buffer_width(), - self.gl.drawing_buffer_height(), - ) - } - - /// id of the canvas html element containing the rendering - fn canvas_id(&self) -> &str { - &self.canvas_id - } - - fn clear(&mut self, clear_color: egui::Rgba) { - let gl = &self.gl; - - gl.disable(Gl::SCISSOR_TEST); - - let width = self.canvas.width() as i32; - let height = self.canvas.height() as i32; - gl.viewport(0, 0, width, height); - - let clear_color: Color32 = clear_color.into(); - gl.clear_color( - clear_color[0] as f32 / 255.0, - clear_color[1] as f32 / 255.0, - clear_color[2] as f32 / 255.0, - clear_color[3] as f32 / 255.0, - ); - gl.clear(Gl::COLOR_BUFFER_BIT); - } - - fn paint_meshes( - &mut self, - clipped_meshes: Vec, - pixels_per_point: f32, - ) -> Result<(), JsValue> { - let gl = &self.gl; - - self.post_process - .begin(self.canvas.width() as i32, self.canvas.height() as i32)?; - - gl.enable(Gl::SCISSOR_TEST); - gl.disable(Gl::CULL_FACE); // egui is not strict about winding order. - gl.enable(Gl::BLEND); - gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA); // premultiplied alpha - gl.use_program(Some(&self.program)); - gl.active_texture(Gl::TEXTURE0); - - let u_screen_size_loc = gl - .get_uniform_location(&self.program, "u_screen_size") - .unwrap(); - let screen_size_pixels = vec2(self.canvas.width() as f32, self.canvas.height() as f32); - let screen_size_points = screen_size_pixels / pixels_per_point; - gl.uniform2f( - Some(&u_screen_size_loc), - screen_size_points.x, - screen_size_points.y, - ); - - let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); - gl.uniform1i(Some(&u_sampler_loc), 0); - - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - if let Some(gl_texture) = self.get_texture(mesh.texture_id) { - gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); - - let clip_min_x = pixels_per_point * clip_rect.min.x; - let clip_min_y = pixels_per_point * clip_rect.min.y; - let clip_max_x = pixels_per_point * clip_rect.max.x; - let clip_max_y = pixels_per_point * clip_rect.max.y; - let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x); - let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y); - let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x); - let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y); - let clip_min_x = clip_min_x.round() as i32; - let clip_min_y = clip_min_y.round() as i32; - let clip_max_x = clip_max_x.round() as i32; - let clip_max_y = clip_max_y.round() as i32; - - // scissor Y coordinate is from the bottom - gl.scissor( - clip_min_x, - self.canvas.height() as i32 - clip_max_y, - clip_max_x - clip_min_x, - clip_max_y - clip_min_y, - ); - - for mesh in mesh.split_to_u16() { - self.paint_mesh(&mesh)?; - } - } else { - tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id); - } - } - - self.post_process.end(); - - Ok(()) - } - - fn name(&self) -> &'static str { - "egui_web (WebGL2)" - } -} - -/// Uses a framebuffer to render everything in linear color space and convert it back to sRGB -/// in a separate "post processing" step -struct PostProcess { - gl: Gl, - pos_buffer: WebGlBuffer, - index_buffer: WebGlBuffer, - vao: WebGlVertexArrayObject, - texture: WebGlTexture, - texture_size: (i32, i32), - fbo: WebGlFramebuffer, - program: WebGlProgram, -} - -impl PostProcess { - fn new(gl: Gl, width: i32, height: i32) -> Result { - let fbo = gl - .create_framebuffer() - .ok_or("failed to create framebuffer")?; - gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&fbo)); - - let texture = gl.create_texture().unwrap(); - gl.bind_texture(Gl::TEXTURE_2D, Some(&texture)); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32); - gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::NEAREST as i32); - gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - 0, - Gl::SRGB8_ALPHA8 as i32, - width, - height, - 0, - Gl::RGBA, - Gl::UNSIGNED_BYTE, - None, - ) - .unwrap(); - gl.framebuffer_texture_2d( - Gl::FRAMEBUFFER, - Gl::COLOR_ATTACHMENT0, - Gl::TEXTURE_2D, - Some(&texture), - 0, - ); - - gl.bind_texture(Gl::TEXTURE_2D, None); - gl.bind_framebuffer(Gl::FRAMEBUFFER, None); - - let vert_shader = compile_shader( - &gl, - Gl::VERTEX_SHADER, - include_str!("shader/post_vertex_100es.glsl"), - )?; - let frag_shader = compile_shader( - &gl, - Gl::FRAGMENT_SHADER, - include_str!("shader/post_fragment_100es.glsl"), - )?; - let program = link_program(&gl, [vert_shader, frag_shader].iter())?; - - let vao = gl.create_vertex_array().ok_or("failed to create vao")?; - gl.bind_vertex_array(Some(&vao)); - - let positions = vec![0u8, 0, 1, 0, 0, 1, 1, 1]; - - let indices = vec![0u8, 1, 2, 1, 2, 3]; - - let pos_buffer = gl.create_buffer().ok_or("failed to create pos_buffer")?; - gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&pos_buffer)); - gl.buffer_data_with_u8_array(Gl::ARRAY_BUFFER, &positions, Gl::STATIC_DRAW); - - let a_pos_loc = gl.get_attrib_location(&program, "a_pos"); - assert!(a_pos_loc >= 0); - gl.vertex_attrib_pointer_with_i32(a_pos_loc as u32, 2, Gl::UNSIGNED_BYTE, false, 0, 0); - gl.enable_vertex_attrib_array(a_pos_loc as u32); - - gl.bind_buffer(Gl::ARRAY_BUFFER, None); - - let index_buffer = gl.create_buffer().ok_or("failed to create index_buffer")?; - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, Some(&index_buffer)); - gl.buffer_data_with_u8_array(Gl::ELEMENT_ARRAY_BUFFER, &indices, Gl::STATIC_DRAW); - - gl.bind_vertex_array(None); - gl.bind_buffer(Gl::ELEMENT_ARRAY_BUFFER, None); - - Ok(PostProcess { - gl, - pos_buffer, - index_buffer, - vao, - texture, - texture_size: (width, height), - fbo, - program, - }) - } - - fn begin(&mut self, width: i32, height: i32) -> Result<(), JsValue> { - let gl = &self.gl; - - if (width, height) != self.texture_size { - gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); - gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1); - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - 0, - Gl::SRGB8_ALPHA8 as i32, - width, - height, - 0, - Gl::RGBA, - Gl::UNSIGNED_BYTE, - None, - )?; - gl.bind_texture(Gl::TEXTURE_2D, None); - - self.texture_size = (width, height); - } - - gl.bind_framebuffer(Gl::FRAMEBUFFER, Some(&self.fbo)); - gl.clear_color(0.0, 0.0, 0.0, 0.0); - gl.clear(Gl::COLOR_BUFFER_BIT); - - Ok(()) - } - - fn end(&self) { - let gl = &self.gl; - - gl.bind_framebuffer(Gl::FRAMEBUFFER, None); - gl.disable(Gl::SCISSOR_TEST); - - gl.use_program(Some(&self.program)); - - gl.active_texture(Gl::TEXTURE0); - gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); - let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); - gl.uniform1i(Some(&u_sampler_loc), 0); - - gl.bind_vertex_array(Some(&self.vao)); - - gl.draw_elements_with_i32(Gl::TRIANGLES, 6, Gl::UNSIGNED_BYTE, 0); - - gl.bind_texture(Gl::TEXTURE_2D, None); - gl.bind_vertex_array(None); - gl.use_program(None); - } -} - -impl Drop for PostProcess { - fn drop(&mut self) { - let gl = &self.gl; - gl.delete_vertex_array(Some(&self.vao)); - gl.delete_buffer(Some(&self.pos_buffer)); - gl.delete_buffer(Some(&self.index_buffer)); - gl.delete_program(Some(&self.program)); - gl.delete_framebuffer(Some(&self.fbo)); - gl.delete_texture(Some(&self.texture)); - } -} - -fn compile_shader( - gl: &WebGl2RenderingContext, - shader_type: u32, - source: &str, -) -> Result { - let shader = gl - .create_shader(shader_type) - .ok_or_else(|| String::from("Unable to create shader object"))?; - gl.shader_source(&shader, source); - gl.compile_shader(&shader); - - if gl - .get_shader_parameter(&shader, Gl::COMPILE_STATUS) - .as_bool() - .unwrap_or(false) - { - Ok(shader) - } else { - Err(gl - .get_shader_info_log(&shader) - .unwrap_or_else(|| "Unknown error creating shader".into())) - } -} - -fn link_program<'a, T: IntoIterator>( - gl: &WebGl2RenderingContext, - shaders: T, -) -> Result { - let program = gl - .create_program() - .ok_or_else(|| String::from("Unable to create shader object"))?; - for shader in shaders { - gl.attach_shader(&program, shader) - } - gl.link_program(&program); - - if gl - .get_program_parameter(&program, Gl::LINK_STATUS) - .as_bool() - .unwrap_or(false) - { - Ok(program) - } else { - Err(gl - .get_program_info_log(&program) - .unwrap_or_else(|| "Unknown error creating program object".into())) - } -} diff --git a/sh/check.sh b/sh/check.sh index 2f5038f0..e2d8bd35 100755 --- a/sh/check.sh +++ b/sh/check.sh @@ -29,7 +29,7 @@ cargo doc --document-private-items --no-deps --all-features (cd epi && cargo check --no-default-features) (cd egui_demo_lib && cargo check --no-default-features) (cd egui_extras && cargo check --no-default-features) -# (cd egui_web && cargo check --no-default-features) # we need to pick webgl or glow backend +(cd egui_web && cargo check --no-default-features) # (cd egui-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded (cd egui_glium && cargo check --no-default-features) (cd egui_glow && cargo check --no-default-features) From 29c52e8eb69ab47367aec562e932cc4e311faf47 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 13 Mar 2022 22:49:24 +0100 Subject: [PATCH 10/12] Remove epi backend from egui_glow (#1361) --- Cargo.lock | 1 - egui_glium/CHANGELOG.md | 2 + egui_glium/Cargo.toml | 13 +-- egui_glium/examples/native_texture.rs | 1 - egui_glium/src/epi_backend.rs | 141 -------------------------- egui_glium/src/lib.rs | 5 - egui_glium/src/painter.rs | 11 +- 7 files changed, 6 insertions(+), 168 deletions(-) delete mode 100644 egui_glium/src/epi_backend.rs diff --git a/Cargo.lock b/Cargo.lock index 939fadfe..593823c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1073,7 +1073,6 @@ dependencies = [ "bytemuck", "egui", "egui-winit", - "epi", "glium", "image", ] diff --git a/egui_glium/CHANGELOG.md b/egui_glium/CHANGELOG.md index 81bcbcbe..6f8cbd8c 100644 --- a/egui_glium/CHANGELOG.md +++ b/egui_glium/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to the `egui_glium` integration will be noted in this file. ## Unreleased +* Remove "epi" feature ([#1361](https://github.com/emilk/egui/pull/1361)). +* Remove need for `trait epi::NativeTexture` to use the `fn register_native_texture/replace_native_texture` ([#1361](https://github.com/emilk/egui/pull/1361)). ## 0.17.0 - 2022-02-22 diff --git a/egui_glium/Cargo.toml b/egui_glium/Cargo.toml index 8dbcfbf7..b23a7a61 100644 --- a/egui_glium/Cargo.toml +++ b/egui_glium/Cargo.toml @@ -38,13 +38,7 @@ default_fonts = ["egui/default_fonts"] links = ["egui-winit/links"] # enable persisting native window options and egui memory -persistence = [ - "egui-winit/persistence", - "egui/persistence", - "epi", # also implied by the lines below, see https://github.com/rust-lang/cargo/issues/8832 - "epi/file_storage", - "epi/persistence", -] +persistence = ["egui-winit/persistence", "egui/persistence"] # experimental support for a screen reader screen_reader = ["egui-winit/screen_reader"] @@ -55,10 +49,7 @@ egui = { version = "0.17.0", path = "../egui", default-features = false, feature "convert_bytemuck", "single_threaded", ] } -egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false, features = [ - "epi", -] } -epi = { version = "0.17.0", path = "../epi", optional = true } +egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false } ahash = "0.7" bytemuck = "1.7" diff --git a/egui_glium/examples/native_texture.rs b/egui_glium/examples/native_texture.rs index 821c3c87..a5197ccf 100644 --- a/egui_glium/examples/native_texture.rs +++ b/egui_glium/examples/native_texture.rs @@ -2,7 +2,6 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use epi::NativeTexture; use glium::glutin; fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { diff --git a/egui_glium/src/epi_backend.rs b/egui_glium/src/epi_backend.rs deleted file mode 100644 index dafca3b4..00000000 --- a/egui_glium/src/epi_backend.rs +++ /dev/null @@ -1,141 +0,0 @@ -use glium::glutin; - -use crate::*; - -struct RequestRepaintEvent; - -struct GliumRepaintSignal( - std::sync::Mutex>, -); - -impl epi::backend::RepaintSignal for GliumRepaintSignal { - fn request_repaint(&self) { - self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); - } -} - -fn create_display( - window_builder: glutin::window::WindowBuilder, - event_loop: &glutin::event_loop::EventLoop, -) -> glium::Display { - let context_builder = glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_srgb(true) - .with_stencil_buffer(0) - .with_vsync(true); - - glium::Display::new(window_builder, context_builder, event_loop).unwrap() -} - -// ---------------------------------------------------------------------------- - -pub use epi::NativeOptions; - -/// Run an egui app -pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { - let persistence = egui_winit::epi::Persistence::from_app_name(app.name()); - let window_settings = persistence.load_window_settings(); - let window_builder = - egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name()); - let event_loop = glutin::event_loop::EventLoop::with_user_event(); - let display = create_display(window_builder, &event_loop); - - let repaint_signal = std::sync::Arc::new(GliumRepaintSignal(std::sync::Mutex::new( - event_loop.create_proxy(), - ))); - - let mut painter = crate::Painter::new(&display); - let mut integration = egui_winit::epi::EpiIntegration::new( - "egui_glium", - painter.max_texture_side(), - display.gl_window().window(), - repaint_signal, - persistence, - app, - ); - - let mut is_focused = true; - - event_loop.run(move |event, _, control_flow| { - let mut redraw = || { - if !is_focused { - // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 - // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 - // But we know if we are focused (in foreground). When minimized, we are not focused. - // However, a user may want an egui with an animation in the background, - // so we still need to repaint quite fast. - std::thread::sleep(std::time::Duration::from_millis(10)); - } - - let egui::FullOutput { - platform_output, - needs_repaint, - textures_delta, - shapes, - } = integration.update(display.gl_window().window()); - - integration.handle_platform_output(display.gl_window().window(), platform_output); - - let clipped_meshes = integration.egui_ctx.tessellate(shapes); - - // paint: - { - use glium::Surface as _; - let mut target = display.draw(); - let color = integration.app.clear_color(); - target.clear_color(color[0], color[1], color[2], color[3]); - - painter.paint_and_update_textures( - &display, - &mut target, - integration.egui_ctx.pixels_per_point(), - clipped_meshes, - &textures_delta, - ); - - target.finish().unwrap(); - } - - { - *control_flow = if integration.should_quit() { - glutin::event_loop::ControlFlow::Exit - } else if needs_repaint { - display.gl_window().window().request_redraw(); - glutin::event_loop::ControlFlow::Poll - } else { - glutin::event_loop::ControlFlow::Wait - }; - } - - integration.maybe_autosave(display.gl_window().window()); - }; - - match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - glutin::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), - - glutin::event::Event::WindowEvent { event, .. } => { - if let glutin::event::WindowEvent::Focused(new_focused) = event { - is_focused = new_focused; - } - - integration.on_event(&event); - if integration.should_quit() { - *control_flow = glium::glutin::event_loop::ControlFlow::Exit; - } - - display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead - } - glutin::event::Event::LoopDestroyed => { - integration.on_exit(display.gl_window().window()); - } - glutin::event::Event::UserEvent(RequestRepaintEvent) => { - display.gl_window().window().request_redraw(); - } - _ => (), - } - }); -} diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 991a072a..db6bf66a 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -90,11 +90,6 @@ mod painter; pub use painter::Painter; -#[cfg(feature = "epi")] -mod epi_backend; -#[cfg(feature = "epi")] -pub use epi_backend::{run, NativeOptions}; - pub use egui_winit; // ---------------------------------------------------------------------------- diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 26f74133..5299a11f 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -21,7 +21,6 @@ pub struct Painter { textures: AHashMap>, - #[cfg(feature = "epi")] /// [`egui::TextureId::User`] index next_native_tex_id: u64, } @@ -56,7 +55,6 @@ impl Painter { max_texture_side, program, textures: Default::default(), - #[cfg(feature = "epi")] next_native_tex_id: 0, } } @@ -266,20 +264,15 @@ impl Painter { fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> { self.textures.get(&texture_id).map(|rc| rc.as_ref()) } -} -#[cfg(feature = "epi")] -impl epi::NativeTexture for Painter { - type Texture = Rc; - - fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { + pub fn register_native_texture(&mut self, native: Rc) -> egui::TextureId { let id = egui::TextureId::User(self.next_native_tex_id); self.next_native_tex_id += 1; self.textures.insert(id, native); id } - fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) { + pub fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Rc) { self.textures.insert(id, replacing); } } From 002158050bcbc0ac23b290f3a1dccf0328801e61 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 14 Mar 2022 12:33:17 +0100 Subject: [PATCH 11/12] Add Frame::canvas - bright in bright mode, dark in dark mode (#1362) and use it in the demo app --- CHANGELOG.md | 10 +++++++++- egui/src/containers/frame.rs | 17 ++++++++++++++--- egui_demo_lib/src/apps/demo/dancing_strings.rs | 13 ++++++++----- egui_demo_lib/src/apps/demo/multi_touch.rs | 9 +++++++-- egui_demo_lib/src/apps/demo/paint_bezier.rs | 4 ++-- egui_demo_lib/src/apps/demo/painting.rs | 4 ++-- 6 files changed, 42 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a7ffb8..acfcfad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,15 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ## Unreleased -* Fixed ComboBoxes always being rendered left-aligned ([1304](https://github.com/emilk/egui/pull/1304)) + +### Added ⭐ +* Added `Frame::canvas` ([1362](https://github.com/emilk/egui/pull/1362)). + +### Changed 🔧 + +### Fixed 🐛 +* Fixed ComboBoxes always being rendered left-aligned ([1304](https://github.com/emilk/egui/pull/1304)). + ## 0.17.0 - 2022-02-22 - Improved font selection and image handling diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index 9e7cd949..7eabe383 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -80,16 +80,27 @@ impl Frame { } } - /// dark canvas to draw on - pub fn dark_canvas(style: &Style) -> Self { + /// A canvas to draw on. + /// + /// In bright mode this will be very bright, + /// and in dark mode this will be very dark. + pub fn canvas(style: &Style) -> Self { Self { margin: Margin::symmetric(10.0, 10.0), rounding: style.visuals.widgets.noninteractive.rounding, - fill: Color32::from_black_alpha(250), + fill: style.visuals.extreme_bg_color, stroke: style.visuals.window_stroke(), ..Default::default() } } + + /// A dark canvas to draw on. + pub fn dark_canvas(style: &Style) -> Self { + Self { + fill: Color32::from_black_alpha(250), + ..Self::canvas(style) + } + } } impl Frame { diff --git a/egui_demo_lib/src/apps/demo/dancing_strings.rs b/egui_demo_lib/src/apps/demo/dancing_strings.rs index afd04dc3..07c6968f 100644 --- a/egui_demo_lib/src/apps/demo/dancing_strings.rs +++ b/egui_demo_lib/src/apps/demo/dancing_strings.rs @@ -22,7 +22,13 @@ impl super::Demo for DancingStrings { impl super::View for DancingStrings { fn ui(&mut self, ui: &mut Ui) { - Frame::dark_canvas(ui.style()).show(ui, |ui| { + let color = if ui.visuals().dark_mode { + Color32::from_additive_luminance(196) + } else { + Color32::from_black_alpha(240) + }; + + Frame::canvas(ui.style()).show(ui, |ui| { ui.ctx().request_repaint(); let time = ui.input().time; @@ -49,10 +55,7 @@ impl super::View for DancingStrings { .collect(); let thickness = 10.0 / mode as f32; - shapes.push(epaint::Shape::line( - points, - Stroke::new(thickness, Color32::from_additive_luminance(196)), - )); + shapes.push(epaint::Shape::line(points, Stroke::new(thickness, color))); } ui.painter().extend(shapes); diff --git a/egui_demo_lib/src/apps/demo/multi_touch.rs b/egui_demo_lib/src/apps/demo/multi_touch.rs index 0f154301..c67ad521 100644 --- a/egui_demo_lib/src/apps/demo/multi_touch.rs +++ b/egui_demo_lib/src/apps/demo/multi_touch.rs @@ -52,7 +52,13 @@ impl super::View for MultiTouch { let num_touches = ui.input().multi_touch().map_or(0, |mt| mt.num_touches); ui.label(format!("Current touches: {}", num_touches)); - Frame::dark_canvas(ui.style()).show(ui, |ui| { + let color = if ui.visuals().dark_mode { + Color32::WHITE + } else { + Color32::BLACK + }; + + Frame::canvas(ui.style()).show(ui, |ui| { // Note that we use `Sense::drag()` although we do not use any pointer events. With // the current implementation, the fact that a touch event of two or more fingers is // recognized, does not mean that the pointer events are suppressed, which are always @@ -76,7 +82,6 @@ impl super::View for MultiTouch { // check for touch input (or the lack thereof) and update zoom and scale factors, plus // color and width: let mut stroke_width = 1.; - let color = Color32::GRAY; if let Some(multi_touch) = ui.ctx().multi_touch() { // This adjusts the current zoom factor and rotation angle according to the dynamic // change (for the current frame) of the touch gesture: diff --git a/egui_demo_lib/src/apps/demo/paint_bezier.rs b/egui_demo_lib/src/apps/demo/paint_bezier.rs index a8261eb9..cf1a0bd2 100644 --- a/egui_demo_lib/src/apps/demo/paint_bezier.rs +++ b/egui_demo_lib/src/apps/demo/paint_bezier.rs @@ -31,7 +31,7 @@ impl Default for PaintBezier { pos2(200.0, 200.0), pos2(250.0, 50.0), ], - stroke: Stroke::new(1.0, Color32::LIGHT_BLUE), + stroke: Stroke::new(1.0, Color32::from_rgb(25, 200, 100)), fill: Color32::from_rgb(50, 100, 150).linear_multiply(0.25), aux_stroke: Stroke::new(1.0, Color32::RED.linear_multiply(0.25)), bounding_box_stroke: Stroke::new(0.0, Color32::LIGHT_GREEN.linear_multiply(0.25)), @@ -164,7 +164,7 @@ impl super::View for PaintBezier { }); self.ui_control(ui); - Frame::dark_canvas(ui.style()).show(ui, |ui| { + Frame::canvas(ui.style()).show(ui, |ui| { self.ui_content(ui); }); } diff --git a/egui_demo_lib/src/apps/demo/painting.rs b/egui_demo_lib/src/apps/demo/painting.rs index 2b76851e..33b66591 100644 --- a/egui_demo_lib/src/apps/demo/painting.rs +++ b/egui_demo_lib/src/apps/demo/painting.rs @@ -12,7 +12,7 @@ impl Default for Painting { fn default() -> Self { Self { lines: Default::default(), - stroke: Stroke::new(1.0, Color32::LIGHT_BLUE), + stroke: Stroke::new(1.0, Color32::from_rgb(25, 200, 100)), } } } @@ -91,7 +91,7 @@ impl super::View for Painting { }); self.ui_control(ui); ui.label("Paint with your mouse/touch!"); - Frame::dark_canvas(ui.style()).show(ui, |ui| { + Frame::canvas(ui.style()).show(ui, |ui| { self.ui_content(ui); }); } From 6aee4997d4c5635975df9cadfdae8da6532bdcec Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 14 Mar 2022 13:25:11 +0100 Subject: [PATCH 12/12] Add Shape::Callback to do custom rendering inside of an egui UI (#1351) * Add Shape::Callback to do custom rendering inside of an egui UI * Use Rc everywhere * Remove trait WebPainter * Add glow::Context to epi::App::setup --- CHANGELOG.md | 6 +- Cargo.lock | 4 + README.md | 4 +- eframe/Cargo.toml | 2 + eframe/examples/custom_3d.rs | 193 ++++++++++++++++++ eframe/examples/custom_font.rs | 1 + egui-winit/Cargo.toml | 10 +- egui-winit/src/epi.rs | 7 +- egui/src/context.rs | 21 +- egui/src/introspection.rs | 6 +- egui/src/lib.rs | 10 +- egui_demo_lib/src/apps/demo/app.rs | 1 + egui_demo_lib/src/lib.rs | 11 +- egui_demo_lib/src/wrap_app.rs | 1 + egui_glium/src/lib.rs | 4 +- egui_glium/src/painter.rs | 27 ++- egui_glow/Cargo.toml | 2 +- egui_glow/examples/pure_glow.rs | 7 +- egui_glow/src/epi_backend.rs | 11 +- egui_glow/src/painter.rs | 301 +++++++++++++++++------------ egui_glow/src/post_process.rs | 95 +++++---- egui_glow/src/winit.rs | 19 +- egui_web/src/backend.rs | 35 ++-- egui_web/src/glow_wrapping.rs | 70 ++++--- egui_web/src/lib.rs | 6 +- egui_web/src/painter.rs | 43 ----- epaint/CHANGELOG.md | 1 + epaint/src/lib.rs | 22 ++- epaint/src/shape.rs | 73 ++++++- epaint/src/shape_transform.rs | 3 + epaint/src/stats.rs | 31 ++- epaint/src/tessellator.rs | 110 +++++++---- epi/Cargo.toml | 1 + epi/src/lib.rs | 24 ++- 34 files changed, 777 insertions(+), 385 deletions(-) create mode 100644 eframe/examples/custom_3d.rs delete mode 100644 egui_web/src/painter.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index acfcfad0..610143f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,14 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ## Unreleased ### Added ⭐ -* Added `Frame::canvas` ([1362](https://github.com/emilk/egui/pull/1362)). +* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). +* Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)). ### Changed 🔧 +* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). ### Fixed 🐛 -* Fixed ComboBoxes always being rendered left-aligned ([1304](https://github.com/emilk/egui/pull/1304)). +* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). ## 0.17.0 - 2022-02-22 - Improved font selection and image handling diff --git a/Cargo.lock b/Cargo.lock index 593823c2..55dee5c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -991,7 +991,9 @@ dependencies = [ "egui_web", "ehttp", "epi", + "glow", "image", + "parking_lot 0.12.0", "poll-promise", "rfd", ] @@ -1016,6 +1018,7 @@ dependencies = [ "dark-light", "egui", "epi", + "glow", "instant", "serde", "tracing", @@ -1217,6 +1220,7 @@ version = "0.17.0" dependencies = [ "directories-next", "egui", + "glow", "ron", "serde", "tracing", diff --git a/README.md b/README.md index 83b4486e..76f53ca8 100644 --- a/README.md +++ b/README.md @@ -204,9 +204,9 @@ loop { let full_output = egui_ctx.run(raw_input, |egui_ctx| { my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here }); - let clipped_meshes = egui_ctx.tessellate(full_output.shapes); // creates triangles to paint + let clipped_primitives = egui_ctx.tessellate(full_output.shapes); // creates triangles to paint - my_integration.paint(&full_output.textures_delta, clipped_meshes); + my_integration.paint(&full_output.textures_delta, clipped_primitives); let platform_output = full_output.platform_output; my_integration.set_cursor_icon(platform_output.cursor_icon); diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index df6cc289..a502ce46 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -66,9 +66,11 @@ egui_web = { version = "0.17.0", path = "../egui_web", default-features = false # For examples: egui_extras = { path = "../egui_extras", features = ["image", "svg"] } ehttp = "0.2" +glow = "0.11" image = { version = "0.24", default-features = false, features = [ "jpeg", "png", ] } +parking_lot = "0.12" poll-promise = "0.1" rfd = "0.8" diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs new file mode 100644 index 00000000..548918f7 --- /dev/null +++ b/eframe/examples/custom_3d.rs @@ -0,0 +1,193 @@ +//! This demo shows how to embed 3D rendering using [`glow`](https://github.com/grovesNL/glow) in `eframe`. +//! +//! This is very advanced usage, and you need to be careful. +//! +//! If you want an easier way to show 3D graphics with egui, take a look at: +//! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) +//! * [`three-d`](https://github.com/asny/three-d) + +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::{egui, epi}; + +use parking_lot::Mutex; +use std::sync::Arc; + +#[derive(Default)] +struct MyApp { + rotating_triangle: Arc>>, + angle: f32, +} + +impl epi::App for MyApp { + fn name(&self) -> &str { + "Custom 3D painting inside an egui window" + } + + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Here is some 3D stuff:"); + + egui::ScrollArea::both().show(ui, |ui| { + egui::Frame::dark_canvas(ui.style()).show(ui, |ui| { + self.custom_painting(ui); + }); + ui.label("Drag to rotate!"); + }); + }); + + let mut frame = egui::Frame::window(&*ctx.style()); + frame.fill = frame.fill.linear_multiply(0.5); // transparent + egui::Window::new("3D stuff in a window") + .frame(frame) + .show(ctx, |ui| { + self.custom_painting(ui); + }); + } +} + +impl MyApp { + fn custom_painting(&mut self, ui: &mut egui::Ui) { + let (rect, response) = + ui.allocate_exact_size(egui::Vec2::splat(256.0), egui::Sense::drag()); + + self.angle += response.drag_delta().x * 0.01; + + let angle = self.angle; + let rotating_triangle = self.rotating_triangle.clone(); + + let callback = egui::epaint::PaintCallback { + rect, + callback: std::sync::Arc::new(move |render_ctx| { + if let Some(painter) = render_ctx.downcast_ref::() { + let mut rotating_triangle = rotating_triangle.lock(); + let rotating_triangle = rotating_triangle + .get_or_insert_with(|| RotatingTriangle::new(painter.gl())); + rotating_triangle.paint(painter.gl(), angle); + } else { + eprintln!("Can't do custom painting because we are not using a glow context"); + } + }), + }; + ui.painter().add(callback); + } +} + +struct RotatingTriangle { + program: glow::Program, + vertex_array: glow::VertexArray, +} + +impl RotatingTriangle { + fn new(gl: &glow::Context) -> Self { + use glow::HasContext as _; + + let shader_version = if cfg!(target_arch = "wasm32") { + "#version 300 es" + } else { + "#version 410" + }; + + unsafe { + let program = gl.create_program().expect("Cannot create program"); + + let (vertex_shader_source, fragment_shader_source) = ( + r#" + const vec2 verts[3] = vec2[3]( + vec2(0.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, -1.0) + ); + const vec4 colors[3] = vec4[3]( + vec4(1.0, 0.0, 0.0, 1.0), + vec4(0.0, 1.0, 0.0, 1.0), + vec4(0.0, 0.0, 1.0, 1.0) + ); + out vec4 v_color; + uniform float u_angle; + void main() { + v_color = colors[gl_VertexID]; + gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); + gl_Position.x *= cos(u_angle); + } + "#, + r#" + precision mediump float; + in vec4 v_color; + out vec4 out_color; + void main() { + out_color = v_color; + } + "#, + ); + + let shader_sources = [ + (glow::VERTEX_SHADER, vertex_shader_source), + (glow::FRAGMENT_SHADER, fragment_shader_source), + ]; + + let shaders: Vec<_> = shader_sources + .iter() + .map(|(shader_type, shader_source)| { + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source)); + gl.compile_shader(shader); + if !gl.get_shader_compile_status(shader) { + panic!("{}", gl.get_shader_info_log(shader)); + } + gl.attach_shader(program, shader); + shader + }) + .collect(); + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + let vertex_array = gl + .create_vertex_array() + .expect("Cannot create vertex array"); + + Self { + program, + vertex_array, + } + } + } + + // TODO: figure out how to call this in a nice way + #[allow(unused)] + fn destroy(self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.delete_program(self.program); + gl.delete_vertex_array(self.vertex_array); + } + } + + fn paint(&self, gl: &glow::Context, angle: f32) { + use glow::HasContext as _; + unsafe { + gl.use_program(Some(self.program)); + gl.uniform_1_f32( + gl.get_uniform_location(self.program, "u_angle").as_ref(), + angle, + ); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.draw_arrays(glow::TRIANGLES, 0, 3); + } + } +} + +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native(Box::new(MyApp::default()), options); +} diff --git a/eframe/examples/custom_font.rs b/eframe/examples/custom_font.rs index 8ba2ced4..0192d32c 100644 --- a/eframe/examples/custom_font.rs +++ b/eframe/examples/custom_font.rs @@ -22,6 +22,7 @@ impl epi::App for MyApp { ctx: &egui::Context, _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>, + _gl: &std::rc::Rc, ) { // Start with the default fonts (we will be adding to them rather than replacing them). let mut fonts = egui::FontDefinitions::default(); diff --git a/egui-winit/Cargo.toml b/egui-winit/Cargo.toml index 1bf48fda..a7cde96f 100644 --- a/egui-winit/Cargo.toml +++ b/egui-winit/Cargo.toml @@ -24,6 +24,12 @@ default = ["clipboard", "dark-light", "links"] # if disabled a clipboard will be simulated so you can still copy/paste within the egui app. clipboard = ["copypasta"] +# implement bytemuck on most types. +convert_bytemuck = ["egui/convert_bytemuck"] + +# Only for `egui_glow` - the official eframe/epi backend. +epi_backend = ["epi", "glow"] + # enable opening links in a browser when an egui hyperlink is clicked. links = ["webbrowser"] @@ -36,9 +42,6 @@ persistence = [ ] # can't add epi/persistence here because of https://github.com/rust-lang/cargo/issues/8832 serialize = ["egui/serialize", "serde"] -# implement bytemuck on most types. -convert_bytemuck = ["egui/convert_bytemuck"] - [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ @@ -53,6 +56,7 @@ epi = { version = "0.17.0", path = "../epi", optional = true } copypasta = { version = "0.7", optional = true } dark-light = { version = "0.2.1", optional = true } # detect dark mode system preference +glow = { version = "0.11", optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } webbrowser = { version = "0.6", optional = true } diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index fd671131..d257ca21 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -232,6 +232,7 @@ impl EpiIntegration { integration_name: &'static str, max_texture_side: usize, window: &winit::window::Window, + gl: &std::rc::Rc, repaint_signal: std::sync::Arc, persistence: crate::epi::Persistence, app: Box, @@ -271,7 +272,7 @@ impl EpiIntegration { can_drag_window: false, }; - slf.setup(window); + slf.setup(window, gl); if slf.app.warm_up_enabled() { slf.warm_up(window); } @@ -279,9 +280,9 @@ impl EpiIntegration { slf } - fn setup(&mut self, window: &winit::window::Window) { + fn setup(&mut self, window: &winit::window::Window, gl: &std::rc::Rc) { self.app - .setup(&self.egui_ctx, &self.frame, self.persistence.storage()); + .setup(&self.egui_ctx, &self.frame, self.persistence.storage(), gl); let app_output = self.frame.take_app_output(); if app_output.quit { diff --git a/egui/src/context.rs b/egui/src/context.rs index c0c261fc..fef366f6 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -122,7 +122,7 @@ impl ContextImpl { /// /// ``` no_run /// # fn handle_platform_output(_: egui::PlatformOutput) {} -/// # fn paint(textures_detla: egui::TexturesDelta, _: Vec) {} +/// # fn paint(textures_detla: egui::TexturesDelta, _: Vec) {} /// let mut ctx = egui::Context::default(); /// /// // Game loop: @@ -137,8 +137,8 @@ impl ContextImpl { /// }); /// }); /// handle_platform_output(full_output.platform_output); -/// let clipped_meshes = ctx.tessellate(full_output.shapes); // create triangles to paint -/// paint(full_output.textures_delta, clipped_meshes); +/// let clipped_primitives = ctx.tessellate(full_output.shapes); // create triangles to paint +/// paint(full_output.textures_delta, clipped_primitives); /// } /// ``` #[derive(Clone)] @@ -773,7 +773,7 @@ impl Context { } /// Tessellate the given shapes into triangle meshes. - pub fn tessellate(&self, shapes: Vec) -> Vec { + pub fn tessellate(&self, shapes: Vec) -> Vec { // A tempting optimization is to reuse the tessellation from last frame if the // shapes are the same, but just comparing the shapes takes about 50% of the time // it takes to tessellate them, so it is not a worth optimization. @@ -782,13 +782,13 @@ impl Context { tessellation_options.pixels_per_point = self.pixels_per_point(); tessellation_options.aa_size = 1.0 / self.pixels_per_point(); let paint_stats = PaintStats::from_shapes(&shapes); - let clipped_meshes = tessellator::tessellate_shapes( + let clipped_primitives = tessellator::tessellate_shapes( shapes, tessellation_options, self.fonts().font_image_size(), ); - self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes); - clipped_meshes + self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives); + clipped_primitives } // --------------------------------------------------------------------- @@ -1246,3 +1246,10 @@ impl Context { self.set_style(style); } } + +#[cfg(test)] +#[test] +fn context_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index e5f814cc..e5b9c7ea 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -91,9 +91,10 @@ impl Widget for &epaint::stats::PaintStats { shape_path, shape_mesh, shape_vec, + num_callbacks, text_shape_vertices, text_shape_indices, - clipped_meshes, + clipped_primitives, vertices, indices, } = self; @@ -104,6 +105,7 @@ impl Widget for &epaint::stats::PaintStats { label(ui, shape_path, "paths"); label(ui, shape_mesh, "nested meshes"); label(ui, shape_vec, "nested shapes"); + ui.label(format!("{} callbacks", num_callbacks)); ui.add_space(10.0); ui.label("Text shapes:"); @@ -113,7 +115,7 @@ impl Widget for &epaint::stats::PaintStats { ui.add_space(10.0); ui.label("Tessellated (and culled):"); - label(ui, clipped_meshes, "clipped_meshes") + label(ui, clipped_primitives, "clipped_primitives") .on_hover_text("Number of separate clip rectangles"); label(ui, vertices, "vertices"); label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles"); diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 8c05fc1a..07e283e4 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -112,7 +112,7 @@ //! ``` no_run //! # fn handle_platform_output(_: egui::PlatformOutput) {} //! # fn gather_input() -> egui::RawInput { egui::RawInput::default() } -//! # fn paint(textures_detla: egui::TexturesDelta, _: Vec) {} +//! # fn paint(textures_detla: egui::TexturesDelta, _: Vec) {} //! let mut ctx = egui::Context::default(); //! //! // Game loop: @@ -128,8 +128,8 @@ //! }); //! }); //! handle_platform_output(full_output.platform_output); -//! let clipped_meshes = ctx.tessellate(full_output.shapes); // create triangles to paint -//! paint(full_output.textures_delta, clipped_meshes); +//! let clipped_primitives = ctx.tessellate(full_output.shapes); // create triangles to paint +//! paint(full_output.textures_delta, clipped_primitives); //! } //! ``` //! @@ -386,8 +386,8 @@ pub use epaint::{ color, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::TexturesDelta, - AlphaImage, ClippedMesh, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape, Stroke, - TextureHandle, TextureId, + AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape, + Stroke, TextureHandle, TextureId, }; pub mod text { diff --git a/egui_demo_lib/src/apps/demo/app.rs b/egui_demo_lib/src/apps/demo/app.rs index 063f29e7..5544a51a 100644 --- a/egui_demo_lib/src/apps/demo/app.rs +++ b/egui_demo_lib/src/apps/demo/app.rs @@ -19,6 +19,7 @@ impl epi::App for DemoApp { _ctx: &egui::Context, _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>, + _gl: &std::rc::Rc, ) { #[cfg(feature = "persistence")] if let Some(storage) = _storage { diff --git a/egui_demo_lib/src/lib.rs b/egui_demo_lib/src/lib.rs index 96112481..ca15d348 100644 --- a/egui_demo_lib/src/lib.rs +++ b/egui_demo_lib/src/lib.rs @@ -148,8 +148,8 @@ fn test_egui_e2e() { let full_output = ctx.run(raw_input.clone(), |ctx| { demo_windows.ui(ctx); }); - let clipped_meshes = ctx.tessellate(full_output.shapes); - assert!(!clipped_meshes.is_empty()); + let clipped_primitives = ctx.tessellate(full_output.shapes); + assert!(!clipped_primitives.is_empty()); } } @@ -167,8 +167,11 @@ fn test_egui_zero_window_size() { let full_output = ctx.run(raw_input.clone(), |ctx| { demo_windows.ui(ctx); }); - let clipped_meshes = ctx.tessellate(full_output.shapes); - assert!(clipped_meshes.is_empty(), "There should be nothing to show"); + let clipped_primitives = ctx.tessellate(full_output.shapes); + assert!( + clipped_primitives.is_empty(), + "There should be nothing to show" + ); } } diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index ad752886..0a119597 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -47,6 +47,7 @@ impl epi::App for WrapApp { _ctx: &egui::Context, _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>, + _gl: &std::rc::Rc, ) { #[cfg(feature = "persistence")] if let Some(storage) = _storage { diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index db6bf66a..9e6760e5 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -159,12 +159,12 @@ impl EguiGlium { pub fn paint(&mut self, display: &glium::Display, target: &mut T) { let shapes = std::mem::take(&mut self.shapes); let textures_delta = std::mem::take(&mut self.textures_delta); - let clipped_meshes = self.egui_ctx.tessellate(shapes); + let clipped_primitives = self.egui_ctx.tessellate(shapes); self.painter.paint_and_update_textures( display, target, self.egui_ctx.pixels_per_point(), - clipped_meshes, + &clipped_primitives, &textures_delta, ); } diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 5299a11f..11d45fff 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -1,6 +1,8 @@ #![allow(deprecated)] // legacy implement_vertex macro #![allow(semicolon_in_expressions_from_macros)] // glium::program! macro +use egui::epaint::Primitive; + use { ahash::AHashMap, egui::{emath::Rect, epaint::Mesh}, @@ -68,14 +70,14 @@ impl Painter { display: &glium::Display, target: &mut T, pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: &[egui::ClippedPrimitive], textures_delta: &egui::TexturesDelta, ) { for (id, image_delta) in &textures_delta.set { self.set_texture(display, *id, image_delta); } - self.paint_meshes(display, target, pixels_per_point, clipped_meshes); + self.paint_primitives(display, target, pixels_per_point, clipped_primitives); for &id in &textures_delta.free { self.free_texture(id); @@ -85,15 +87,26 @@ impl Painter { /// Main entry-point for painting a frame. /// You should call `target.clear_color(..)` before /// and `target.finish()` after this. - pub fn paint_meshes( + pub fn paint_primitives( &mut self, display: &glium::Display, target: &mut T, pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: &[egui::ClippedPrimitive], ) { - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh); + for egui::ClippedPrimitive { + clip_rect, + primitive, + } in clipped_primitives + { + match primitive { + Primitive::Mesh(mesh) => { + self.paint_mesh(target, display, pixels_per_point, clip_rect, mesh); + } + Primitive::Callback(_) => { + panic!("Custom rendering callbacks are not implemented in egui_glium"); + } + } } } @@ -103,7 +116,7 @@ impl Painter { target: &mut T, display: &glium::Display, pixels_per_point: f32, - clip_rect: Rect, + clip_rect: &Rect, mesh: &Mesh, ) { debug_assert!(mesh.is_valid()); diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index aa6ed648..924210a1 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -69,7 +69,7 @@ tracing = "0.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] egui-winit = { version = "0.17.0", path = "../egui-winit", optional = true, default-features = false, features = [ "dark-light", - "epi", + "epi_backend", ] } glutin = { version = "0.28.0", optional = true } diff --git a/egui_glow/examples/pure_glow.rs b/egui_glow/examples/pure_glow.rs index a6196dbd..870f4121 100644 --- a/egui_glow/examples/pure_glow.rs +++ b/egui_glow/examples/pure_glow.rs @@ -43,8 +43,9 @@ fn main() { let event_loop = glutin::event_loop::EventLoop::with_user_event(); let (gl_window, gl) = create_display(&event_loop); + let gl = std::rc::Rc::new(gl); - let mut egui_glow = egui_glow::EguiGlow::new(gl_window.window(), &gl); + let mut egui_glow = egui_glow::EguiGlow::new(gl_window.window(), gl.clone()); event_loop.run(move |event, _, control_flow| { let mut redraw = || { @@ -78,7 +79,7 @@ fn main() { // draw things behind egui here - egui_glow.paint(gl_window.window(), &gl); + egui_glow.paint(gl_window.window()); // draw things on top of egui here @@ -108,7 +109,7 @@ fn main() { gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } glutin::event::Event::LoopDestroyed => { - egui_glow.destroy(&gl); + egui_glow.destroy(); } _ => (), diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 878a5e1c..57c3f46e 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -54,17 +54,19 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name()); let event_loop = winit::event_loop::EventLoop::with_user_event(); let (gl_window, gl) = create_display(window_builder, &event_loop); + let gl = std::rc::Rc::new(gl); let repaint_signal = std::sync::Arc::new(GlowRepaintSignal(std::sync::Mutex::new( event_loop.create_proxy(), ))); - let mut painter = crate::Painter::new(&gl, None, "") + let mut painter = crate::Painter::new(gl.clone(), None, "") .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); let mut integration = egui_winit::epi::EpiIntegration::new( "egui_glow", painter.max_texture_side(), gl_window.window(), + &gl, repaint_signal, persistence, app, @@ -92,7 +94,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { integration.handle_platform_output(gl_window.window(), platform_output); - let clipped_meshes = integration.egui_ctx.tessellate(shapes); + let clipped_primitives = integration.egui_ctx.tessellate(shapes); // paint: { @@ -104,10 +106,9 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { gl.clear(glow::COLOR_BUFFER_BIT); } painter.paint_and_update_textures( - &gl, gl_window.window().inner_size().into(), integration.egui_ctx.pixels_per_point(), - clipped_meshes, + &clipped_primitives, &textures_delta, ); @@ -153,7 +154,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { } winit::event::Event::LoopDestroyed => { integration.on_exit(gl_window.window()); - painter.destroy(&gl); + painter.destroy(); } winit::event::Event::UserEvent(RequestRepaintEvent) => { gl_window.window().request_redraw(); diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index c8c59b0b..8e68836c 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -1,10 +1,10 @@ #![allow(unsafe_code)] -use std::collections::HashMap; +use std::{collections::HashMap, rc::Rc}; use egui::{ emath::Rect, - epaint::{Color32, Mesh, Vertex}, + epaint::{Color32, Mesh, Primitive, Vertex}, }; use glow::HasContext; use memoffset::offset_of; @@ -19,11 +19,16 @@ pub use glow::Context; const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); -/// OpenGL painter +/// An OpenGL painter using [`glow`]. +/// +/// This is responsible for painting egui and managing egui textures. +/// You can access the underlying [`glow::Context`] with [`Self::gl`]. /// /// This struct must be destroyed with [`Painter::destroy`] before dropping, to ensure OpenGL /// objects have been properly deleted and are not leaked. pub struct Painter { + gl: Rc, + max_texture_side: usize, program: glow::Program, @@ -86,16 +91,16 @@ impl Painter { /// * failed to create postprocess on webgl with `sRGB` support /// * failed to create buffer pub fn new( - gl: &glow::Context, + gl: Rc, pp_fb_extent: Option<[i32; 2]>, shader_prefix: &str, ) -> Result { - check_for_gl_error(gl, "before Painter::new"); + check_for_gl_error(&gl, "before Painter::new"); let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize; - let support_vao = crate::misc_util::supports_vao(gl); - let shader_version = ShaderVersion::get(gl); + let support_vao = crate::misc_util::supports_vao(&gl); + let shader_version = ShaderVersion::get(&gl); let is_webgl_1 = shader_version == ShaderVersion::Es100; let header = shader_version.version(); tracing::debug!("Shader header: {:?}.", header); @@ -110,7 +115,7 @@ impl Painter { // install post process to correct sRGB color: ( Some(PostProcess::new( - gl, + gl.clone(), shader_prefix, support_vao, is_webgl_1, @@ -134,7 +139,7 @@ impl Painter { unsafe { let vert = compile_shader( - gl, + &gl, glow::VERTEX_SHADER, &format!( "{}\n{}\n{}\n{}", @@ -145,7 +150,7 @@ impl Painter { ), )?; let frag = compile_shader( - gl, + &gl, glow::FRAGMENT_SHADER, &format!( "{}\n{}\n{}\n{}\n{}", @@ -156,7 +161,7 @@ impl Painter { FRAG_SRC ), )?; - let program = link_program(gl, [vert, frag].iter())?; + let program = link_program(&gl, [vert, frag].iter())?; gl.detach_shader(program, vert); gl.detach_shader(program, frag); gl.delete_shader(vert); @@ -170,12 +175,12 @@ impl Painter { let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap(); let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap(); let mut vertex_array = if support_vao { - crate::misc_util::VAO::native(gl) + crate::misc_util::VAO::native(&gl) } else { crate::misc_util::VAO::emulated() }; - vertex_array.bind_vertex_array(gl); - vertex_array.bind_buffer(gl, &vertex_buffer); + vertex_array.bind_vertex_array(&gl); + vertex_array.bind_buffer(&gl, &vertex_buffer); let stride = std::mem::size_of::() as i32; let position_buffer_info = vao_emulate::BufferInfo { location: a_pos_loc, @@ -201,12 +206,13 @@ impl Painter { stride, offset: offset_of!(Vertex, color) as i32, }; - vertex_array.add_new_attribute(gl, position_buffer_info); - vertex_array.add_new_attribute(gl, tex_coord_buffer_info); - vertex_array.add_new_attribute(gl, color_buffer_info); - check_for_gl_error(gl, "after Painter::new"); + vertex_array.add_new_attribute(&gl, position_buffer_info); + vertex_array.add_new_attribute(&gl, tex_coord_buffer_info); + vertex_array.add_new_attribute(&gl, color_buffer_info); + check_for_gl_error(&gl, "after Painter::new"); Ok(Painter { + gl, max_texture_side, program, u_screen_size, @@ -228,6 +234,11 @@ impl Painter { } } + /// Access the shared glow context. + pub fn gl(&self) -> &std::rc::Rc { + &self.gl + } + pub fn max_texture_side(&self) -> usize { self.max_texture_side } @@ -235,16 +246,15 @@ impl Painter { unsafe fn prepare_painting( &mut self, [width_in_pixels, height_in_pixels]: [u32; 2], - gl: &glow::Context, pixels_per_point: f32, ) -> (u32, u32) { - gl.enable(glow::SCISSOR_TEST); + self.gl.enable(glow::SCISSOR_TEST); // egui outputs mesh in both winding orders - gl.disable(glow::CULL_FACE); + self.gl.disable(glow::CULL_FACE); - gl.enable(glow::BLEND); - gl.blend_equation(glow::FUNC_ADD); - gl.blend_func_separate( + self.gl.enable(glow::BLEND); + self.gl.blend_equation(glow::FUNC_ADD); + self.gl.blend_func_separate( // egui outputs colors with premultiplied alpha: glow::ONE, glow::ONE_MINUS_SRC_ALPHA, @@ -257,35 +267,37 @@ impl Painter { let width_in_points = width_in_pixels as f32 / pixels_per_point; let height_in_points = height_in_pixels as f32 / pixels_per_point; - gl.viewport(0, 0, width_in_pixels as i32, height_in_pixels as i32); - gl.use_program(Some(self.program)); + self.gl + .viewport(0, 0, width_in_pixels as i32, height_in_pixels as i32); + self.gl.use_program(Some(self.program)); - gl.uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points); - gl.uniform_1_i32(Some(&self.u_sampler), 0); - gl.active_texture(glow::TEXTURE0); - self.vertex_array.bind_vertex_array(gl); + self.gl + .uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points); + self.gl.uniform_1_i32(Some(&self.u_sampler), 0); + self.gl.active_texture(glow::TEXTURE0); + self.vertex_array.bind_vertex_array(&self.gl); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); + self.gl + .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); (width_in_pixels, height_in_pixels) } pub fn paint_and_update_textures( &mut self, - gl: &glow::Context, inner_size: [u32; 2], pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: &[egui::ClippedPrimitive], textures_delta: &egui::TexturesDelta, ) { for (id, image_delta) in &textures_delta.set { - self.set_texture(gl, *id, image_delta); + self.set_texture(*id, image_delta); } - self.paint_meshes(gl, inner_size, pixels_per_point, clipped_meshes); + self.paint_primitives(inner_size, pixels_per_point, clipped_primitives); for &id in &textures_delta.free { - self.free_texture(gl, id); + self.free_texture(id); } } @@ -308,92 +320,107 @@ impl Painter { /// /// Please be mindful of these effects when integrating into your program, and also be mindful /// of the effects your program might have on this code. Look at the source if in doubt. - pub fn paint_meshes( + pub fn paint_primitives( &mut self, - gl: &glow::Context, inner_size: [u32; 2], pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: &[egui::ClippedPrimitive], ) { self.assert_not_destroyed(); 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); + post_process.begin(inner_size[0] as i32, inner_size[1] as i32); } } - let size_in_pixels = unsafe { self.prepare_painting(inner_size, gl, pixels_per_point) }; - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - self.paint_mesh(gl, size_in_pixels, pixels_per_point, clip_rect, &mesh); + let size_in_pixels = unsafe { self.prepare_painting(inner_size, pixels_per_point) }; + + for egui::ClippedPrimitive { + clip_rect, + primitive, + } in clipped_primitives + { + set_clip_rect(&self.gl, size_in_pixels, pixels_per_point, *clip_rect); + + match primitive { + Primitive::Mesh(mesh) => { + self.paint_mesh(mesh); + } + Primitive::Callback(callback) => { + if callback.rect.is_positive() { + // Transform callback rect to physical pixels: + let rect_min_x = pixels_per_point * callback.rect.min.x; + let rect_min_y = pixels_per_point * callback.rect.min.y; + let rect_max_x = pixels_per_point * callback.rect.max.x; + let rect_max_y = pixels_per_point * callback.rect.max.y; + + let rect_min_x = rect_min_x.round() as i32; + let rect_min_y = rect_min_y.round() as i32; + let rect_max_x = rect_max_x.round() as i32; + let rect_max_y = rect_max_y.round() as i32; + + unsafe { + self.gl.viewport( + rect_min_x, + size_in_pixels.1 as i32 - rect_max_y, + rect_max_x - rect_min_x, + rect_max_y - rect_min_y, + ); + } + + callback.call(self); + + // Restore state: + unsafe { + if let Some(ref mut post_process) = self.post_process { + post_process.bind(); + } + self.prepare_painting(inner_size, pixels_per_point) + }; + } + } + } } unsafe { - self.vertex_array.unbind_vertex_array(gl); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); + self.vertex_array.unbind_vertex_array(&self.gl); + self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); if let Some(ref post_process) = self.post_process { - post_process.end(gl); + post_process.end(); } - gl.disable(glow::SCISSOR_TEST); + self.gl.disable(glow::SCISSOR_TEST); - check_for_gl_error(gl, "painting"); + check_for_gl_error(&self.gl, "painting"); } } #[inline(never)] // Easier profiling - fn paint_mesh( - &mut self, - gl: &glow::Context, - size_in_pixels: (u32, u32), - pixels_per_point: f32, - clip_rect: Rect, - mesh: &Mesh, - ) { + fn paint_mesh(&mut self, mesh: &Mesh) { debug_assert!(mesh.is_valid()); if let Some(texture) = self.get_texture(mesh.texture_id) { unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); - gl.buffer_data_u8_slice( + self.gl + .bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + self.gl.buffer_data_u8_slice( glow::ARRAY_BUFFER, bytemuck::cast_slice(&mesh.vertices), glow::STREAM_DRAW, ); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); - gl.buffer_data_u8_slice( + self.gl + .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); + self.gl.buffer_data_u8_slice( glow::ELEMENT_ARRAY_BUFFER, bytemuck::cast_slice(&mesh.indices), glow::STREAM_DRAW, ); - gl.bind_texture(glow::TEXTURE_2D, Some(texture)); + self.gl.bind_texture(glow::TEXTURE_2D, Some(texture)); } - // Transform clip rect to physical pixels: - let clip_min_x = pixels_per_point * clip_rect.min.x; - let clip_min_y = pixels_per_point * clip_rect.min.y; - let clip_max_x = pixels_per_point * clip_rect.max.x; - let clip_max_y = pixels_per_point * clip_rect.max.y; - - // Make sure clip rect can fit within a `u32`: - let clip_min_x = clip_min_x.clamp(0.0, size_in_pixels.0 as f32); - let clip_min_y = clip_min_y.clamp(0.0, size_in_pixels.1 as f32); - let clip_max_x = clip_max_x.clamp(clip_min_x, size_in_pixels.0 as f32); - let clip_max_y = clip_max_y.clamp(clip_min_y, size_in_pixels.1 as f32); - - let clip_min_x = clip_min_x.round() as i32; - let clip_min_y = clip_min_y.round() as i32; - let clip_max_x = clip_max_x.round() as i32; - let clip_max_y = clip_max_y.round() as i32; - unsafe { - gl.scissor( - clip_min_x, - size_in_pixels.1 as i32 - clip_max_y, - clip_max_x - clip_min_x, - clip_max_y - clip_min_y, - ); - gl.draw_elements( + self.gl.draw_elements( glow::TRIANGLES, mesh.indices.len() as i32, glow::UNSIGNED_INT, @@ -411,20 +438,15 @@ impl Painter { // ------------------------------------------------------------------------ - pub fn set_texture( - &mut self, - gl: &glow::Context, - tex_id: egui::TextureId, - delta: &egui::epaint::ImageDelta, - ) { + pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { self.assert_not_destroyed(); let glow_texture = *self .textures .entry(tex_id) - .or_insert_with(|| unsafe { gl.create_texture().unwrap() }); + .or_insert_with(|| unsafe { self.gl.create_texture().unwrap() }); unsafe { - gl.bind_texture(glow::TEXTURE_2D, Some(glow_texture)); + self.gl.bind_texture(glow::TEXTURE_2D, Some(glow_texture)); } match &delta.image { @@ -437,7 +459,7 @@ impl Painter { let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref()); - self.upload_texture_srgb(gl, delta.pos, image.size, data); + self.upload_texture_srgb(delta.pos, image.size, data); } egui::ImageData::Alpha(image) => { assert_eq!( @@ -456,43 +478,37 @@ impl Painter { .flat_map(|a| a.to_array()) .collect(); - self.upload_texture_srgb(gl, delta.pos, image.size, &data); + self.upload_texture_srgb(delta.pos, image.size, &data); } }; } - fn upload_texture_srgb( - &mut self, - gl: &glow::Context, - pos: Option<[usize; 2]>, - [w, h]: [usize; 2], - data: &[u8], - ) { + fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) { assert_eq!(data.len(), w * h * 4); assert!(w >= 1 && h >= 1); unsafe { - gl.tex_parameter_i32( + self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, self.texture_filter.glow_code() as i32, ); - gl.tex_parameter_i32( + self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, self.texture_filter.glow_code() as i32, ); - gl.tex_parameter_i32( + self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32, ); - gl.tex_parameter_i32( + self.gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32, ); - check_for_gl_error(gl, "tex_parameter"); + check_for_gl_error(&self.gl, "tex_parameter"); let (internal_format, src_format) = if self.is_webgl_1 { let format = if self.srgb_support { @@ -505,11 +521,11 @@ impl Painter { (glow::SRGB8_ALPHA8, glow::RGBA) }; - gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); + self.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); let level = 0; if let Some([x, y]) = pos { - gl.tex_sub_image_2d( + self.gl.tex_sub_image_2d( glow::TEXTURE_2D, level, x as _, @@ -520,10 +536,10 @@ impl Painter { glow::UNSIGNED_BYTE, glow::PixelUnpackData::Slice(data), ); - check_for_gl_error(gl, "tex_sub_image_2d"); + check_for_gl_error(&self.gl, "tex_sub_image_2d"); } else { let border = 0; - gl.tex_image_2d( + self.gl.tex_image_2d( glow::TEXTURE_2D, level, internal_format as _, @@ -534,42 +550,42 @@ impl Painter { glow::UNSIGNED_BYTE, Some(data), ); - check_for_gl_error(gl, "tex_image_2d"); + check_for_gl_error(&self.gl, "tex_image_2d"); } } } - pub fn free_texture(&mut self, gl: &glow::Context, tex_id: egui::TextureId) { + pub fn free_texture(&mut self, tex_id: egui::TextureId) { if let Some(old_tex) = self.textures.remove(&tex_id) { - unsafe { gl.delete_texture(old_tex) }; + unsafe { self.gl.delete_texture(old_tex) }; } } - fn get_texture(&self, texture_id: egui::TextureId) -> Option { + /// Get the [`glow::Texture`] bound to a [`egui::TextureId`]. + pub fn get_texture(&self, texture_id: egui::TextureId) -> Option { self.textures.get(&texture_id).copied() } - unsafe fn destroy_gl(&self, gl: &glow::Context) { - gl.delete_program(self.program); + unsafe fn destroy_gl(&self) { + self.gl.delete_program(self.program); for tex in self.textures.values() { - gl.delete_texture(*tex); + self.gl.delete_texture(*tex); } - gl.delete_buffer(self.vertex_buffer); - gl.delete_buffer(self.element_array_buffer); + self.gl.delete_buffer(self.vertex_buffer); + self.gl.delete_buffer(self.element_array_buffer); for t in &self.textures_to_destroy { - gl.delete_texture(*t); + self.gl.delete_texture(*t); } } - /// This function must be called before Painter is dropped, as Painter has some OpenGL objects + /// This function must be called before [`Painter`] is dropped, as [`Painter`] has some OpenGL objects /// that should be deleted. - - pub fn destroy(&mut self, gl: &glow::Context) { + pub fn destroy(&mut self) { if !self.destroyed { unsafe { - self.destroy_gl(gl); + self.destroy_gl(); if let Some(ref post_process) = self.post_process { - post_process.destroy(gl); + post_process.destroy(); } } self.destroyed = true; @@ -626,3 +642,36 @@ impl epi::NativeTexture for Painter { } } } + +fn set_clip_rect( + gl: &glow::Context, + size_in_pixels: (u32, u32), + pixels_per_point: f32, + clip_rect: Rect, +) { + // Transform clip rect to physical pixels: + let clip_min_x = pixels_per_point * clip_rect.min.x; + let clip_min_y = pixels_per_point * clip_rect.min.y; + let clip_max_x = pixels_per_point * clip_rect.max.x; + let clip_max_y = pixels_per_point * clip_rect.max.y; + + // Make sure clip rect can fit within a `u32`: + let clip_min_x = clip_min_x.clamp(0.0, size_in_pixels.0 as f32); + let clip_min_y = clip_min_y.clamp(0.0, size_in_pixels.1 as f32); + let clip_max_x = clip_max_x.clamp(clip_min_x, size_in_pixels.0 as f32); + let clip_max_y = clip_max_y.clamp(clip_min_y, size_in_pixels.1 as f32); + + let clip_min_x = clip_min_x.round() as i32; + let clip_min_y = clip_min_y.round() as i32; + let clip_max_x = clip_max_x.round() as i32; + let clip_max_y = clip_max_y.round() as i32; + + unsafe { + gl.scissor( + clip_min_x, + size_in_pixels.1 as i32 - clip_max_y, + clip_max_x - clip_min_x, + clip_max_y - clip_min_y, + ); + } +} diff --git a/egui_glow/src/post_process.rs b/egui_glow/src/post_process.rs index e12c1a0e..0706b2a6 100644 --- a/egui_glow/src/post_process.rs +++ b/egui_glow/src/post_process.rs @@ -6,6 +6,7 @@ use glow::HasContext; /// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB` /// in a separate "post processing" step pub(crate) struct PostProcess { + gl: std::rc::Rc, pos_buffer: glow::Buffer, index_buffer: glow::Buffer, vertex_array: crate::misc_util::VAO, @@ -18,7 +19,7 @@ pub(crate) struct PostProcess { impl PostProcess { pub(crate) unsafe fn new( - gl: &glow::Context, + gl: std::rc::Rc, shader_prefix: &str, need_to_emulate_vao: bool, is_webgl_1: bool, @@ -76,7 +77,7 @@ impl PostProcess { glow::UNSIGNED_BYTE, None, ); - check_for_gl_error(gl, "post process texture initialization"); + check_for_gl_error(&gl, "post process texture initialization"); gl.framebuffer_texture_2d( glow::FRAMEBUFFER, @@ -89,7 +90,7 @@ impl PostProcess { gl.bind_framebuffer(glow::FRAMEBUFFER, None); let vert_shader = compile_shader( - gl, + &gl, glow::VERTEX_SHADER, &format!( "{}\n{}", @@ -98,7 +99,7 @@ impl PostProcess { ), )?; let frag_shader = compile_shader( - gl, + &gl, glow::FRAGMENT_SHADER, &format!( "{}\n{}", @@ -106,7 +107,7 @@ impl PostProcess { include_str!("shader/post_fragment_100es.glsl") ), )?; - let program = link_program(gl, [vert_shader, frag_shader].iter())?; + let program = link_program(&gl, [vert_shader, frag_shader].iter())?; let positions = vec![0.0f32, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]; @@ -126,10 +127,10 @@ impl PostProcess { let mut vertex_array = if need_to_emulate_vao { crate::misc_util::VAO::emulated() } else { - crate::misc_util::VAO::native(gl) + crate::misc_util::VAO::native(&gl) }; - vertex_array.bind_vertex_array(gl); - vertex_array.bind_buffer(gl, &pos_buffer); + vertex_array.bind_vertex_array(&gl); + vertex_array.bind_buffer(&gl, &pos_buffer); let buffer_info_a_pos = BufferInfo { location: a_pos_loc, vector_size: 2, @@ -138,16 +139,17 @@ impl PostProcess { stride: 0, offset: 0, }; - vertex_array.add_new_attribute(gl, buffer_info_a_pos); + vertex_array.add_new_attribute(&gl, buffer_info_a_pos); let index_buffer = gl.create_buffer()?; gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW); gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - check_for_gl_error(gl, "post process initialization"); + check_for_gl_error(&gl, "post process initialization"); Ok(PostProcess { + gl, pos_buffer, index_buffer, vertex_array, @@ -159,17 +161,17 @@ impl PostProcess { }) } - pub(crate) unsafe fn begin(&mut self, gl: &glow::Context, width: i32, height: i32) { + pub(crate) unsafe fn begin(&mut self, width: i32, height: i32) { if (width, height) != self.texture_size { - gl.bind_texture(glow::TEXTURE_2D, Some(self.texture)); - gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); + self.gl.bind_texture(glow::TEXTURE_2D, Some(self.texture)); + self.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); let (internal_format, format) = if self.is_webgl_1 { (glow::SRGB_ALPHA, glow::SRGB_ALPHA) } else { (glow::SRGB8_ALPHA8, glow::RGBA) }; - gl.tex_image_2d( + self.gl.tex_image_2d( glow::TEXTURE_2D, 0, internal_format as i32, @@ -181,40 +183,49 @@ impl PostProcess { None, ); - gl.bind_texture(glow::TEXTURE_2D, None); + self.gl.bind_texture(glow::TEXTURE_2D, None); self.texture_size = (width, height); } - gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); - gl.clear_color(0.0, 0.0, 0.0, 0.0); - gl.clear(glow::COLOR_BUFFER_BIT); + self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); + self.gl.clear_color(0.0, 0.0, 0.0, 0.0); + self.gl.clear(glow::COLOR_BUFFER_BIT); } - pub(crate) unsafe fn end(&self, gl: &glow::Context) { - gl.bind_framebuffer(glow::FRAMEBUFFER, None); - gl.disable(glow::SCISSOR_TEST); - - gl.use_program(Some(self.program)); - - gl.active_texture(glow::TEXTURE0); - gl.bind_texture(glow::TEXTURE_2D, Some(self.texture)); - let u_sampler_loc = gl.get_uniform_location(self.program, "u_sampler").unwrap(); - gl.uniform_1_i32(Some(&u_sampler_loc), 0); - self.vertex_array.bind_vertex_array(gl); - - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); - gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0); - self.vertex_array.unbind_vertex_array(gl); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - gl.bind_texture(glow::TEXTURE_2D, None); - gl.use_program(None); + pub(crate) unsafe fn bind(&self) { + self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); } - pub(crate) unsafe fn destroy(&self, gl: &glow::Context) { - gl.delete_buffer(self.pos_buffer); - gl.delete_buffer(self.index_buffer); - gl.delete_program(self.program); - gl.delete_framebuffer(self.fbo); - gl.delete_texture(self.texture); + pub(crate) unsafe fn end(&self) { + self.gl.bind_framebuffer(glow::FRAMEBUFFER, None); + self.gl.disable(glow::SCISSOR_TEST); + + self.gl.use_program(Some(self.program)); + + self.gl.active_texture(glow::TEXTURE0); + self.gl.bind_texture(glow::TEXTURE_2D, Some(self.texture)); + let u_sampler_loc = self + .gl + .get_uniform_location(self.program, "u_sampler") + .unwrap(); + self.gl.uniform_1_i32(Some(&u_sampler_loc), 0); + self.vertex_array.bind_vertex_array(&self.gl); + + self.gl + .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); + self.gl + .draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0); + self.vertex_array.unbind_vertex_array(&self.gl); + self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); + self.gl.bind_texture(glow::TEXTURE_2D, None); + self.gl.use_program(None); + } + + pub(crate) unsafe fn destroy(&self) { + self.gl.delete_buffer(self.pos_buffer); + self.gl.delete_buffer(self.index_buffer); + self.gl.delete_program(self.program); + self.gl.delete_framebuffer(self.fbo); + self.gl.delete_texture(self.texture); } } diff --git a/egui_glow/src/winit.rs b/egui_glow/src/winit.rs index 8be08c88..547e9065 100644 --- a/egui_glow/src/winit.rs +++ b/egui_glow/src/winit.rs @@ -12,7 +12,7 @@ pub struct EguiGlow { } impl EguiGlow { - pub fn new(window: &winit::window::Window, gl: &glow::Context) -> Self { + pub fn new(window: &winit::window::Window, gl: std::rc::Rc) -> Self { let painter = crate::Painter::new(gl, None, "") .map_err(|error| { tracing::error!("error occurred in initializing painter:\n{}", error); @@ -63,30 +63,29 @@ impl EguiGlow { } /// Paint the results of the last call to [`Self::run`]. - pub fn paint(&mut self, window: &winit::window::Window, gl: &glow::Context) { + pub fn paint(&mut self, window: &winit::window::Window) { let shapes = std::mem::take(&mut self.shapes); let mut textures_delta = std::mem::take(&mut self.textures_delta); for (id, image_delta) in textures_delta.set { - self.painter.set_texture(gl, id, &image_delta); + self.painter.set_texture(id, &image_delta); } - let clipped_meshes = self.egui_ctx.tessellate(shapes); + let clipped_primitives = self.egui_ctx.tessellate(shapes); let dimensions: [u32; 2] = window.inner_size().into(); - self.painter.paint_meshes( - gl, + self.painter.paint_primitives( dimensions, self.egui_ctx.pixels_per_point(), - clipped_meshes, + &clipped_primitives, ); for id in textures_delta.free.drain(..) { - self.painter.free_texture(gl, id); + self.painter.free_texture(id); } } /// Call to release the allocated graphics resources. - pub fn destroy(&mut self, gl: &glow::Context) { - self.painter.destroy(gl); + pub fn destroy(&mut self) { + self.painter.destroy(); } } diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index ec11a18d..50313303 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -1,18 +1,10 @@ -use crate::*; +use crate::{glow_wrapping::WrappedGlowPainter, *}; use egui::TexturesDelta; pub use egui::{pos2, Color32}; // ---------------------------------------------------------------------------- -fn create_painter(canvas_id: &str) -> Result, JsValue> { - Ok(Box::new( - crate::glow_wrapping::WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?, - )) -} - -// ---------------------------------------------------------------------------- - /// Data gathered between frames. #[derive(Default)] pub struct WebInput { @@ -140,7 +132,7 @@ fn test_parse_query() { pub struct AppRunner { pub(crate) frame: epi::Frame, egui_ctx: egui::Context, - painter: Box, + painter: WrappedGlowPainter, pub(crate) input: WebInput, app: Box, pub(crate) needs_repaint: std::sync::Arc, @@ -154,7 +146,7 @@ pub struct AppRunner { impl AppRunner { pub fn new(canvas_id: &str, app: Box) -> Result { - let painter = create_painter(canvas_id)?; + let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?; let prefer_dark_mode = crate::prefer_dark_mode(); @@ -162,7 +154,7 @@ impl AppRunner { let frame = epi::Frame::new(epi::backend::FrameData { info: epi::IntegrationInfo { - name: painter.name(), + name: "egui_web", web_info: Some(epi::WebInfo { location: web_location(), }), @@ -201,11 +193,10 @@ impl AppRunner { runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side()); - { - runner - .app - .setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage)); - } + let gl = runner.painter.painter.gl(); + runner + .app + .setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage), gl); Ok(runner) } @@ -245,7 +236,7 @@ impl AppRunner { /// Returns `true` if egui requests a repaint. /// /// Call [`Self::paint`] later to paint - pub fn logic(&mut self) -> Result<(bool, Vec), JsValue> { + pub fn logic(&mut self) -> Result<(bool, Vec), JsValue> { let frame_start = now_sec(); resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points()); @@ -264,7 +255,7 @@ impl AppRunner { self.handle_platform_output(platform_output); self.textures_delta.append(textures_delta); - let clipped_meshes = self.egui_ctx.tessellate(shapes); + let clipped_primitives = self.egui_ctx.tessellate(shapes); { let app_output = self.frame.take_app_output(); @@ -278,17 +269,17 @@ impl AppRunner { } self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32); - Ok((needs_repaint, clipped_meshes)) + Ok((needs_repaint, clipped_primitives)) } /// Paint the results of the last call to [`Self::logic`]. - pub fn paint(&mut self, clipped_meshes: Vec) -> Result<(), JsValue> { + pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> { let textures_delta = std::mem::take(&mut self.textures_delta); self.painter.clear(self.app.clear_color()); self.painter.paint_and_update_textures( - clipped_meshes, + clipped_primitives, self.egui_ctx.pixels_per_point(), &textures_delta, )?; diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index c21a38b7..c589aad1 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -1,4 +1,4 @@ -use egui::{ClippedMesh, Rgba}; +use egui::{ClippedPrimitive, Rgba}; use egui_glow::glow; use wasm_bindgen::JsCast; use wasm_bindgen::JsValue; @@ -7,7 +7,6 @@ use web_sys::HtmlCanvasElement; use web_sys::{WebGl2RenderingContext, WebGlRenderingContext}; pub(crate) struct WrappedGlowPainter { - pub(crate) glow_ctx: glow::Context, pub(crate) canvas: HtmlCanvasElement, pub(crate) canvas_id: String, pub(crate) painter: egui_glow::Painter, @@ -17,14 +16,14 @@ impl WrappedGlowPainter { pub fn new(canvas_id: &str) -> Result { let canvas = crate::canvas_element_or_die(canvas_id); - let (glow_ctx, shader_prefix) = init_glow_context_from_canvas(&canvas)?; + let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas)?; + let gl = std::rc::Rc::new(gl); let dimension = [canvas.width() as i32, canvas.height() as i32]; - let painter = egui_glow::Painter::new(&glow_ctx, Some(dimension), shader_prefix) + let painter = egui_glow::Painter::new(gl, Some(dimension), shader_prefix) .map_err(|error| format!("Error starting glow painter: {}", error))?; Ok(Self { - glow_ctx, canvas, canvas_id: canvas_id.to_owned(), painter, @@ -32,44 +31,55 @@ impl WrappedGlowPainter { } } -impl crate::WebPainter for WrappedGlowPainter { - fn name(&self) -> &'static str { - "egui_web" - } - - fn max_texture_side(&self) -> usize { +impl WrappedGlowPainter { + pub fn max_texture_side(&self) -> usize { self.painter.max_texture_side() } - fn canvas_id(&self) -> &str { + pub fn canvas_id(&self) -> &str { &self.canvas_id } - fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { - self.painter.set_texture(&self.glow_ctx, tex_id, delta); + pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) { + self.painter.set_texture(tex_id, delta); } - fn free_texture(&mut self, tex_id: egui::TextureId) { - self.painter.free_texture(&self.glow_ctx, tex_id); + pub fn free_texture(&mut self, tex_id: egui::TextureId) { + self.painter.free_texture(tex_id); } - fn clear(&mut self, clear_color: Rgba) { + pub fn clear(&mut self, clear_color: Rgba) { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; - egui_glow::painter::clear(&self.glow_ctx, canvas_dimension, clear_color) + egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color) } - fn paint_meshes( + pub fn paint_primitives( &mut self, - clipped_meshes: Vec, + clipped_primitives: &[ClippedPrimitive], pixels_per_point: f32, ) -> Result<(), JsValue> { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; - self.painter.paint_meshes( - &self.glow_ctx, - canvas_dimension, - pixels_per_point, - clipped_meshes, - ); + self.painter + .paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives); + Ok(()) + } + + pub fn paint_and_update_textures( + &mut self, + clipped_primitives: &[egui::ClippedPrimitive], + pixels_per_point: f32, + textures_delta: &egui::TexturesDelta, + ) -> Result<(), JsValue> { + for (id, image_delta) in &textures_delta.set { + self.set_texture(*id, image_delta); + } + + self.paint_primitives(clipped_primitives, pixels_per_point)?; + + for &id in &textures_delta.free { + self.free_texture(id); + } + Ok(()) } } @@ -115,9 +125,9 @@ fn init_webgl1(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static st "" }; - let glow_ctx = glow::Context::from_webgl1_context(gl1_ctx); + let gl = glow::Context::from_webgl1_context(gl1_ctx); - Some((glow_ctx, shader_prefix)) + Some((gl, shader_prefix)) } fn init_webgl2(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static str)> { @@ -131,10 +141,10 @@ fn init_webgl2(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static st let gl2_ctx = gl2_ctx .dyn_into::() .unwrap(); - let glow_ctx = glow::Context::from_webgl2_context(gl2_ctx); + let gl = glow::Context::from_webgl2_context(gl2_ctx); let shader_prefix = ""; - Some((glow_ctx, shader_prefix)) + Some((gl, shader_prefix)) } trait DummyWebGLConstructor { diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 4177ee8b..062c9ac0 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -17,7 +17,6 @@ pub mod backend; mod glow_wrapping; mod input; -mod painter; pub mod screen_reader; mod text_agent; @@ -28,7 +27,6 @@ pub use wasm_bindgen; pub use web_sys; use input::*; -pub use painter::WebPainter; use web_sys::EventTarget; use std::collections::BTreeMap; @@ -349,8 +347,8 @@ fn paint_and_schedule(runner_ref: &AppRunnerRef, panicked: Arc) -> R fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.lock(); if runner_lock.needs_repaint.fetch_and_clear() { - let (needs_repaint, clipped_meshes) = runner_lock.logic()?; - runner_lock.paint(clipped_meshes)?; + let (needs_repaint, clipped_primitives) = runner_lock.logic()?; + runner_lock.paint(&clipped_primitives)?; if needs_repaint { runner_lock.needs_repaint.set_true(); } diff --git a/egui_web/src/painter.rs b/egui_web/src/painter.rs deleted file mode 100644 index 11fcd4dd..00000000 --- a/egui_web/src/painter.rs +++ /dev/null @@ -1,43 +0,0 @@ -use wasm_bindgen::prelude::JsValue; - -/// What is needed to paint egui. -pub trait WebPainter { - fn name(&self) -> &'static str; - - /// Max size of one side of a texture. - fn max_texture_side(&self) -> usize; - - /// id of the canvas html element containing the rendering - fn canvas_id(&self) -> &str; - - fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta); - - fn free_texture(&mut self, tex_id: egui::TextureId); - - fn clear(&mut self, clear_color: egui::Rgba); - - fn paint_meshes( - &mut self, - clipped_meshes: Vec, - pixels_per_point: f32, - ) -> Result<(), JsValue>; - - fn paint_and_update_textures( - &mut self, - clipped_meshes: Vec, - pixels_per_point: f32, - textures_delta: &egui::TexturesDelta, - ) -> Result<(), JsValue> { - for (id, image_delta) in &textures_delta.set { - self.set_texture(*id, image_delta); - } - - self.paint_meshes(clipped_meshes, pixels_per_point)?; - - for &id in &textures_delta.free { - self.free_texture(id); - } - - Ok(()) - } -} diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index eb83894c..9a300704 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the epaint crate will be documented in this file. ## Unreleased +* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). ## 0.17.0 - 2022-02-22 diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index 55e6b7b7..e75d3026 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -110,7 +110,7 @@ pub use { image::{AlphaImage, ColorImage, ImageData, ImageDelta}, mesh::{Mesh, Mesh16, Vertex}, shadow::Shadow, - shape::{CircleShape, PathShape, RectShape, Rounding, Shape, TextShape}, + shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape}, stats::PaintStats, stroke::Stroke, tessellator::{tessellate_shapes, TessellationOptions, Tessellator}, @@ -166,18 +166,24 @@ pub struct ClippedShape( pub Shape, ); -/// A [`Mesh`] within a clip rectangle. +/// A [`Mesh`] or [`PaintCallback`] within a clip rectangle. /// /// Everything is using logical points. #[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct ClippedMesh( +pub struct ClippedPrimitive { /// Clip / scissor rectangle. /// Only show the part of the [`Mesh`] that falls within this. - pub emath::Rect, - /// The shape - pub Mesh, -); + pub clip_rect: emath::Rect, + /// What to paint - either a [`Mesh`] or a [`PaintCallback`]. + pub primitive: Primitive, +} + +/// A rendering primitive - either a [`Mesh`] or a [`PaintCallback`]. +#[derive(Clone, Debug)] +pub enum Primitive { + Mesh(Mesh), + Callback(PaintCallback), +} // ---------------------------------------------------------------------------- diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 40c9b3fe..1d846fb2 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -1,10 +1,13 @@ +//! The different shapes that can be painted. + use crate::{ text::{FontId, Fonts, Galley}, Color32, Mesh, Stroke, }; -use crate::{CubicBezierShape, QuadraticBezierShape}; use emath::*; +pub use crate::{CubicBezierShape, QuadraticBezierShape}; + /// A paint primitive such as a circle or a piece of text. /// Coordinates are all screen space points (not physical pixels). #[must_use = "Add a Shape to a Painter"] @@ -29,6 +32,16 @@ pub enum Shape { Mesh(Mesh), QuadraticBezier(QuadraticBezierShape), CubicBezier(CubicBezierShape), + + /// Backend-specific painting. + Callback(PaintCallback), +} + +#[cfg(test)] +#[test] +fn shape_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); } impl From> for Shape { @@ -196,6 +209,7 @@ impl Shape { Self::Mesh(mesh) => mesh.calc_bounds(), Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(), Self::CubicBezier(bezier) => bezier.visual_bounding_rect(), + Self::Callback(custom) => custom.rect, } } } @@ -252,6 +266,9 @@ impl Shape { *p += delta; } } + Shape::Callback(shape) => { + shape.rect = shape.rect.translate(delta); + } } } } @@ -616,3 +633,57 @@ fn dashes_from_line( position_on_segment -= segment_length; }); } + +// ---------------------------------------------------------------------------- + +/// If you want to paint some 3D shapes inside an egui region, you can use this. +/// +/// This is advanced usage, and is backend specific. +#[derive(Clone)] +pub struct PaintCallback { + /// Where to paint. + pub rect: Rect, + + /// Paint something custom using. + /// + /// The argument is the render context, and what it contains depends on the backend. + /// In `eframe` it will be `egui_glow::Painter`. + /// + /// The rendering backend is responsible for first setting the active viewport to [`Self::rect`]. + /// The rendering backend is also responsible for restoring any state it needs, + /// such as the bound shader program and vertex array. + pub callback: std::sync::Arc, +} + +impl PaintCallback { + #[inline] + pub fn call(&self, render_ctx: &dyn std::any::Any) { + (self.callback)(render_ctx); + } +} + +impl std::fmt::Debug for PaintCallback { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CustomShape") + .field("rect", &self.rect) + .finish_non_exhaustive() + } +} + +impl std::cmp::PartialEq for PaintCallback { + fn eq(&self, other: &Self) -> bool { + // As I understand it, the problem this clippy is trying to protect against + // can only happen if we do dynamic casts back and forth on the pointers, and we don't do that. + #[allow(clippy::vtable_address_comparisons)] + { + self.rect.eq(&other.rect) && std::sync::Arc::ptr_eq(&self.callback, &other.callback) + } + } +} + +impl From for Shape { + #[inline(always)] + fn from(shape: PaintCallback) -> Self { + Self::Callback(shape) + } +} diff --git a/epaint/src/shape_transform.rs b/epaint/src/shape_transform.rs index c033a8ee..a4c060db 100644 --- a/epaint/src/shape_transform.rs +++ b/epaint/src/shape_transform.rs @@ -51,5 +51,8 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { adjust_color(&mut bezier.fill); adjust_color(&mut bezier.stroke.color); } + Shape::Callback(_) => { + // Can't tint user callback code + } } } diff --git a/epaint/src/stats.rs b/epaint/src/stats.rs index 322c0ddf..2316d905 100644 --- a/epaint/src/stats.rs +++ b/epaint/src/stats.rs @@ -162,12 +162,13 @@ pub struct PaintStats { pub shape_path: AllocInfo, pub shape_mesh: AllocInfo, pub shape_vec: AllocInfo, + pub num_callbacks: usize, pub text_shape_vertices: AllocInfo, pub text_shape_indices: AllocInfo, /// Number of separate clip rectangles - pub clipped_meshes: AllocInfo, + pub clipped_primitives: AllocInfo, pub vertices: AllocInfo, pub indices: AllocInfo, } @@ -215,27 +216,25 @@ impl PaintStats { Shape::Mesh(mesh) => { self.shape_mesh += AllocInfo::from_mesh(mesh); } + Shape::Callback(_) => { + self.num_callbacks += 1; + } } } - pub fn with_clipped_meshes(mut self, clipped_meshes: &[crate::ClippedMesh]) -> Self { - self.clipped_meshes += AllocInfo::from_slice(clipped_meshes); - for ClippedMesh(_, indices) in clipped_meshes { - self.vertices += AllocInfo::from_slice(&indices.vertices); - self.indices += AllocInfo::from_slice(&indices.indices); + pub fn with_clipped_primitives( + mut self, + clipped_primitives: &[crate::ClippedPrimitive], + ) -> Self { + self.clipped_primitives += AllocInfo::from_slice(clipped_primitives); + for clipped_primitive in clipped_primitives { + if let Primitive::Mesh(mesh) = &clipped_primitive.primitive { + self.vertices += AllocInfo::from_slice(&mesh.vertices); + self.indices += AllocInfo::from_slice(&mesh.indices); + } } self } - - // pub fn total(&self) -> AllocInfo { - // self.shapes - // + self.shape_text - // + self.shape_path - // + self.shape_mesh - // + self.clipped_meshes - // + self.vertices - // + self.indices - // } } fn megabytes(size: usize) -> String { diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index cc1c75fc..f46af30d 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -781,6 +781,9 @@ impl Tessellator { self.tessellate_quadratic_bezier(quadratic_shape, out); } Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(cubic_shape, out), + Shape::Callback(_) => { + panic!("Shape::Callback passed to Tessellator"); + } } } @@ -1046,58 +1049,97 @@ pub fn tessellate_shapes( shapes: Vec, options: TessellationOptions, tex_size: [usize; 2], -) -> Vec { +) -> Vec { let mut tessellator = Tessellator::from_options(options); - let mut clipped_meshes: Vec = Vec::default(); + let mut clipped_primitives: Vec = Vec::default(); - for ClippedShape(clip_rect, shape) in shapes { - if !clip_rect.is_positive() { + for ClippedShape(new_clip_rect, new_shape) in shapes { + if !new_clip_rect.is_positive() { continue; // skip empty clip rectangles } - let start_new_mesh = match clipped_meshes.last() { - None => true, - Some(cm) => cm.0 != clip_rect || cm.1.texture_id != shape.texture_id(), - }; + if let Shape::Callback(callback) = new_shape { + clipped_primitives.push(ClippedPrimitive { + clip_rect: new_clip_rect, + primitive: Primitive::Callback(callback), + }); + } else { + let start_new_mesh = match clipped_primitives.last() { + None => true, + Some(output_clipped_primitive) => { + output_clipped_primitive.clip_rect != new_clip_rect + || if let Primitive::Mesh(output_mesh) = &output_clipped_primitive.primitive + { + output_mesh.texture_id != new_shape.texture_id() + } else { + true + } + } + }; - if start_new_mesh { - clipped_meshes.push(ClippedMesh(clip_rect, Mesh::default())); - } + if start_new_mesh { + clipped_primitives.push(ClippedPrimitive { + clip_rect: new_clip_rect, + primitive: Primitive::Mesh(Mesh::default()), + }); + } - let out = &mut clipped_meshes.last_mut().unwrap().1; - tessellator.clip_rect = clip_rect; - tessellator.tessellate_shape(tex_size, shape, out); - } + let out = clipped_primitives.last_mut().unwrap(); - if options.debug_paint_clip_rects { - for ClippedMesh(clip_rect, mesh) in &mut clipped_meshes { - if mesh.texture_id == TextureId::default() { - tessellator.clip_rect = Rect::EVERYTHING; - tessellator.tessellate_shape( - tex_size, - Shape::rect_stroke( - *clip_rect, - 0.0, - Stroke::new(2.0, Color32::from_rgb(150, 255, 150)), - ), - mesh, - ); + if let Primitive::Mesh(out_mesh) = &mut out.primitive { + tessellator.clip_rect = new_clip_rect; + tessellator.tessellate_shape(tex_size, new_shape, out_mesh); } else { - // TODO: create a new `ClippedMesh` just for the painted clip rectangle + unreachable!(); } } } + if options.debug_paint_clip_rects { + clipped_primitives = add_clip_rects(&mut tessellator, tex_size, clipped_primitives); + } + if options.debug_ignore_clip_rects { - for ClippedMesh(clip_rect, _) in &mut clipped_meshes { - *clip_rect = Rect::EVERYTHING; + for clipped_primitive in &mut clipped_primitives { + clipped_primitive.clip_rect = Rect::EVERYTHING; } } - for ClippedMesh(_, mesh) in &clipped_meshes { - crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); + for clipped_primitive in &clipped_primitives { + if let Primitive::Mesh(mesh) = &clipped_primitive.primitive { + crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); + } } - clipped_meshes + clipped_primitives +} + +fn add_clip_rects( + tessellator: &mut Tessellator, + tex_size: [usize; 2], + clipped_primitives: Vec, +) -> Vec { + tessellator.clip_rect = Rect::EVERYTHING; + let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150)); + + clipped_primitives + .into_iter() + .flat_map(|clipped_primitive| { + let mut clip_rect_mesh = Mesh::default(); + tessellator.tessellate_shape( + tex_size, + Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke), + &mut clip_rect_mesh, + ); + + [ + clipped_primitive, + ClippedPrimitive { + clip_rect: Rect::EVERYTHING, // whatever + primitive: Primitive::Mesh(clip_rect_mesh), + }, + ] + }) + .collect() } diff --git a/epi/Cargo.toml b/epi/Cargo.toml index 398e426c..4e894a62 100644 --- a/epi/Cargo.toml +++ b/epi/Cargo.toml @@ -31,6 +31,7 @@ persistence = ["ron", "serde", "egui/persistence"] egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ "single_threaded", ] } +glow = "0.11" tracing = "0.1" directories-next = { version = "2", optional = true } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index ecda8514..033a4a78 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -94,6 +94,7 @@ pub mod file_storage; pub use egui; // Re-export for user convenience +pub use glow; // Re-export for user convenience use std::sync::{Arc, Mutex}; @@ -112,13 +113,23 @@ pub trait App { /// or call [`Frame::request_repaint`] at any time (e.g. from another thread). fn update(&mut self, ctx: &egui::Context, frame: &Frame); - /// Called once before the first frame. + /// Called exactly once at startup, before any call to [`Self::update`]. /// /// Allows you to do setup code, e.g to call [`egui::Context::set_fonts`], /// [`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::Context, _frame: &Frame, _storage: Option<&dyn Storage>) {} + /// Also allows you to restore state, if there is a storage (requires the "persistence" feature). + /// + /// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that + /// you might want to use later from a [`egui::PaintCallback`]. + fn setup( + &mut self, + _ctx: &egui::Context, + _frame: &Frame, + _storage: Option<&dyn Storage>, + _gl: &std::rc::Rc, + ) { + } /// Called on shutdown, and perhaps at regular intervals. Allows you to save state. /// @@ -361,6 +372,13 @@ impl Frame { } } +#[cfg(test)] +#[test] +fn frame_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} + /// Information about the web environment (if applicable). #[derive(Clone, Debug)] pub struct WebInfo {