`capture_backdrop` copied the whole target into the backdrop texture for every
callback that asked for one, so a panel-sized blur on a large screen paid for
the entire frame, once per blur.
Ask the callback what it needs instead: `CallbackTrait::backdrop_rect` is given
the rect the callback will be drawn into, its own rect already cut down by the
clip rect, and returns the region it needs to read. Those are different
questions — a blur reads its radius beyond every edge it draws — and keeping
them apart is what lets the margin survive clipping, which inflating the
callback's own rect could not do: that rect is also the viewport it draws into.
`Backdrop::valid_in_pixels` reports what was captured. The texture stays
full-size so a pixel is still where the effect expects it; only the region that
was asked for holds this frame.
The default answer is the rect the callback is drawn into, which is right for an
effect that only reads the pixels it covers, so nothing has to change unless it
reads further out.
With nine blurred panels of 240x150 at radius 24 on a 1600x1200 canvas, this
plus scissoring the blur passes in regui takes the frame rate from 146 to 1690
fps on WebGPU and 135 to 616 on WebGL2, measured in Chrome; the marginal cost of
one blur drops about twentyfold. egui_kittest's snapshots of the same blurs come
out pixel for pixel identical.
The web painter called `Renderer::render`, which tells the renderer that no
backdrop can be captured, so a callback asking for one silently fell back to
`CallbackTrait::paint` and drew nothing. A blur behind a panel worked natively
and came out as plain glass in a browser.
Do on web what the winit painter already does: when a callback needs a
backdrop, render into our own texture rather than straight into the canvas
surface, which cannot be sampled, then run the
`render_from` / `capture_backdrop` loop and blit the result onto the surface.
The screen capture machinery is the same shape, so this reuses it, and frames
with no backdrop effect still go straight into the surface with no blit.
Checked in Chrome on both backends, WebGPU and WebGL2.
We had a ton of issues around `TexturesDelta` that weren't properly
applied because we early-out of some function:
* https://github.com/emilk/egui/pull/8313
* https://github.com/emilk/egui/pull/8250
* https://github.com/emilk/egui/pull/8279
This PR changes texture updates, so that we always store them after
taking them out of `FullOutput` and keep the delta around until it's
actually applied (by passing &mut refs and draining instead of
iterating). So even if we add a new early return somewhere, that can't
break texture updates.
It also optimizes `TexturesDelta::append` by dropping any previous
deltas if there's a new `whole` delta or a `free`.
It also adds a debug assert that any `TexturesDelta` is empty when
dropped, as an additional safeguard in case the bug sneaks back in.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary
- [x] I have followed the instructions in the PR template
Fixes comment-style violations of the `CONTRIBUTING.md` code style rule:
> `// Comment like this.` and not `//like this`
### Files changed
- `crates/epaint/src/shapes/bezier_shape.rs`: `//temporary solution` →
`// temporary solution`, and 22× `//add the start point` → `// add the
start point`
- `crates/egui/src/animation_manager.rs`: `//start new animation…` → `//
start new animation…`
- `crates/eframe/src/web/web_painter_wgpu.rs`:
`//create_new.instance_descriptor…` → `//
create_new.instance_descriptor…`
No code logic changes — only adding the missing space after `//` in
inline and standalone comments.
## Test plan
- [x] `cargo fmt --check`
- [x] `cargo clippy -p epaint -p egui`
- [x] `cargo test -p epaint --lib`
### Related
* Closes#8134.
* Related to #5136.
Possibly fixes:
* #8123
* #5145
### What
We did not properly handle the variants of
[`CurrentSurfaceTexture`](https://docs.rs/wgpu/latest/wgpu/enum.CurrentSurfaceTexture.html)
and always returned `SkipFrame`.
Because of this `egui` could end up in a state where frames are always
skipped after observing `Outdated`, without the chance to recover
(unless an event arrives from the outside).
> [!NOTE]
> This is not Wayland-specific, but could happen on all platforms. It
just happens frequently for Wayland compositors that directly resize a
window after creation (such as tiling/scrolling compositors like
`hyprland` and `niri`).
This PR improves this by separating the code paths for `Outdated` and
`Lost`, to help recover from those events.
Let apps change present_mode and desired_maximum_frame_latency at
runtime instead of only at startup.
API changes (egui-wgpu):
- New SurfaceConfig { present_mode, desired_maximum_frame_latency }.
- WgpuConfiguration now nests these as pub surface: SurfaceConfig (was
two top-level fields).
- RenderState gains pub surface_config: SurfaceConfig — the
currently-requested value.
API additions (eframe):
- Frame::wgpu_surface_config() / Frame::set_wgpu_surface_config(...) for
get/set.
- SurfaceConfig re-exported as eframe::SurfaceConfig.
How it works:
The wgpu painter compares render_state.surface_config to its
currently-applied values each paint. If they differ it updates its
config and flips
needs_reconfigure on every surface, piggybacking on the existing
deferred-reconfigure pathway.
Demo:
The backend panel (egui_demo_app) gets dropdowns for present mode and
desired max frame latency, wired through the new Frame accessors.
<img width="282" height="172" alt="image"
src="https://github.com/user-attachments/assets/0b1274b2-7e4e-4413-969b-0a014c415f79"
/>
* [x] I have followed the instructions in the PR template
This updates wgpu to v29 across the egui crate stack.
There a a few API changes due to the requirement to provide a display
handle up front to properly support GLES on linux. I have done my best
to make the api changes as reasonable as possible, but I don't have all
the greater project context, so lmk if things should be done a bit
differently.
I've also updated glow to 0.17 to make cargo deny happy, there are no
source changes. I'm not sure how you want to land these.
---------
Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
* Closes https://github.com/emilk/egui/issues/5889
See the above issue for motivation.
To use glow instead, disable the default features of `eframe` and opt-in
to `glow`.
This also changes egui.rs to use wgpu, which means WebGPU when
available, and WebGL otherwise
This implements web support for taking screenshots in an eframe app (and
adds a nice demo).
It also updates the native screenshot implementation to work with the
wgpu gl backend.
The wgpu implementation is quite different than the native one because
we can't block to wait for the screenshot result, so instead I use a
channel to pass the result to a future frame asynchronously.
* Closes <https://github.com/emilk/egui/issues/5425>
* [x] I have followed the instructions in the PR template
https://github.com/user-attachments/assets/67cad40b-0384-431d-96a3-075cc3cb98fb
When mixing and matching eframe with other wgpu applications
(https://github.com/tracel-ai/burn in my case), it can be helpful to use
an existing wgpu setup to initialize eframe with. This PR changes the
WpuConfiguration (in a non-backwards compat way :/), to either take some
options how to create a wgpu setup, or an existing wgpu setup
(consisting of an instance, adapter, device and queue).
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Andreas Reich <r_andreas2@web.de>
A very common usability issue on egui-wgpu callbacks is that `paint`
can't access any data that doesn't strictly outlive the callback
resources' data. E.g. if the callback resources have an `Arc` to some
resource manager, you can't easily pull out resources since you
statically needed to ensure that those resource references outlived the
renderpass, whose lifetime was only constrained to the callback
resources themselves.
Wgpu 22 no longer has this restriction! Its (render/compute-)passes take
care of the lifetime of any passed resource internally. The lifetime
constraint is _still_ opt-out since it protects from a common runtime
error of adding commands/passes on the parent encoder while a previously
created pass wasn't closed yet.
This is not a concern in egui-wgpu since the paint method where we have
to access the render pass doesn't even have access to the encoder!
Previously, any frames in flight (`requestAnimationFrame`) on web were
not being cancelled (`cancelAnimationFrame`) when `WebRunner::destroy`
was called. If a user called `destroy`, then immediately removed the
canvas from the DOM, eframe could panic with a "failed to find (canvas)
element by id" error message.
This PR changes two things:
- The canvas element is directly referenced everywhere it's needed
instead of being looked up by `canvas_id`[^1]
- The RAF handle is stored in `WebRunner` and `cancelAnimationFrame` is
called on it inside of `WebRunner::destroy`[^2]
[^1]: The WebGL/WGPU backends were already holding onto the canvas (and
associated GPU context), so the change is just converting all the
`get_element_by_id` lookups to retrieve the canvas from the web runner
handle.
[^2]: There is only ever one frame in flight, so we store it directly as
a scalar field.
* Closes#1044
---
(new PR description written by @emilk)
## Overview
This PR introduces the concept of `Viewports`, which on the native
eframe backend corresponds to native OS windows.
You can spawn a new viewport using `Context::show_viewport` and
`Cotext::show_viewport_immediate`.
These needs to be called every frame the viewport should be visible.
This is implemented by the native `eframe` backend, but not the web one.
## Viewport classes
The viewports form a tree of parent-child relationships.
There are different classes of viewports.
### Root vieport
The root viewport is the original viewport, and cannot be closed without
closing the application.
### Deferred viewports
These are created with `Context::show_viewport`.
Deferred viewports take a closure that is called by the integration at a
later time, perhaps multiple times.
Deferred viewports are repainted independenantly of the parent viewport.
This means communication with them need to done via channels, or
`Arc/Mutex`.
This is the most performant type of child viewport, though a bit more
cumbersome to work with compared to immediate viewports.
### Immediate viewports
These are created with `Context::show_viewport_immediate`.
Immediate viewports take a `FnOnce` closure, similar to other egui
functions, and is called immediately. This makes communication with them
much simpler than with deferred viewports, but this simplicity comes at
a cost: whenever tha parent viewports needs to be repainted, so will the
child viewport, and vice versa. This means that if you have `N`
viewports you are poentially doing `N` times as much CPU work. However,
if all your viewports are showing animations, and thus are repainting
constantly anyway, this doesn't matter.
In short: immediate viewports are simpler to use, but can waste a lot of
CPU time.
### Embedded viewports
These are not real, independenant viewports, but is a fallback mode for
when the integration does not support real viewports. In your callback
is called with `ViewportClass::Embedded` it means you need to create an
`egui::Window` to wrap your ui in, which will then be embedded in the
parent viewport, unable to escape it.
## Using the viewports
Only one viewport is active at any one time, identified wth
`Context::viewport_id`.
You can send commands to other viewports using
`Context::send_viewport_command_to`.
There is an example in
<https://github.com/emilk/egui/tree/master/examples/multiple_viewports/src/main.rs>.
## For integrations
There are several changes relevant to integrations.
* There is a [`crate::RawInput::viewport`] with information about the
current viewport.
* The repaint callback set by `Context::set_request_repaint_callback`
now points to which viewport should be repainted.
* `Context::run` now returns a list of viewports in `FullOutput` which
should result in their own independant windows
* There is a new `Context::set_immediate_viewport_renderer` for setting
up the immediate viewport integration
* If you support viewports, you need to call
`Context::set_embed_viewports(false)`, or all new viewports will be
embedded (the default behavior).
## Future work
* Make it easy to wrap child viewports in the same chrome as
`egui::Window`
* Automatically show embedded viewports using `egui::Window`
* Use the new `ViewportBuilder` in `eframe::NativeOptions`
* Automatically position new viewport windows (they currently cover each
other)
* Add a `Context` method for listing all existing viewports
Find more at https://github.com/emilk/egui/issues/3556
---
<details>
<summary>
Outdated PR description by @konkitoman
</summary>
## Inspiration
- Godot because the app always work desktop or single_window because of
embedding
- Dear ImGui viewport system
## What is a Viewport
A Viewport is a egui isolated component!
Can be used by the egui integration to create native windows!
When you create a Viewport is possible that the backend do not supports
that!
So you need to check if the Viewport was created or you are in the
normal egui context!
This is how you can do that:
```rust
if ctx.viewport_id() != ctx.parent_viewport_id() {
// In here you add the code for the viewport context, like
egui::CentralPanel::default().show(ctx, |ui|{
ui.label("This is in a native window!");
});
}else{
// In here you add the code for when viewport cannot be created!
// You cannot use CentralPanel in here because you will override the app CentralPanel
egui::Window::new("Virtual Viewport").show(ctx, |ui|{
ui.label("This is without a native window!\nThis is in a embedded viewport");
});
}
```
This PR do not support for drag and drop between Viewports!
After this PR is accepted i will begin work to intregrate the Viewport
system in `egui::Window`!
The `egui::Window` i want to behave the same on desktop and web
The `egui::Window` will be like Godot Window
## Changes and new
These are only public structs and functions!
<details>
<summary>
## New
</summary>
- `egui::ViewportId`
- `egui::ViewportBuilder`
This is like winit WindowBuilder
- `egui::ViewportCommand`
With this you can set any winit property on a viewport, when is a native
window!
- `egui::Context::new`
- `egui::Context::create_viewport`
- `egui::Context::create_viewport_sync`
- `egui::Context::viewport_id`
- `egui::Context::parent_viewport_id`
- `egui::Context::viewport_id_pair`
- `egui::Context::set_render_sync_callback`
- `egui::Context::is_desktop`
- `egui::Context::force_embedding`
- `egui::Context::set_force_embedding`
- `egui::Context::viewport_command`
- `egui::Context::send_viewport_command_to`
- `egui::Context::input_for`
- `egui::Context::input_mut_for`
- `egui::Context::frame_nr_for`
- `egui::Context::request_repaint_for`
- `egui::Context::request_repaint_after_for`
- `egui::Context::requested_repaint_last_frame`
- `egui::Context::requested_repaint_last_frame_for`
- `egui::Context::requested_repaint`
- `egui::Context::requested_repaint_for`
- `egui::Context::inner_rect`
- `egui::Context::outer_rect`
- `egui::InputState::inner_rect`
- `egui::InputState::outer_rect`
- `egui::WindowEvent`
</details>
<details>
<summary>
## Changes
</summary>
- `egui::Context::run`
Now needs the viewport that we want to render!
- `egui::Context::begin_frame`
Now needs the viewport that we want to render!
- `egui::Context::tessellate`
Now needs the viewport that we want to render!
- `egui::FullOutput`
```diff
- repaint_after
+ viewports
+ viewport_commands
```
- `egui::RawInput`
```diff
+ inner_rect
+ outer_rect
```
- `egui::Event`
```diff
+ WindowEvent
```
</details>
### Async Viewport
Async means that is independent from other viewports!
Is created by `egui::Context::create_viewport`
To be used you will need to wrap your state in `Arc<RwLock<T>>`
Look at viewports example to understand how to use it!
### Sync Viewport
Sync means that is dependent on his parent!
Is created by `egui::Context::create_viewport_sync`
This will pause the parent then render itself the resumes his parent!
#### ⚠️ This currently will make the fps/2 for every sync
viewport
### Common
#### ⚠️ Attention
You will need to do this when you render your content
```rust
ctx.create_viewport(ViewportBuilder::new("Simple Viewport"), | ctx | {
let content = |ui: &mut egui::Ui|{
ui.label("Content");
};
// This will make the content a popup if cannot create a native window
if ctx.viewport_id() != ctx.parent_viewport_id() {
egui::CentralPanel::default().show(ctx, content);
} else {
egui::Area::new("Simple Viewport").show(ctx, |ui| {
egui::Frame::popup(ui.style()).show(ui, content);
});
};
});
````
## What you need to know as egui user
### If you are using eframe
You don't need to change anything!
### If you have a manual implementation
Now `egui::run` or `egui::begin` and `egui::tessellate` will need the
current viewport id!
You cannot create a `ViewportId` only `ViewportId::MAIN`
If you make a single window app you will set the viewport id to be
`egui::ViewportId::MAIN` or see the `examples/pure_glow`
If you want to have multiples window support look at `crates/eframe`
glow or wgpu implementations!
## If you want to try this
- cargo run -p viewports
## This before was wanted to change
This will probably be in feature PR's
### egui::Window
To create a native window when embedded was set to false
You can try that in viewports example before:
[78a0ae8](78a0ae879e)
### egui popups, context_menu, tooltip
To be a native window
</details>
---------
Co-authored-by: Konkitoman <konkitoman@users.noreply.github.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Co-authored-by: Pablo Sichert <mail@pablosichert.com>
Tested on M1 Mac:
* native
* webgl, firefox
* webgpu, chrome
all looking normal
Updated minor ahash version because 0.8.1 got yanked. Added some deny
exceptions for now - we'll have to update winit soon to resolve glow
related cargo deny errors (not a big issue though since we don't expect
wgpu and glow backends to be used at the same time)
* Make wgpu webgl/gles opt-in (but still work out of the box via feature flag), workaround canvas creation issue
* missing allow unsafe code annotations
* add breaking change not to eframe release notes
* Add --webgpu flag to build_demo_web.sh
* Improve CHANGELOG docs
* Clean up, and prepare for having to wasm:s
* put canvas without workaround under `if false`
* fix spelling
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* Replace tracing crate with log
It's just so much simpler to use
* Add `bacon wasm` job
* eframe: add a WebLogger for piping log events to the web console
* Clear color values are not explicitely sent to the rendering backend as-is.
Previously, converting from Color32 to Rgba caused an srgb->linear conversion. This conversion is incorrect if the backbuffer doesn't perform automatic conversion from linear->srgb (lack of this conversion is generally what egui assumes!).
* fill in pr numbers in changelog
* Epi comment fix
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* Color32 comment fix
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* move changelog line
* rename fix
* use backticks in doc
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* Add depth stencil initialization to `Painter`
* Move depth stencil initialization into `resize_and_generate_depth_texture_view`, and call it in `set_window` and `on_window_resized`
* Allow for depth texture in WASM builds
* change `map` to `if let` statement
* use reference for render state
* Clean up descriptors and move texture generation to on resize events
* remove unused field from WebOptions
* introduce new wgpu configuration option to allow configuring wgpu renderer
* use new options with wgpu web painter
* use on_surface_error callback
* changelog update
* cleanup
* changelog and comment fixes