mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Fix random hangs by improving wgpu::Surface lifecycle handling (#8171)
### 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.
This commit is contained in:
@@ -12,6 +12,7 @@ use super::web_painter::WebPainter;
|
||||
|
||||
pub(crate) struct WebPainterWgpu {
|
||||
canvas: HtmlCanvasElement,
|
||||
instance: wgpu::Instance,
|
||||
surface: wgpu::Surface<'static>,
|
||||
surface_configuration: wgpu::SurfaceConfiguration,
|
||||
render_state: Option<RenderState>,
|
||||
@@ -23,6 +24,7 @@ pub(crate) struct WebPainterWgpu {
|
||||
capture_rx: CaptureReceiver,
|
||||
ctx: egui::Context,
|
||||
needs_reconfigure: bool,
|
||||
needs_recreate: bool,
|
||||
}
|
||||
|
||||
/// Owned web display handle that is `Send + Sync`.
|
||||
@@ -129,6 +131,7 @@ impl WebPainterWgpu {
|
||||
|
||||
Ok(Self {
|
||||
canvas,
|
||||
instance,
|
||||
render_state: Some(render_state),
|
||||
surface,
|
||||
surface_configuration,
|
||||
@@ -140,6 +143,7 @@ impl WebPainterWgpu {
|
||||
capture_rx,
|
||||
ctx,
|
||||
needs_reconfigure: false,
|
||||
needs_recreate: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -173,6 +177,24 @@ impl WebPainter for WebPainterWgpu {
|
||||
));
|
||||
};
|
||||
|
||||
// If the previous frame produced `CurrentSurfaceTexture::Lost`, drop and recreate the
|
||||
// surface from the canvas before re-borrowing `self.render_state` for the rest of paint.
|
||||
if self.needs_recreate {
|
||||
self.needs_recreate = false;
|
||||
match self
|
||||
.instance
|
||||
.create_surface(wgpu::SurfaceTarget::Canvas(self.canvas.clone()))
|
||||
{
|
||||
Ok(new_surface) => {
|
||||
new_surface.configure(&render_state.device, &self.surface_configuration);
|
||||
self.surface = new_surface;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to recreate wgpu surface for canvas: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut encoder =
|
||||
render_state
|
||||
.device
|
||||
@@ -239,10 +261,18 @@ impl WebPainter for WebPainterWgpu {
|
||||
}
|
||||
other => {
|
||||
match (*self.on_surface_status)(&other) {
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
SurfaceErrorAction::Reconfigure => {
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
}
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
// Full recovery needs `&mut self`, which conflicts with the live
|
||||
// `render_state` / `self.surface` borrows here. Defer to the top
|
||||
// of the next paint via the `needs_recreate` flag, and request a
|
||||
// repaint so the next frame actually invokes `paint` to consume it.
|
||||
self.needs_recreate = true;
|
||||
self.ctx.request_repaint();
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame => {}
|
||||
}
|
||||
return Ok(());
|
||||
|
||||
Reference in New Issue
Block a user