diff --git a/Cargo.lock b/Cargo.lock index 2ce1e0bc..88cd1f4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1454,9 +1454,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e493c573fce17f00dcab13b6ac057994f3ce17d1af4dc39bfd482b83c6eb6157" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" dependencies = [ "litrs", ] @@ -1479,9 +1479,9 @@ dependencies = [ [[package]] name = "ecolor" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57539aabcdbb733b6806ef421b66dec158dc1582107ad6d51913db3600303354" +checksum = "03cfe80b1890e1a8cdbffc6044d6872e814aaf6011835a2a5e2db0e5c5c4ef4e" dependencies = [ "bytemuck", "serde", @@ -1489,9 +1489,9 @@ dependencies = [ [[package]] name = "egui" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0bf640ed7f3bf3d14ebf00d73bacc09c886443ee84ca6494bde37953012c9e3" +checksum = "180f595432a5b615fc6b74afef3955249b86cfea72607b40740a4cd60d5297d0" dependencies = [ "accesskit", "ahash 0.8.6", @@ -1505,34 +1505,34 @@ dependencies = [ [[package]] name = "egui-modal" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4dd394948863624a7269cedfaeb5c6a568e6d1d7accfac6fa70c283699c78b" +checksum = "da17dbe55bb1b04fd8b4624ab1b68f6ec245f44aedc90893463f8dbc70f28231" dependencies = [ "egui", ] [[package]] name = "egui-notify" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e978910d93935cb5a73322d885a9804616c9b15570428a9c5cd68fb3b5cd1e8" +checksum = "4abbfff21281b41451a347f2c0938ce278fab65ee450eb7a495efffa0bc3bb95" dependencies = [ "egui", ] [[package]] name = "egui-winit" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d95d9762056c541bd2724de02910d8bccf3af8e37689dc114b21730e64f80a0" +checksum = "aa4d44f8d89f70d4480545eb2346b76ea88c3022e9f4706cebc799dbe8b004a2" dependencies = [ "accesskit_winit", "arboard", "egui", "log", "puffin", - "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", "serde", "smithay-clipboard", "web-time", @@ -1542,9 +1542,9 @@ dependencies = [ [[package]] name = "egui_dock" -version = "0.10.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727ea33d9940fb3eea4ba59c78e47db5d0c25bdcb3f3169be68d0ee7088f5e61" +checksum = "d14beb118bc4d114bb875f14d1d437736be7010939cedf60c9ee002d7d02d09f" dependencies = [ "duplicate", "egui", @@ -1553,9 +1553,9 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "753c36d3e2f7a32425af5290af2e52efb3471ea3a263b87f003b5433351b0fd7" +checksum = "3f4a6962241a76da5be5e64e41b851ee1c95fda11f76635522a3c82b119b5475" dependencies = [ "egui", "enum-map", @@ -1574,9 +1574,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee58355767587db7ba3738930d93cad3052cd834c2b48b9ef6ef26fe4823b7e" +checksum = "6916301ecf80448f786cdf3eb51d9dbdd831538732229d49119e2d4312eaaf09" dependencies = [ "bytemuck", "serde", @@ -1658,9 +1658,9 @@ dependencies = [ [[package]] name = "epaint" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e638cb066bff0903bbb6143116cfd134a42279c7d68f19c0352a94f15a402de7" +checksum = "77b9fdf617dd7f58b0c8e6e9e4a1281f730cde0831d40547da446b2bb76a47af" dependencies = [ "ab_glyph", "ahash 0.8.6", @@ -1670,6 +1670,7 @@ dependencies = [ "log", "nohash-hasher", "parking_lot", + "puffin", "serde", ] @@ -2968,9 +2969,9 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "litrs" -version = "0.2.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" @@ -3191,6 +3192,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "web-time", "wgpu", "winapi", "winit", @@ -3208,6 +3210,7 @@ dependencies = [ "puffin", "thiserror", "type-map", + "web-time", "wgpu", "winit", ] @@ -4546,14 +4549,15 @@ checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b" [[package]] name = "puffin" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0b84517b2fb755da3a634bc030fcbc7b6337a786aa25a7fb350cdd51ab5e15" +checksum = "b9f76ad4bb049fded4e572df72cbb6381ff5d1f41f85c3a04b56e4eca287a02f" dependencies = [ "anyhow", "byteorder", "cfg-if", "once_cell", + "parking_lot", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9730131a..954ed440 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,9 +60,9 @@ categories = ["games"] # Shared dependencies [workspace.dependencies] -egui = "0.25.0" -egui_extras = { version = "0.25.0", features = ["svg", "image"] } -epaint = "0.25.0" +egui = "0.26.2" +egui_extras = { version = "0.26.2", features = ["svg", "image"] } +epaint = "0.26.2" luminol-eframe = { version = "0.4.0", path = "crates/eframe/", features = [ "wgpu", @@ -73,7 +73,7 @@ luminol-eframe = { version = "0.4.0", path = "crates/eframe/", features = [ "wayland", ], default-features = false } luminol-egui-wgpu = { version = "0.4.0", path = "crates/egui-wgpu/" } -egui-winit = "0.25.0" +egui-winit = "0.26.2" wgpu = { version = "0.19.1", features = ["naga-ir"] } glam = { version = "0.24.2", features = ["bytemuck"] } @@ -97,9 +97,13 @@ paste = "1.0.14" thiserror = "1.0.37" bitflags = "2.4.0" color-eyre = "0.6.2" -puffin = "0.18" + +puffin = "0.19" raw-window-handle = "0.6.0" winit = { version = "0.29.4", default-features = false } +log = { version = "0.4", features = ["std"] } +document-features = "0.2.8" +web-time = "0.2" parking_lot = { version = "0.12.1", features = [ "nightly", # This is required for parking_lot to work properly in WebAssembly builds with atomics support diff --git a/assets/sw.js b/assets/sw.js index 3fb78664..1cea85eb 100644 --- a/assets/sw.js +++ b/assets/sw.js @@ -106,7 +106,7 @@ if (typeof window === 'undefined') { }); // Auto-cache non-error, non-opaque responses for all same-origin requests other than buildinfo.json - if (response.type === "error" || new URL(request.url).origin !== self.origin || request.url.endsWith('/buildinfo.json')) { + if (response.type === "error" || url.origin !== self.origin || url.pathname.endsWith("/buildinfo.json")) { return newResponse; } else { return self.caches @@ -116,7 +116,7 @@ if (typeof window === 'undefined') { } }) .catch((e) => { - if (!request.url.endsWith('/buildinfo.json')) { + if (!url.pathname.endsWith("/buildinfo.json")) { console.error(e); } }) diff --git a/crates/components/src/lib.rs b/crates/components/src/lib.rs index 7104e95c..c60e416a 100644 --- a/crates/components/src/lib.rs +++ b/crates/components/src/lib.rs @@ -159,6 +159,8 @@ where .width(ui.available_width() - ui.spacing().item_spacing.x) .selected_text(self.reference.to_string()) .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(true); + for (i, variant) in T::iter().enumerate() { ui.with_stripe(i % 2 != 0, |ui| { if ui @@ -364,6 +366,8 @@ where } }, |this, ui, ids, first_row_is_faint, show_none| { + ui.style_mut().wrap = Some(true); + if show_none && ui .with_stripe(false, |ui| { @@ -418,6 +422,8 @@ where ui, |this| (this.formatter)(*this.reference), |this, ui, ids, first_row_is_faint, _| { + ui.style_mut().wrap = Some(true); + let mut is_faint = first_row_is_faint; for id in ids { diff --git a/crates/components/src/map_view.rs b/crates/components/src/map_view.rs index f1c2b6d5..d06f6627 100644 --- a/crates/components/src/map_view.rs +++ b/crates/components/src/map_view.rs @@ -214,7 +214,7 @@ impl MapView { if let Some(pos) = response.hover_pos() { // We need to store the old scale before applying any transformations let old_scale = self.scale; - let delta = ui.input(|i| i.scroll_delta.y); + let delta = ui.input(|i| i.smooth_scroll_delta.y); // Apply scroll and cap max zoom to 15% self.scale *= (delta / 9.0f32.exp2()).exp2(); @@ -509,7 +509,7 @@ impl MapView { 5., egui::Stroke::new(1., egui::Color32::WHITE), ), - } + }; }); if let Some(hover_tile) = self.hover_tile { diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 8b5c8fa1..f413240a 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -19,9 +19,9 @@ workspace = true [dependencies] egui.workspace = true -egui_dock = "0.10.0" -egui-notify = "0.12.0" -egui-modal = "0.3.2" +egui_dock = "0.11.2" +egui-notify = "0.13.0" +egui-modal = "0.3.4" poll-promise.workspace = true once_cell.workspace = true diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 171cf5db..f0566955 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,32 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.26.2 - 2024-02-14 +* Add `winuser` feature to `winapi` to fix unresolved import [#4037](https://github.com/emilk/egui/pull/4037) (thanks [@varphone](https://github.com/varphone)!) + + +## 0.26.1 - 2024-02-11 +* Fix high CPU usage on Windows when app is minimized [#3985](https://github.com/emilk/egui/pull/3985) (thanks [@rustbasic](https://github.com/rustbasic)!) +* Update to document-features 0.2.8 [#4003](https://github.com/emilk/egui/pull/4003) + + +## 0.26.0 - 2024-02-05 +* Update `wgpu` to 0.19 [#3824](https://github.com/emilk/egui/pull/3824) +* Disable the default features of `wgpu` [#3875](https://github.com/emilk/egui/pull/3875) +* Much more accurate `cpu_usage` timing [#3913](https://github.com/emilk/egui/pull/3913) +* Update to puffin 0.19 [#3940](https://github.com/emilk/egui/pull/3940) + +#### Desktop/Native: +* Keep `ViewportInfo::maximized` and `minimized` up-to-date on Windows [#3831](https://github.com/emilk/egui/pull/3831) (thanks [@rustbasic](https://github.com/rustbasic)!) +* Handle `IconData::default()` without crashing [#3842](https://github.com/emilk/egui/pull/3842) +* Fix Android crash on resume [#3847](https://github.com/emilk/egui/pull/3847) [#3867](https://github.com/emilk/egui/pull/3867) (thanks [@Garoven](https://github.com/Garoven)!) +* Add `WgpuConfiguration::desired_maximum_frame_latency` [#3874](https://github.com/emilk/egui/pull/3874) +* Don't call `App::update` on minimized windows [#3877](https://github.com/emilk/egui/pull/3877) (thanks [@rustbasic](https://github.com/rustbasic)!) + +#### Web: +* When using `wgpu` on web, `eframe` will try to use WebGPU if available, then fall back to WebGL [#3824](https://github.com/emilk/egui/pull/3824) [#3895](https://github.com/emilk/egui/pull/3895) (thanks [@Wumpf](https://github.com/Wumpf)!) + + ## 0.25.0 - 2024-01-08 * If both `glow` and `wgpu` features are enabled, default to `wgpu` [#3717](https://github.com/emilk/egui/pull/3717) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index bfd09dfa..57eb8430 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -88,7 +88,11 @@ puffin = [ ] ## Enables wayland support and fixes clipboard issue. -wayland = ["egui-winit/wayland"] +wayland = [ + "egui-winit/wayland", + "luminol-egui-wgpu?/wayland", + #"egui_glow?/wayland", +] ## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web. ## @@ -105,7 +109,7 @@ web_screen_reader = [ ## By default, only WebGPU is enabled on web. ## If you want to enable WebGL, you need to turn on the `webgl` feature of crate `wgpu`: ## -## ```ignore +## ```toml ## wgpu = { version = "*", features = ["webgpu", "webgl"] } ## ``` ## @@ -114,7 +118,11 @@ web_screen_reader = [ wgpu = ["dep:wgpu", "dep:luminol-egui-wgpu", "dep:pollster"] ## Enables compiling for x11. -x11 = ["egui-winit/x11"] +x11 = [ + "egui-winit/x11", + "luminol-egui-wgpu?/x11", + #"egui_glow?/x11", +] ## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit. ## This is used to generate images for examples. @@ -125,17 +133,18 @@ egui = { workspace = true, features = [ "bytemuck", "log", ] } -log = { version = "0.4", features = ["std"] } -parking_lot = "0.12" + +document-features.workspace = true +log.workspace = true +parking_lot.workspace = true raw-window-handle.workspace = true static_assertions = "1.1.0" thiserror.workspace = true +web-time.workspace = true -#! ### Optional dependencies -## Enable this when generating docs. -document-features = { version = "0.2", optional = true } +# Optional dependencies -#egui_glow = { workspace = true, optional = true } +#egui_glow = { workspace = true, optional = true, default-features = false } #glow = { workspace = true, optional = true } # glutin stuck on old version of raw-window-handle: rwh_05 = { package = "raw-window-handle", version = "0.5.2", optional = true, features = [ @@ -168,7 +177,12 @@ pollster = { version = "0.3", optional = true } # needed for wgpu glutin = { version = "0.31", optional = true } glutin-winit = { version = "0.4", optional = true } puffin = { workspace = true, optional = true } -wgpu = { workspace = true, optional = true } +wgpu = { workspace = true, optional = true, features = [ + # Let's enable some backends so that users can use `eframe` out-of-the-box + # without having to explicitly opt-in to backends + "metal", + "webgpu", +] } # mac: [target.'cfg(any(target_os = "macos"))'.dependencies] @@ -177,7 +191,7 @@ objc = "0.2.7" # windows: [target.'cfg(any(target_os = "windows"))'.dependencies] -winapi = "0.3.9" +winapi = { version = "0.3.9", features = ["winuser"] } # ------------------------------------------- # web: diff --git a/crates/eframe/README.md b/crates/eframe/README.md index f8ffb330..478291d5 100644 --- a/crates/eframe/README.md +++ b/crates/eframe/README.md @@ -1,5 +1,5 @@ > [!IMPORTANT] -> luminol-eframe is currently based on emilk/egui@0.25.0 +> luminol-eframe is currently based on emilk/egui@0.26.2 > [!NOTE] > This is Luminol's modified version of eframe. The original version is dual-licensed under MIT and Apache 2.0. @@ -30,7 +30,7 @@ ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) -`eframe` is the official framework library for writing apps using [`egui`](https://github.com/emilk/egui). The app can be compiled both to run natively (cross platform) or be compiled to a web app (using WASM). +`eframe` is the official framework library for writing apps using [`egui`](https://github.com/emilk/egui). The app can be compiled both to run natively (for Linux, Mac, Windows, and Android) or as a web app (using [Wasm](https://en.wikipedia.org/wiki/WebAssembly)). To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples). To learn how to set up `eframe` for web and native, go to and follow the instructions there! @@ -61,15 +61,15 @@ To get copy-paste working on web, you need to compile with `export RUSTFLAGS=--c You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/winit) to build your own app as demonstrated in . -## Problems with running egui on the web -`eframe` 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 challenges and serious downsides. +## Limitations when running egui on the web +`eframe` 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 challenges 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. * Bringing up an on-screen keyboard on mobile: there is no JS function to do this, so `eframe` fakes it by adding some invisible DOM elements. It doesn't always work. * Mobile text editing is not as good as for a normal web app. -* Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns). * No integration with browser settings for colors and fonts. +* Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns). `egui` supports [AccessKit](https://github.com/AccessKit/accesskit), but as of early 2024, AccessKit lacks a Web backend. In many ways, `eframe` is trying to make the browser do something it wasn't designed to do (though there are many things browser vendors could do to improve how well libraries like egui work). @@ -77,12 +77,13 @@ The suggested use for `eframe` are for web apps where performance and responsive ## Companion crates -Not all rust crates work when compiled to WASM, but here are some useful crates have been designed to work well both natively and as WASM: +Not all rust crates work when compiled to Wasm, but here are some useful crates have been designed to work well both natively and as Wasm: -* Audio: [`cpal`](https://github.com/RustAudio/cpal). -* HTTP client: [`ehttp`](https://github.com/emilk/ehttp) and [`reqwest`](https://github.com/seanmonstar/reqwest). -* Time: [`chrono`](https://github.com/chronotope/chrono). -* WebSockets: [`ewebsock`](https://github.com/rerun-io/ewebsock). +* Audio: [`cpal`](https://github.com/RustAudio/cpal) +* File dialogs: [rfd](https://docs.rs/rfd/latest/rfd/) +* HTTP client: [`ehttp`](https://github.com/emilk/ehttp) and [`reqwest`](https://github.com/seanmonstar/reqwest) +* Time: [`chrono`](https://github.com/chronotope/chrono) +* WebSockets: [`ewebsock`](https://github.com/rerun-io/ewebsock) ## Name diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 6b86c0b5..7054bd52 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -757,7 +757,13 @@ pub struct IntegrationInfo { /// `None` means "don't know". pub system_theme: Option, - /// Seconds of cpu usage (in seconds) of UI code on the previous frame. + /// Seconds of cpu usage (in seconds) on the previous frame. + /// + /// This includes [`App::update`] as well as rendering (except for vsync waiting). + /// + /// For a more detailed view of cpu usage, use the [`puffin`](https://crates.io/crates/puffin) + /// profiler together with the `puffin` feature of `eframe`. + /// /// `None` if this is the first frame. pub cpu_usage: Option, } diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index fa97526f..05717d40 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -130,7 +130,7 @@ //! ``` //! //! ## Feature flags -#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] +#![doc = document_features::document_features!()] //! #![warn(missing_docs)] // let's keep eframe well-documented @@ -152,6 +152,8 @@ mod epi; // Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is: pub use epi::*; +pub(crate) mod stopwatch; + // ---------------------------------------------------------------------------- // When compiling for web @@ -293,7 +295,7 @@ pub fn run_native( /// .labelled_by(name_label.id); /// }); /// ui.add(egui::Slider::new(&mut age, 0..=120).text("age")); -/// if ui.button("Click each year").clicked() { +/// if ui.button("Increment").clicked() { /// age += 1; /// } /// ui.label(format!("Hello '{name}', age {age}")); diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index c5b98063..1f12d1cc 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -1,7 +1,6 @@ //! Common tools used by [`super::glow_integration`] and [`super::wgpu_integration`]. -use std::time::Instant; - +use web_time::Instant; use winit::event_loop::EventLoopWindowTarget; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; @@ -259,7 +258,6 @@ impl EpiIntegration { } pub fn pre_update(&mut self) { - self.frame_start = Instant::now(); self.app_icon_setter.update(); } @@ -304,9 +302,8 @@ impl EpiIntegration { std::mem::take(&mut self.pending_full_output) } - pub fn post_update(&mut self) { - let frame_time = self.frame_start.elapsed().as_secs_f64() as f32; - self.frame.info.cpu_usage = Some(frame_time); + pub fn report_frame_time(&mut self, seconds: f32) { + self.frame.info.cpu_usage = Some(seconds); } pub fn post_rendering(&mut self, window: &winit::window::Window) { diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 75820bec..be30d853 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -39,18 +39,6 @@ use super::{ *, }; -// Note: that the current Glutin API design tightly couples the GL context with -// the Window which means it's not practically possible to just destroy the -// window and re-create a new window while continuing to use the same GL context. -// -// For now this means it's not possible to support Android as well as we can with -// wgpu because we're basically forced to destroy and recreate _everything_ when -// the application suspends and resumes. -// -// There is work in progress to improve the Glutin API so it has a separate Surface -// API that would allow us to just destroy a Window/Surface when suspending, see: -// https://github.com/rust-windowing/glutin/pull/1435 - // ---------------------------------------------------------------------------- // Types: @@ -505,6 +493,9 @@ impl GlowWinitRunning { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); + let mut frame_timer = crate::stopwatch::Stopwatch::new(); + frame_timer.start(); + { let glutin = self.glutin.borrow(); let viewport = &glutin.viewports[&viewport_id]; @@ -525,7 +516,9 @@ impl GlowWinitRunning { let mut glutin = self.glutin.borrow_mut(); let egui_ctx = glutin.egui_ctx.clone(); let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); - let window = viewport.window.as_ref().unwrap(); + let Some(window) = viewport.window.as_ref() else { + return EventResult::Wait; + }; egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window); let egui_winit = viewport.egui_winit.as_mut().unwrap(); @@ -566,7 +559,11 @@ impl GlowWinitRunning { let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); - change_gl_context(current_gl_context, gl_surface); + { + frame_timer.pause(); + change_gl_context(current_gl_context, gl_surface); + frame_timer.resume(); + } self.painter .borrow() @@ -610,17 +607,20 @@ impl GlowWinitRunning { let viewport = viewports.get_mut(&viewport_id).unwrap(); viewport.info.events.clear(); // they should have been processed - let window = viewport.window.as_ref().unwrap(); + let window = viewport.window.clone().unwrap(); let gl_surface = viewport.gl_surface.as_ref().unwrap(); let egui_winit = viewport.egui_winit.as_mut().unwrap(); - integration.post_update(); - egui_winit.handle_platform_output(window, platform_output); + egui_winit.handle_platform_output(&window, platform_output); let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point); - // We may need to switch contexts again, because of immediate viewports: - change_gl_context(current_gl_context, gl_surface); + { + // We may need to switch contexts again, because of immediate viewports: + frame_timer.pause(); + change_gl_context(current_gl_context, gl_surface); + frame_timer.resume(); + } let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); @@ -647,10 +647,12 @@ impl GlowWinitRunning { image: screenshot.into(), }); } - integration.post_rendering(window); + integration.post_rendering(&window); } { + // vsync - don't count as frame-time: + frame_timer.pause(); crate::profile_scope!("swap_buffers"); if let Err(err) = gl_surface.swap_buffers( current_gl_context @@ -659,6 +661,7 @@ impl GlowWinitRunning { ) { log::error!("swap_buffers failed: {err}"); } + frame_timer.resume(); } // give it time to settle: @@ -669,7 +672,11 @@ impl GlowWinitRunning { } } - integration.maybe_autosave(app.as_mut(), Some(window)); + glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output); + + integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time + + integration.maybe_autosave(app.as_mut(), Some(&window)); if window.is_minimized() == Some(true) { // On Mac, a minimized Window uses up all CPU: @@ -678,8 +685,6 @@ impl GlowWinitRunning { std::thread::sleep(std::time::Duration::from_millis(10)); } - glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output); - if integration.should_close() { EventResult::Exit } else { diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index d5c172c3..370be7b8 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -66,7 +66,7 @@ fn run_and_return( ) -> Result<()> { use winit::{event_loop::ControlFlow, platform::run_on_demand::EventLoopExtRunOnDemand}; - log::debug!("Entering the winit event loop (run_on_demand)…"); + log::trace!("Entering the winit event loop (run_on_demand)…"); // When to repaint what window let mut windows_next_repaint_times = HashMap::default(); @@ -192,8 +192,13 @@ fn run_and_return( if let Some(window) = winit_app.window(*window_id) { log::trace!("request_redraw for {window_id:?}"); - window.request_redraw(); - true + let is_minimized = window.is_minimized().unwrap_or(false); + if is_minimized { + false + } else { + window.request_redraw(); + true + } } else { log::trace!("No window found for {window_id:?}"); false @@ -231,7 +236,7 @@ fn run_and_exit( mut winit_app: impl WinitApp + 'static, ) -> Result<()> { use winit::event_loop::ControlFlow; - log::debug!("Entering the winit event loop (run)…"); + log::trace!("Entering the winit event loop (run)…"); // When to repaint what window let mut windows_next_repaint_times = HashMap::default(); @@ -345,8 +350,13 @@ fn run_and_exit( if let Some(window) = winit_app.window(*window_id) { log::trace!("request_redraw for {window_id:?}"); - window.request_redraw(); - true + let is_minimized = window.is_minimized().unwrap_or(false); + if is_minimized { + false + } else { + window.request_redraw(); + true + } } else { log::trace!("No window found for {window_id:?}"); false diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index d0a6d27c..70a12e01 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -139,6 +139,32 @@ impl WgpuWinitApp { } } + #[cfg(target_os = "android")] + fn recreate_window( + &self, + event_loop: &EventLoopWindowTarget, + running: &WgpuWinitRunning, + ) { + let SharedState { + egui_ctx, + viewports, + viewport_from_window, + painter, + .. + } = &mut *running.shared.borrow_mut(); + + initialize_or_update_viewport( + egui_ctx, + viewports, + ViewportIdPair::ROOT, + ViewportClass::Root, + self.native_options.viewport.clone(), + None, + None, + ) + .initialize_window(event_loop, egui_ctx, viewport_from_window, painter); + } + #[cfg(target_os = "android")] fn drop_window(&mut self) -> Result<(), luminol_egui_wgpu::WgpuError> { if let Some(running) = &mut self.running { @@ -386,6 +412,8 @@ impl WinitApp for WgpuWinitApp { log::debug!("Event::Resumed"); let running = if let Some(running) = &self.running { + #[cfg(target_os = "android")] + self.recreate_window(event_loop, running); running } else { let storage = epi_integration::create_storage( @@ -500,6 +528,9 @@ impl WgpuWinitRunning { shared, } = self; + let mut frame_timer = crate::stopwatch::Stopwatch::new(); + frame_timer.start(); + let (viewport_ui_cb, raw_input) = { crate::profile_scope!("Prepare"); let mut shared_lock = shared.borrow_mut(); @@ -600,8 +631,6 @@ impl WgpuWinitRunning { return EventResult::Wait; }; - integration.post_update(); - let FullOutput { platform_output, textures_delta, @@ -612,27 +641,25 @@ impl WgpuWinitRunning { egui_winit.handle_platform_output(window, platform_output); - { - let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); - - let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); - let screenshot = painter.paint_and_update_textures( - viewport_id, - pixels_per_point, - app.clear_color(&egui_ctx.style().visuals), - &clipped_primitives, - &textures_delta, - screenshot_requested, - ); - if let Some(screenshot) = screenshot { - egui_winit - .egui_input_mut() - .events - .push(egui::Event::Screenshot { - viewport_id, - image: screenshot.into(), - }); - } + let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); + + let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); + let (vsync_secs, screenshot) = painter.paint_and_update_textures( + viewport_id, + pixels_per_point, + app.clear_color(&egui_ctx.style().visuals), + &clipped_primitives, + &textures_delta, + screenshot_requested, + ); + if let Some(screenshot) = screenshot { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + image: screenshot.into(), + }); } integration.post_rendering(window); @@ -656,6 +683,8 @@ impl WgpuWinitRunning { .and_then(|id| viewports.get(id)) .and_then(|vp| vp.window.as_ref()); + integration.report_frame_time(frame_timer.total_time_sec() - vsync_secs); // don't count auto-save time as part of regular frame time + integration.maybe_autosave(app.as_mut(), window.map(|w| w.as_ref())); if let Some(window) = window { diff --git a/crates/eframe/src/stopwatch.rs b/crates/eframe/src/stopwatch.rs new file mode 100644 index 00000000..9b013618 --- /dev/null +++ b/crates/eframe/src/stopwatch.rs @@ -0,0 +1,50 @@ +#![allow(dead_code)] // not everything is used on wasm + +use web_time::Instant; + +pub struct Stopwatch { + total_time_ns: u128, + + /// None = not running + start: Option, +} + +impl Stopwatch { + pub fn new() -> Self { + Self { + total_time_ns: 0, + start: None, + } + } + + pub fn start(&mut self) { + assert!(self.start.is_none()); + self.start = Some(Instant::now()); + } + + pub fn pause(&mut self) { + let start = self.start.take().unwrap(); + let duration = start.elapsed(); + self.total_time_ns += duration.as_nanos(); + } + + pub fn resume(&mut self) { + assert!(self.start.is_none()); + self.start = Some(Instant::now()); + } + + pub fn total_time_ns(&self) -> u128 { + if let Some(start) = self.start { + // Running + let duration = start.elapsed(); + self.total_time_ns + duration.as_nanos() + } else { + // Paused + self.total_time_ns + } + } + + pub fn total_time_sec(&self) -> f32 { + self.total_time_ns() as f32 * 1e-9 + } +} diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 4aa9b8af..cdcbdd91 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -201,8 +201,6 @@ impl AppRunner { /// /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`]. pub fn logic(&mut self) { - let frame_start = now_sec(); - let raw_input = self.input.new_frame( egui::vec2(self.painter.width as f32, self.painter.height as f32), self.painter.pixel_ratio, @@ -241,8 +239,6 @@ impl AppRunner { )); self.textures_delta.append(textures_delta); self.clipped_primitives = Some(self.egui_ctx.tessellate(shapes, pixels_per_point)); - - self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32); } /// Paint the results of the last call to [`Self::logic`]. @@ -262,6 +258,10 @@ impl AppRunner { } } + pub fn report_frame_time(&mut self, cpu_usage_seconds: f32) { + self.frame.info.cpu_usage = Some(cpu_usage_seconds); + } + pub(super) fn handle_platform_output( state: &super::MainState, platform_output: egui::PlatformOutput, diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 93024519..65bbba27 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -117,11 +117,16 @@ fn paint_if_needed(runner: &mut AppRunner) { // running the logic, as the logic could cause it to be set again. runner.needs_repaint.clear(); + let mut stopwatch = crate::stopwatch::Stopwatch::new(); + stopwatch.start(); + // Run user code… runner.logic(); // …and paint the result. runner.paint(); + + runner.report_frame_time(stopwatch.total_time_sec()); } } runner.auto_save_if_needed(); diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index b1a8bd67..39a24357 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -6,7 +6,7 @@ use raw_window_handle::{ }; use wasm_bindgen::JsValue; -use luminol_egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction}; +use luminol_egui_wgpu::{RenderState, SurfaceErrorAction}; use crate::WebOptions; @@ -96,27 +96,90 @@ impl WebPainterWgpu { ) -> Result { log::debug!("Creating wgpu painter"); - { + let mut backends = options.wgpu_options.supported_backends; + + // Don't try WebGPU if we're not in a secure context. + if backends.contains(wgpu::Backends::BROWSER_WEBGPU) { let is_secure_context = luminol_web::bindings::worker() .expect("failed to get worker context") .is_secure_context(); if !is_secure_context { log::info!( - "WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost" + "WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost." ); + + // Don't try WebGPU since we established now that it will fail. + backends.remove(wgpu::Backends::BROWSER_WEBGPU); + + if backends.is_empty() { + return Err("No available supported graphics backends.".to_owned()); + } } } - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: options.wgpu_options.supported_backends, + log::debug!("Creating wgpu instance with backends {:?}", backends); + let mut instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, ..Default::default() }); + // It can happen that a browser advertises WebGPU support, but then fails to create a + // suitable adapter. As of writing this happens for example on Linux with Chrome 121. + // + // Since WebGPU is handled in a special way in wgpu, we have to recreate the instance + // if we instead want to try with WebGL. + // + // To make matters worse, once a canvas has been used with either WebGL or WebGPU, + // we can't go back and change that without replacing the canvas (which is hard to do from here). + // Therefore, we have to create the surface *after* requesting the adapter. + // However, wgpu offers to pass in a surface on adapter creation to ensure it is actually compatible with the chosen backend. + // This in turn isn't all that important on the web, but it still makes sense for the design of + // `egui::RenderState`! + // Therefore, we have to first check if it's possible to create a WebGPU adapter, + // and if it is not, start over with a WebGL instance. + // + // Note that we also might needlessly try this here if wgpu already determined that there's no + // WebGPU support in the first place. This is not a huge problem since it fails very fast, but + // it would be nice to avoid this. See https://github.com/gfx-rs/wgpu/issues/5142 + if backends.contains(wgpu::Backends::BROWSER_WEBGPU) { + log::debug!("Attempting to create WebGPU adapter to check for support."); + if let Some(adapter) = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: options.wgpu_options.power_preference, + compatible_surface: None, + force_fallback_adapter: false, + }) + .await + { + // WebGPU doesn't spec yet a destroy on the adapter, only on the device. + //adapter.destroy(); + log::debug!( + "Successfully created WebGPU adapter, WebGPU confirmed to be supported!" + ); + } else { + log::debug!("Failed to create WebGPU adapter."); + + if backends.contains(wgpu::Backends::GL) { + log::debug!("Recreating wgpu instance with WebGL backend only."); + backends = wgpu::Backends::GL; + instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + ..Default::default() + }); + } else { + return Err( + "Failed to create WebGPU adapter and WebGL was not enabled.".to_owned() + ); + } + } + } + let surface = instance .create_surface(wgpu::SurfaceTarget::OffscreenCanvas(canvas.clone())) .map_err(|err| format!("failed to create wgpu surface: {err}"))?; let depth_format = luminol_egui_wgpu::depth_format_from_bits(options.depth_buffer, 0); + let render_state = RenderState::create(&options.wgpu_options, &instance, &surface, depth_format, 1) .await @@ -125,13 +188,11 @@ impl WebPainterWgpu { let (width, height) = (0, 0); let surface_configuration = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: render_state.target_format, present_mode: options.wgpu_options.present_mode, - alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![render_state.target_format], ..surface - .get_default_config(&render_state.adapter, width, height) + .get_default_config(&render_state.adapter, 0, 0) // Width/height is set later. .ok_or("The surface isn't supported by this adapter")? }; @@ -187,7 +248,7 @@ impl WebPainter for WebPainterWgpu { }); // Upload all resources for the GPU. - let screen_descriptor = ScreenDescriptor { + let screen_descriptor = luminol_egui_wgpu::ScreenDescriptor { size_in_pixels, pixels_per_point, }; diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 870d2d97..56cecde1 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,25 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.26.2 - 2024-02-14 +* Nothing new + + +## 0.26.1 - 2024-02-11 +* Improve panic message in egui-wgpu when failing to create buffers [#3986](https://github.com/emilk/egui/pull/3986) + + +## 0.26.0 - 2024-02-05 +* Update wgpu to 0.19 [#3824](https://github.com/emilk/egui/pull/3824) +* Add `WgpuConfiguration::desired_maximum_frame_latency` [#3874](https://github.com/emilk/egui/pull/3874) +* Disable the default features of `wgpu` [#3875](https://github.com/emilk/egui/pull/3875) +* If WebGPU fails, re-try adapter creation with WebGL [#3895](https://github.com/emilk/egui/pull/3895) (thanks [@Wumpf](https://github.com/Wumpf)!) +* Delay call to `get_current_texture` (possible small performance win) [#3914](https://github.com/emilk/egui/pull/3914) +* Add `x11` and `wayland` features [#3909](https://github.com/emilk/egui/pull/3909) (thanks [@YgorSouza](https://github.com/YgorSouza)!) +* Pass `ScreenDescriptor` to `egui_wgpu::CallbackTrait::prepare` [#3960](https://github.com/emilk/egui/pull/3960) (thanks [@StratusFearMe21](https://github.com/StratusFearMe21)!) +* Make `egui_wgpu::renderer` a private module [#3979](https://github.com/emilk/egui/pull/3979) + + ## 0.25.0 - 2024-01-08 * Only call wgpu paint callback if viewport is positive [#3778](https://github.com/emilk/egui/pull/3778) (thanks [@msparkles](https://github.com/msparkles)!) diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 95f70e3e..76b4568f 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -28,28 +28,34 @@ all-features = true [features] +default = [] + ## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate. puffin = ["dep:puffin"] -## Enable [`winit`](https://docs.rs/winit) integration. +## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11` winit = ["dep:winit"] +## Enables Wayland support for winit. +wayland = ["winit?/wayland"] + +## Enables x11 support for winit. +x11 = ["winit?/x11"] + [dependencies] -egui.workspace = true -epaint = { workspace = true, features = [ - "bytemuck", -] } +egui = { workspace = true } +epaint = { workspace = true, features = ["bytemuck"] } bytemuck = "1.7" -log = { version = "0.4", features = ["std"] } +document-features.workspace = true +log.workspace = true thiserror.workspace = true type-map = "0.5.0" -wgpu.workspace = true +web-time.workspace = true +wgpu = { workspace = true, features = ["wgsl"] } -#! ### Optional dependencies -## Enable this when generating docs. -document-features = { version = "0.2", optional = true } +# Optional dependencies: winit = { workspace = true, optional = true, default-features = false, features = [ "rwh_06", diff --git a/crates/egui-wgpu/README.md b/crates/egui-wgpu/README.md index a6d152c4..36b90d6d 100644 --- a/crates/egui-wgpu/README.md +++ b/crates/egui-wgpu/README.md @@ -1,5 +1,5 @@ > [!IMPORTANT] -> luminol-egui-wgpu is currently based on emilk/egui@0.25.0 +> luminol-egui-wgpu is currently based on emilk/egui@0.26.2 > [!NOTE] > This is Luminol's modified version of egui-wgpu. The original version is dual-licensed under MIT and Apache 2.0. diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 30b5f5a9..65ad84f9 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -3,7 +3,7 @@ //! If you're targeting WebGL you also need to turn on the //! `webgl` feature of the `wgpu` crate: //! -//! ```ignore +//! ```toml //! # Enable both WebGL and WebGPU backends on web. //! wgpu = { version = "*", features = ["webgpu", "webgl"] } //! ``` @@ -13,7 +13,7 @@ //! The default is to prefer WebGPU and fall back on WebGL. //! //! ## Feature flags -#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] +#![doc = document_features::document_features!()] //! #![allow(unsafe_code)] @@ -24,9 +24,9 @@ pub use wgpu; /// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`]. -pub mod renderer; -pub use renderer::Renderer; -pub use renderer::{Callback, CallbackResources, CallbackTrait}; +mod renderer; + +pub use renderer::*; /// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`]. #[cfg(feature = "winit")] @@ -231,6 +231,15 @@ pub struct WgpuConfiguration { /// Present mode used for the primary surface. pub present_mode: wgpu::PresentMode, + /// Desired maximum number of frames that the presentation engine should queue in advance. + /// + /// Use `1` for low-latency, and `2` for high-throughput. + /// + /// See [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`] for details. + /// + /// `None` = `wgpu` default. + pub desired_maximum_frame_latency: Option, + /// Power preference for the adapter. pub power_preference: wgpu::PowerPreference, @@ -240,10 +249,22 @@ pub struct WgpuConfiguration { impl std::fmt::Debug for WgpuConfiguration { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + supported_backends, + device_descriptor: _, + present_mode, + desired_maximum_frame_latency, + power_preference, + on_surface_error: _, + } = self; f.debug_struct("WgpuConfiguration") - .field("supported_backends", &self.supported_backends) - .field("present_mode", &self.present_mode) - .field("power_preference", &self.power_preference) + .field("supported_backends", &supported_backends) + .field("present_mode", &present_mode) + .field( + "desired_maximum_frame_latency", + &desired_maximum_frame_latency, + ) + .field("power_preference", &power_preference) .finish_non_exhaustive() } } @@ -277,6 +298,8 @@ impl Default for WgpuConfiguration { present_mode: wgpu::PresentMode::AutoVsync, + desired_maximum_frame_latency: None, + power_preference: wgpu::util::power_preference_from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 780c9086..b028af60 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -4,7 +4,6 @@ use std::{borrow::Cow, num::NonZeroU64, ops::Range}; use epaint::{ahash::HashMap, emath::NumExt, PaintCallbackInfo, Primitive, Vertex}; -use wgpu; use wgpu::util::DeviceExt as _; /// You can use this for storage when implementing [`CallbackTrait`]. @@ -79,6 +78,7 @@ pub trait CallbackTrait: Send + Sync { &self, _device: &wgpu::Device, _queue: &wgpu::Queue, + _screen_descriptor: &ScreenDescriptor, _egui_encoder: &mut wgpu::CommandEncoder, _callback_resources: &mut CallbackResources, ) -> Vec { @@ -814,9 +814,10 @@ impl Renderer { }; if index_count > 0 { - crate::profile_scope!("indices"); + crate::profile_scope!("indices", index_count.to_string()); self.index_buffer.slices.clear(); + let required_index_buffer_size = (std::mem::size_of::() * index_count) as u64; if self.index_buffer.capacity < required_index_buffer_size { // Resize index buffer if needed. @@ -825,13 +826,16 @@ impl Renderer { self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity); } - let mut index_buffer_staging = queue - .write_buffer_with( - &self.index_buffer.buffer, - 0, - NonZeroU64::new(required_index_buffer_size).unwrap(), - ) - .expect("Failed to create staging buffer for index data"); + let index_buffer_staging = queue.write_buffer_with( + &self.index_buffer.buffer, + 0, + NonZeroU64::new(required_index_buffer_size).unwrap(), + ); + + let Some(mut index_buffer_staging) = index_buffer_staging else { + panic!("Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)", self.index_buffer.buffer.size(), self.index_buffer.capacity); + }; + let mut index_offset = 0; for epaint::ClippedPrimitive { primitive, .. } in paint_jobs { match primitive { @@ -848,9 +852,10 @@ impl Renderer { } } if vertex_count > 0 { - crate::profile_scope!("vertices"); + crate::profile_scope!("vertices", vertex_count.to_string()); self.vertex_buffer.slices.clear(); + let required_vertex_buffer_size = (std::mem::size_of::() * vertex_count) as u64; if self.vertex_buffer.capacity < required_vertex_buffer_size { // Resize vertex buffer if needed. @@ -860,13 +865,16 @@ impl Renderer { create_vertex_buffer(device, self.vertex_buffer.capacity); } - let mut vertex_buffer_staging = queue - .write_buffer_with( - &self.vertex_buffer.buffer, - 0, - NonZeroU64::new(required_vertex_buffer_size).unwrap(), - ) - .expect("Failed to create staging buffer for vertex data"); + let vertex_buffer_staging = queue.write_buffer_with( + &self.vertex_buffer.buffer, + 0, + NonZeroU64::new(required_vertex_buffer_size).unwrap(), + ); + + let Some(mut vertex_buffer_staging) = vertex_buffer_staging else { + panic!("Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)", self.vertex_buffer.buffer.size(), self.vertex_buffer.capacity); + }; + let mut vertex_offset = 0; for epaint::ClippedPrimitive { primitive, .. } in paint_jobs { match primitive { @@ -890,6 +898,7 @@ impl Renderer { user_cmd_bufs.extend(callback.prepare( device, queue, + screen_descriptor, encoder, &mut self.callback_resources, )); @@ -923,12 +932,19 @@ fn create_sampler( epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest, epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear, }; + let address_mode = match options.wrap_mode { + epaint::textures::TextureWrapMode::ClampToEdge => wgpu::AddressMode::ClampToEdge, + epaint::textures::TextureWrapMode::Repeat => wgpu::AddressMode::Repeat, + epaint::textures::TextureWrapMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat, + }; device.create_sampler(&wgpu::SamplerDescriptor { label: Some(&format!( "egui sampler (mag: {mag_filter:?}, min {min_filter:?})" )), mag_filter, min_filter, + address_mode_u: address_mode, + address_mode_v: address_mode, ..Default::default() }) } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 15460686..07c91715 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -73,7 +73,7 @@ impl BufferPadding { /// Everything you need to paint egui with [`wgpu`] on [`winit`]. /// -/// Alternatively you can use [`crate::renderer`] directly. +/// Alternatively you can use [`crate::Renderer`] directly. /// /// NOTE: all egui viewports share the same painter. pub struct Painter { @@ -142,7 +142,7 @@ impl Painter { fn configure_surface( surface_state: &SurfaceState, render_state: &RenderState, - present_mode: wgpu::PresentMode, + config: &WgpuConfiguration, ) { crate::profile_function!(); @@ -155,21 +155,25 @@ impl Painter { let width = surface_state.width; let height = surface_state.height; - surface_state.surface.configure( - &render_state.device, - &wgpu::SurfaceConfiguration { - // TODO(emilk): expose `desired_maximum_frame_latency` to eframe users - usage, - format: render_state.target_format, - present_mode, - alpha_mode: surface_state.alpha_mode, - view_formats: vec![render_state.target_format], - ..surface_state - .surface - .get_default_config(&render_state.adapter, width, height) - .expect("The surface isn't supported by this adapter") - }, - ); + let mut surf_config = wgpu::SurfaceConfiguration { + usage, + format: render_state.target_format, + present_mode: config.present_mode, + alpha_mode: surface_state.alpha_mode, + view_formats: vec![render_state.target_format], + ..surface_state + .surface + .get_default_config(&render_state.adapter, width, height) + .expect("The surface isn't supported by this adapter") + }; + + if let Some(desired_maximum_frame_latency) = config.desired_maximum_frame_latency { + surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency; + } + + surface_state + .surface + .configure(&render_state.device, &surf_config); } /// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`] @@ -328,7 +332,7 @@ impl Painter { surface_state.width = width; surface_state.height = height; - Self::configure_surface(surface_state, render_state, self.configuration.present_mode); + Self::configure_surface(surface_state, render_state, &self.configuration); if let Some(depth_format) = self.depth_format { self.depth_texture_view.insert( @@ -500,7 +504,10 @@ impl Painter { }) } - // Returns a vector with the frame's pixel data if it was requested. + /// Returns two things: + /// + /// The approximate number of seconds spent on vsync-waiting (if any), + /// and the captures captured screenshot if it was requested. pub fn paint_and_update_textures( &mut self, viewport_id: ViewportId, @@ -509,33 +516,16 @@ impl Painter { clipped_primitives: &[epaint::ClippedPrimitive], textures_delta: &epaint::textures::TexturesDelta, capture: bool, - ) -> Option { + ) -> (f32, Option) { crate::profile_function!(); - let render_state = self.render_state.as_mut()?; - let surface_state = self.surfaces.get(&viewport_id)?; + let mut vsync_sec = 0.0; - let output_frame = { - crate::profile_scope!("get_current_texture"); - // This is what vsync-waiting happens, at least on Mac. - surface_state.surface.get_current_texture() + let Some(render_state) = self.render_state.as_mut() else { + return (vsync_sec, None); }; - - let output_frame = match output_frame { - Ok(frame) => frame, - Err(err) => match (*self.configuration.on_surface_error)(err) { - SurfaceErrorAction::RecreateSurface => { - Self::configure_surface( - surface_state, - render_state, - self.configuration.present_mode, - ); - return None; - } - SurfaceErrorAction::SkipFrame => { - return None; - } - }, + let Some(surface_state) = self.surfaces.get(&viewport_id) else { + return (vsync_sec, None); }; let mut encoder = @@ -580,6 +570,28 @@ impl Painter { } }; + let output_frame = { + crate::profile_scope!("get_current_texture"); + // This is what vsync-waiting happens on my Mac. + let start = web_time::Instant::now(); + let output_frame = surface_state.surface.get_current_texture(); + vsync_sec += start.elapsed().as_secs_f32(); + output_frame + }; + + let output_frame = match output_frame { + Ok(frame) => frame, + Err(err) => match (*self.configuration.on_surface_error)(err) { + SurfaceErrorAction::RecreateSurface => { + Self::configure_surface(surface_state, render_state, &self.configuration); + return (vsync_sec, None); + } + SurfaceErrorAction::SkipFrame => { + return (vsync_sec, None); + } + }, + }; + { let renderer = render_state.renderer.read(); let frame_view = if capture { @@ -589,8 +601,11 @@ impl Painter { render_state, ); self.screen_capture_state - .as_ref()? - .texture + .as_ref() + .map_or_else( + || &output_frame.texture, + |capture_state| &capture_state.texture, + ) .create_view(&wgpu::TextureViewDescriptor::default()) } else { output_frame @@ -654,23 +669,33 @@ impl Painter { // Submit the commands: both the main buffer and user-defined ones. { crate::profile_scope!("Queue::submit"); + // wgpu doesn't document where vsync can happen. Maybe here? + let start = web_time::Instant::now(); render_state .queue .submit(user_cmd_bufs.into_iter().chain([encoded])); + vsync_sec += start.elapsed().as_secs_f32(); }; let screenshot = if capture { - let screen_capture_state = self.screen_capture_state.as_ref()?; - Self::read_screen_rgba(screen_capture_state, render_state, &output_frame) + self.screen_capture_state + .as_ref() + .and_then(|screen_capture_state| { + Self::read_screen_rgba(screen_capture_state, render_state, &output_frame) + }) } else { None }; { crate::profile_scope!("present"); + // wgpu doesn't document where vsync can happen. Maybe here? + let start = web_time::Instant::now(); output_frame.present(); + vsync_sec += start.elapsed().as_secs_f32(); } - screenshot + + (vsync_sec, screenshot) } pub fn gc_viewports(&mut self, active_viewports: &ViewportIdSet) { diff --git a/crates/ui/src/windows/script_edit.rs b/crates/ui/src/windows/script_edit.rs index e9011576..eeaf3fb8 100644 --- a/crates/ui/src/windows/script_edit.rs +++ b/crates/ui/src/windows/script_edit.rs @@ -77,19 +77,18 @@ impl luminol_core::Window for Window { let scripts_len = scripts.data.len(); for (index, script) in scripts.data.iter_mut().enumerate() { - let response = ui - .text_edit_singleline(&mut script.name) - .context_menu(|ui| { - if ui.button("Insert").clicked() { - insert_index = Some(index); + let response = ui.text_edit_singleline(&mut script.name); + response.context_menu(|ui| { + if ui.button("Insert").clicked() { + insert_index = Some(index); + } + + ui.add_enabled_ui(scripts_len > 1, |ui| { + if ui.button("Delete").clicked() { + del_index = Some(index); } - - ui.add_enabled_ui(scripts_len > 1, |ui| { - if ui.button("Delete").clicked() { - del_index = Some(index); - } - }); }); + }); if response.double_clicked() { self.tabs