Skip to content

Commit f344727

Browse files
committed
Throttle invisible window repaints to prevent high CPU usage
Schedule a 100ms heartbeat for invisible windows so they keep processing viewport commands (e.g. Visible(true)) without spinning the event loop. Also skip ControlFlow::Poll for invisible windows and throttle their RequestRepaint events. Closes #7776
1 parent d4904d1 commit f344727

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

crates/eframe/src/native/glow_integration.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,9 +744,11 @@ impl GlowWinitRunning<'_> {
744744

745745
integration.maybe_autosave(app.as_mut(), Some(&window));
746746

747-
if window.is_minimized() == Some(true) {
747+
if window.is_minimized() == Some(true) || window.is_visible() == Some(false) {
748748
// On Mac, a minimized Window uses up all CPU:
749749
// https://github.com/emilk/egui/issues/325
750+
// On Windows, an invisible window also uses up all CPU:
751+
// https://github.com/emilk/egui/issues/7776
750752
profiling::scope!("minimized_sleep");
751753
std::thread::sleep(std::time::Duration::from_millis(10));
752754
}

crates/eframe/src/native/run.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,19 +185,18 @@ impl<T: WinitApp> WinitAppWrapper<T> {
185185
return true; // not yet ready
186186
}
187187

188-
event_loop.set_control_flow(ControlFlow::Poll);
189-
190188
if let Some(window) = self.winit_app.window(*window_id) {
191-
log::trace!("request_redraw for {window_id:?}");
192-
window.request_redraw();
193-
194189
// On Windows, invisible windows don't receive RedrawRequested
195190
// events, so pending viewport commands (e.g. Visible(true)) would
196191
// never be processed. We collect these windows to paint them
197192
// directly below.
198193
// See: https://github.com/emilk/egui/issues/5229
199194
if window.is_visible() == Some(false) {
200195
invisible_window_ids.push(*window_id);
196+
} else {
197+
log::trace!("request_redraw for {window_id:?}");
198+
event_loop.set_control_flow(ControlFlow::Poll);
199+
window.request_redraw();
201200
}
202201
} else {
203202
log::trace!("No window found for {window_id:?}");
@@ -208,11 +207,24 @@ impl<T: WinitApp> WinitAppWrapper<T> {
208207
// Paint invisible windows directly, since they won't receive
209208
// RedrawRequested events on Windows. This ensures that viewport
210209
// commands like Visible(true) are still processed.
211-
for window_id in invisible_window_ids {
212-
let event_result = self.winit_app.run_ui_and_paint(event_loop, window_id);
210+
for window_id in &invisible_window_ids {
211+
let event_result = self.winit_app.run_ui_and_paint(event_loop, *window_id);
213212
self.handle_event_result(event_loop, event_result);
214213
}
215214

215+
// Schedule a throttled repaint for invisible windows so they keep
216+
// processing viewport commands even when egui doesn't request repaints.
217+
// See: https://github.com/emilk/egui/issues/7776
218+
if !invisible_window_ids.is_empty() {
219+
let next_paint = Instant::now() + std::time::Duration::from_millis(100);
220+
for window_id in &invisible_window_ids {
221+
self.windows_next_repaint_times
222+
.entry(*window_id)
223+
.and_modify(|t| *t = (*t).min(next_paint))
224+
.or_insert(next_paint);
225+
}
226+
}
227+
216228
let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
217229
if let Some(next_repaint_time) = next_repaint_time {
218230
event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
@@ -289,6 +301,16 @@ impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
289301
if let Some(window_id) =
290302
self.winit_app.window_id_from_viewport_id(viewport_id)
291303
{
304+
// Throttle repaints for invisible windows to prevent
305+
// high CPU usage on Windows.
306+
// See: https://github.com/emilk/egui/issues/7776
307+
let when = if let Some(window) = self.winit_app.window(window_id)
308+
&& window.is_visible() == Some(false)
309+
{
310+
when.max(Instant::now() + std::time::Duration::from_millis(100))
311+
} else {
312+
when
313+
};
292314
Ok(EventResult::RepaintAt(window_id, when))
293315
} else {
294316
Ok(EventResult::Wait)

crates/eframe/src/native/wgpu_integration.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -748,10 +748,12 @@ impl WgpuWinitRunning<'_> {
748748
integration.maybe_autosave(app.as_mut(), window.map(|w| w.as_ref()));
749749

750750
if let Some(window) = window
751-
&& window.is_minimized() == Some(true)
751+
&& (window.is_minimized() == Some(true) || window.is_visible() == Some(false))
752752
{
753753
// On Mac, a minimized Window uses up all CPU:
754754
// https://github.com/emilk/egui/issues/325
755+
// On Windows, an invisible window also uses up all CPU:
756+
// https://github.com/emilk/egui/issues/7776
755757
profiling::scope!("minimized_sleep");
756758
std::thread::sleep(std::time::Duration::from_millis(10));
757759
}

0 commit comments

Comments
 (0)