mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Don't busy-loop a CPU core while waiting for a redraw (#8398)
Closes #8326 `check_redraw_requests` switched the event loop to `ControlFlow::Poll` every time it called `request_redraw`, and only ever restored a sleeping control flow when a *timed* repaint was still pending. Once the last scheduled repaint had been consumed the `Poll` was never undone, so the loop kept spinning. This is most visible on Wayland, where `RedrawRequested` is only delivered after the compositor sends a frame callback: between the request and the callback eframe burns 100% of a CPU core, so simply moving the mouse over a reactive app pegs a core. `request_redraw` already wakes the event loop on its own, so the `Poll` is not needed. Drop it, and always set an explicit sleeping control flow at the end of `check_redraw_requests`: `WaitUntil` for the earliest scheduled repaint, `Wait` when nothing is scheduled. **Measured effect of this patch** Two byte-identical eframe apps (a 400-row scrolling page, free-running at 60fps), toggling only whether `eframe` resolves to stock 0.36.0 or this patch. Same machine and session, native Wayland (niri, wgpu/Vulkan). Whole-process CPU is `utime+stime` from `/proc/self/stat`, so it counts every thread — what a system monitor sees. | configuration | whole process | |---|---| | eframe 0.34.3, Wayland | ~12% of a core | | stock 0.36.0, Wayland | **99–100% of a core** | | stock 0.36.0, XWayland (same binary) | ~15% of a core | | **0.36.0 + this patch, Wayland** | **15–16% of a core** | The patch restores the 0.34 baseline and matches the XWayland figure for the same binary — ~6.5× less CPU — with frame delivery unchanged at 60fps. Two details worth noting: the same 0.36 binary is already fine on XWayland, so this isn't application repaint behaviour; and the per-frame *closure* cost rises slightly (1.55 to 2.15 ms) because those frames now run on a CPU that isn't being held at max clocks by the spin loop. * [X] I have followed the instructions in the PR template
This commit is contained in:
@@ -207,7 +207,12 @@ impl<T: WinitApp> WinitAppWrapper<T> {
|
|||||||
invisible_window_ids.push(*window_id);
|
invisible_window_ids.push(*window_id);
|
||||||
} else {
|
} else {
|
||||||
log::trace!("request_redraw for {window_id:?}");
|
log::trace!("request_redraw for {window_id:?}");
|
||||||
event_loop.set_control_flow(ControlFlow::Poll);
|
// Don't switch to `ControlFlow::Poll` here. `request_redraw`
|
||||||
|
// is enough to wake the event loop, and on Wayland the
|
||||||
|
// `RedrawRequested` event is only delivered once the
|
||||||
|
// compositor sends a frame callback. Polling in the meantime
|
||||||
|
// busy-loops a whole CPU core.
|
||||||
|
// See https://github.com/emilk/egui/issues/8326.
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -237,10 +242,16 @@ impl<T: WinitApp> WinitAppWrapper<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always set an explicit, sleeping control flow. Previously we only set
|
||||||
|
// `WaitUntil` when a repaint was already scheduled, which meant that a
|
||||||
|
// `ControlFlow::Poll` set earlier was never undone once the last timed
|
||||||
|
// repaint had been consumed, leaving the loop spinning.
|
||||||
|
// See https://github.com/emilk/egui/issues/8326.
|
||||||
let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
|
let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
|
||||||
if let Some(next_repaint_time) = next_repaint_time {
|
event_loop.set_control_flow(match next_repaint_time {
|
||||||
event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
|
Some(next_repaint_time) => ControlFlow::WaitUntil(next_repaint_time),
|
||||||
}
|
None => ControlFlow::Wait,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user