Skip to content

eframe: keep running on Wayland when frame callbacks stop - #8631

Open
crmne wants to merge 1 commit into
emilk:mainfrom
crmne:eframe-wayland-frame-pacing
Open

crmne wants to merge 1 commit into
emilk:mainfrom
crmne:eframe-wayland-frame-pacing

Conversation

@crmne

@crmne crmne commented Sep 25, 2026

Copy link
Copy Markdown

Part of #5136.

On Wayland a compositor may stop sending frame callbacks to a window it isn't showing, without telling the client that the window is occluded. Hyprland does this when another window on the same workspace goes fullscreen or maximized. With the glow backend, eframe then blocks in a vsync eglSwapBuffers until the window is shown again. Nothing runs, pings go unanswered, and the compositor reports the app as not responding. With wgpu, pre_present_notify keeps eframe from blocking, but RedrawRequested never arrives, so App::logic stops running.

In rust-windowing/winit#4668 the winit maintainers said that winit should keep holding RedrawRequested until the frame callback arrives, and that a timeout belongs in the toolkit. This PR adds that timeout:

  • glow calls Window::pre_present_notify before swapping, like egui-wgpu already does, so winit paces RedrawRequested by frame callbacks.
  • On Wayland, glow swaps with interval 0. The frame callbacks already pace it. With the NVIDIA driver (egl-wayland2), a vsync swap also blocks forever for a window that starts on a hidden workspace: the compositor sends a frame callback for the first buffer but never presents it, and the next swap waits for that buffer.
  • If a RedrawRequested we asked for hasn't arrived within 250 ms, eframe runs App::logic without an egui pass, the same as for other hidden windows. Once the compositor sends a frame callback again, the window paints normally.

glow_options.vsync therefore has no effect on Wayland. That's what vsync means there anyway, and the docs now say so.

Testing

I tested with a small probe that calls request_repaint on every App::logic call and counts logic and ui calls. It ran on Hyprland 0.56.2 with NVIDIA and a headless output, and I counted pings and pongs with WAYLAND_DEBUG=1. Each phase lasted 10 s:

Scenario Before After
Visible (glow and wgpu) 60 fps 60 fps
Covered by a fullscreen window, glow blocked in eglSwapBuffers, 0 pings answered about 4 logic/s, no paints, all pings answered
Covered by a maximized window, glow same (Hyprland stops the callbacks) same as above
Covered, wgpu responsive, but 0 logic calls about 4 logic/s
Window on a hidden workspace glow blocks about 4 logic/s, all pings answered
Started on a hidden workspace, glow blocked on the second swap about 4 logic/s, all pings answered
Shown again resumes resumes on the next frame callback
Idle 0 CPU 0 CPU

X11 and other platforms: I only checked that they compile. Nothing changes there except that run_logic may run if a RedrawRequested takes more than 250 ms to arrive, which only calls App::logic one extra time.

When winit reports Occluded on Wayland (rust-windowing/winit#4709), eframe's existing occluded handling takes over for hidden workspaces. This timeout still covers windows the compositor hides without suspending them.

  • I have followed the instructions in the PR template

On Wayland, a compositor may stop sending frame callbacks to a window that
is not shown, for example one covered by a fullscreen window on the same
workspace, without telling the client it is occluded. The glow backend then
blocked in a vsync `eglSwapBuffers` until the window was shown again: no
input, no `App::logic`, and unanswered pings, so the compositor reports the
app as not responding. The wgpu backend already waits for frame callbacks
through `pre_present_notify`, so it did not block, but `App::logic` stopped
running.

* glow calls `Window::pre_present_notify` before swapping, like wgpu, so
  winit paces `RedrawRequested` by frame callbacks.
* On Wayland, glow swaps with interval 0: frame callbacks already pace it,
  and with the NVIDIA driver a vsync swap blocks forever for a window that
  starts on a hidden workspace.
* If a requested `RedrawRequested` has not arrived within 250 ms, eframe
  runs `App::logic` without an egui pass, as it does for other hidden
  windows, and paints normally once the compositor asks for a frame.
@github-actions

Copy link
Copy Markdown

Preview is being built...

Preview will be available at https://egui-pr-preview.github.io/pr/8631-eframe-wayland-frame-pacing

View snapshot changes at kitdiff

crmne added a commit to crmne/zapfast that referenced this pull request Sep 25, 2026
Hyprland stops frame callbacks to a window covered by a fullscreen or
maximized one without marking it suspended, so a vsync swap blocked the
event loop until the compositor called ZapFast unresponsive (#190).

Pin the shared forks: crmne/egui apps-0.36 presents without a blocking
swap on Wayland, paces frames by frame callbacks, and runs only the app
logic when a redraw is 250 ms overdue (emilk/egui#8631); crmne/winit
apps-0.30 reports suspended as Occluded and commits on the app's behalf
(rust-windowing/winit#4709). vsync.rs's compositor check goes: vsync is
requested everywhere and eframe handles Wayland.

Fixes #190
crmne added a commit to crmne/chatwithwork-local-agent that referenced this pull request Sep 25, 2026
Hyprland sends no frame callbacks to a window covered by a fullscreen or
maximized one, on a hidden workspace, or with the display off, so a
vsync swap blocked the event loop the tray shares and the compositor
called the app unresponsive.

Pin the forks ZapFast, Spotifast, RekordFlash and TonePush share:
crmne/egui apps-0.36 paces Wayland frames by frame callbacks
(emilk/egui#8631) and crmne/winit apps-0.30 reports suspended as
Occluded (rust-windowing/winit#4709). egui_kittest comes from the same
revision so the tests share one egui.
crmne added a commit to crmne/chatwithwork-local-agent that referenced this pull request Sep 25, 2026
Hyprland sends no frame callbacks to a window covered by a fullscreen or
maximized one, on a hidden workspace, or with the display off, so a
vsync swap blocked the event loop the tray shares and the compositor
called the app unresponsive.

Pin the forks ZapFast, Spotifast, RekordFlash and TonePush share:
crmne/egui apps-0.36 paces Wayland frames by frame callbacks
(emilk/egui#8631) and crmne/winit apps-0.30 reports suspended as
Occluded (rust-windowing/winit#4709). egui_kittest comes from the same
revision so the tests share one egui.
crmne added a commit to crmne/chatwithwork-local-agent that referenced this pull request Sep 25, 2026
Hyprland sends no frame callbacks to a window covered by a fullscreen or
maximized one, on a hidden workspace, or with the display off, so a
vsync swap blocked the event loop the tray shares and the compositor
called the app unresponsive.

Pin the forks ZapFast, Spotifast, RekordFlash and TonePush share:
crmne/egui apps-0.36 paces Wayland frames by frame callbacks
(emilk/egui#8631) and crmne/winit apps-0.30 reports suspended as
Occluded (rust-windowing/winit#4709). egui_kittest comes from the same
revision so the tests share one egui.
@crmne
crmne marked this pull request as ready for review September 26, 2026 10:10
aquamoth added a commit to aquamoth/parterre that referenced this pull request Sep 26, 2026
…r freezes the app

On Wayland sessions (WAYLAND_DISPLAY/WAYLAND_SOCKET set, or XDG_SESSION_TYPE=wayland) glow
vsync is turned off before run_native, and App::ui sleeps away what is left of 8 ms since the
previous frame. A vsync'ed EGL swap of a minimized window waits forever for a frame callback
and blocks every viewport (egui#5145). Automated runs keep vsync off there but skip the cap.
X11, Windows and macOS are unchanged. Remove the cap once emilk/egui#8631 is released.

Closes #38.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant