diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 325070810..c42b0aa68 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -558,7 +558,7 @@ impl GlowWinitRunning<'_> { } } - let (raw_input, viewport_ui_cb, is_visible) = { + let (raw_input, viewport_ui_cb, is_visible, run_ui) = { let mut glutin = self.glutin.borrow_mut(); let egui_ctx = glutin.egui_ctx.clone(); let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else { @@ -577,6 +577,9 @@ impl GlowWinitRunning<'_> { let mut raw_input = egui_winit.take_egui_input(window); let viewport_ui_cb = viewport.viewport_ui_cb.clone(); + let run_ui = + is_visible || is_viewport_or_descendant_visible(&glutin.viewports, viewport_id); + self.integration.pre_update(); raw_input.time = Some(self.integration.beginning.elapsed().as_secs_f64()); @@ -586,7 +589,7 @@ impl GlowWinitRunning<'_> { .map(|(id, viewport)| (*id, viewport.info.clone())) .collect(); - (raw_input, viewport_ui_cb, is_visible) + (raw_input, viewport_ui_cb, is_visible, run_ui) }; // HACK: In order to get the right clear_color, the system theme needs to be set, which @@ -641,7 +644,7 @@ impl GlowWinitRunning<'_> { self.app.as_mut(), viewport_ui_cb.as_deref(), raw_input, - is_visible, + run_ui, ); // ------------------------------------------------------------ @@ -1462,6 +1465,28 @@ fn initialize_or_update_viewport( } } +/// Is this viewport, or any of its (transitive) descendant viewports, visible? +/// +/// Immediate viewports are rendered inline while their parent's UI runs, so even +/// if this viewport's window is occluded or minimized we must still run its UI to +/// give any visible descendant a chance to be painted. +fn is_viewport_or_descendant_visible( + viewports: &OrderedViewportIdMap, + viewport_id: ViewportId, +) -> bool { + let Some(viewport) = viewports.get(&viewport_id) else { + return false; + }; + if viewport.info.visible().unwrap_or(true) { + return true; + } + viewports.values().any(|child| { + child.ids.parent == viewport_id + && child.ids.this != viewport_id // ROOT is its own parent; avoid self-recursion. + && is_viewport_or_descendant_visible(viewports, child.ids.this) + }) +} + /// This is called (via a callback) by user code to render immediate viewports, /// i.e. viewport that are directly nested inside a parent viewport. fn render_immediate_viewport( diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 882cfb3b5..ef4e7e9ca 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -601,7 +601,7 @@ impl WgpuWinitRunning<'_> { let mut frame_timer = crate::stopwatch::Stopwatch::new(); frame_timer.start(); - let (viewport_ui_cb, raw_input, is_visible) = { + let (viewport_ui_cb, raw_input, is_visible, run_ui) = { profiling::scope!("Prepare"); let mut shared_lock = shared.borrow_mut(); @@ -657,6 +657,8 @@ impl WgpuWinitRunning<'_> { }; let mut raw_input = egui_winit.take_egui_input(window); + let run_ui = is_visible || is_viewport_or_descendant_visible(viewports, viewport_id); + integration.pre_update(); raw_input.time = Some(integration.beginning.elapsed().as_secs_f64()); @@ -667,19 +669,15 @@ impl WgpuWinitRunning<'_> { painter.handle_screenshots(&mut raw_input.events); - (viewport_ui_cb, raw_input, is_visible) + (viewport_ui_cb, raw_input, is_visible, run_ui) }; // ------------------------------------------------------------ // Runs the update, which could call immediate viewports, // so make sure we hold no locks here! - let full_output = integration.update( - app.as_mut(), - viewport_ui_cb.as_deref(), - raw_input, - is_visible, - ); + let full_output = + integration.update(app.as_mut(), viewport_ui_cb.as_deref(), raw_input, run_ui); // ------------------------------------------------------------ @@ -1026,6 +1024,25 @@ fn create_window( Ok((window, viewport_builder)) } +/// Is this viewport, or any of its (transitive) descendant viewports, visible? +/// +/// Immediate viewports are rendered inline while their parent's UI runs, so even +/// if this viewport's window is occluded or minimized we must still run its UI to +/// give any visible descendant a chance to be painted. +fn is_viewport_or_descendant_visible(viewports: &Viewports, viewport_id: ViewportId) -> bool { + let Some(viewport) = viewports.get(&viewport_id) else { + return false; + }; + if viewport.info.visible().unwrap_or(true) { + return true; + } + viewports.values().any(|child| { + child.ids.parent == viewport_id + && child.ids.this != viewport_id + && is_viewport_or_descendant_visible(viewports, child.ids.this) + }) +} + fn render_immediate_viewport( beginning: Instant, shared: &RefCell,