diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 724ddc6d571..b1f2175e085 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -114,6 +114,11 @@ struct Viewport { /// None for immediate viewports. viewport_ui_cb: Option>, + /// When `true`, the window was created hidden to avoid a white flash. + /// It will be shown after the first frame is painted. + /// See + needs_first_show: bool, + // These three live and die together. // TODO(emilk): clump them together into one struct! gl_surface: Option>, @@ -755,6 +760,14 @@ impl GlowWinitRunning<'_> { } } + // Show viewport after the first frame has been painted to prevent white flash. + // See https://github.com/emilk/egui/issues/3625 + if let Some(viewport) = viewports.get_mut(&viewport_id) { + if std::mem::take(&mut viewport.needs_first_show) { + window.set_visible(true); + } + } + glutin.handle_viewport_output(event_loop, &integration.egui_ctx, &viewport_output); integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time @@ -1097,6 +1110,7 @@ impl GlutinWindowContext { info: viewport_info, actions_requested: Default::default(), viewport_ui_cb: None, + needs_first_show: false, // Root window visibility is handled by EpiIntegration::post_rendering gl_surface: None, window: window.map(Arc::new), egui_winit: None, @@ -1159,9 +1173,20 @@ impl GlutinWindowContext { window } else { log::debug!("Creating a window for viewport {viewport_id:?}"); + + // Start non-root viewports hidden to prevent a white flash. + // They will be shown after the first frame is painted. + // The root viewport is handled separately by EpiIntegration::post_rendering. + // See https://github.com/emilk/egui/issues/3625 + let mut builder = viewport.builder.clone(); + if viewport_id != ViewportId::ROOT && builder.visible.unwrap_or(true) { + builder.visible = Some(false); + viewport.needs_first_show = true; + } + let window_attributes = egui_winit::create_winit_window_attributes( &self.egui_ctx, - viewport.builder.clone(), + builder, ); if window_attributes.transparent() && self.gl_config.supports_transparency() == Some(false) @@ -1413,6 +1438,7 @@ fn initialize_or_update_viewport( info: Default::default(), actions_requested: Default::default(), viewport_ui_cb, + needs_first_show: false, // Set to true in initialize_window when window is created window: None, egui_winit: None, gl_surface: None, @@ -1585,6 +1611,12 @@ fn render_immediate_viewport( } } + // Show viewport after the first frame has been painted to prevent white flash. + // See https://github.com/emilk/egui/issues/3625 + if std::mem::take(&mut viewport.needs_first_show) { + window.set_visible(true); + } + egui_winit.handle_platform_output(window, platform_output); event_loop_context::with_current_event_loop(|event_loop| { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 9d628380829..59bb40112f5 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -87,6 +87,11 @@ pub struct Viewport { /// `None` for sync viewports. viewport_ui_cb: Option>, + /// When `true`, the window was created hidden to avoid a white flash. + /// It will be shown after the first frame is painted. + /// See + needs_first_show: bool, + /// Window surface state that's initialized when the app starts running via a Resumed event /// and on Android will also be destroyed if the application is paused. window: Option>, @@ -305,6 +310,7 @@ impl<'app> WgpuWinitApp<'app> { info: viewport_info, actions_requested: Default::default(), viewport_ui_cb: None, + needs_first_show: false, // Root window visibility is handled by EpiIntegration::post_rendering window: Some(window), egui_winit: Some(egui_winit), }, @@ -745,6 +751,16 @@ impl WgpuWinitRunning<'_> { 0.0 }; + // Show viewport after the first frame has been painted to prevent white flash. + // See https://github.com/emilk/egui/issues/3625 + if let Some(viewport) = viewports.get_mut(&viewport_id) { + if std::mem::take(&mut viewport.needs_first_show) { + if let Some(window) = &viewport.window { + window.set_visible(true); + } + } + } + let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); handle_viewport_output( @@ -942,7 +958,17 @@ impl Viewport { let viewport_id = self.ids.this; - match egui_winit::create_window(egui_ctx, event_loop, &self.builder) { + // Start non-root viewports hidden to prevent a white flash. + // They will be shown after the first frame is painted. + // The root viewport is handled separately by EpiIntegration::post_rendering. + // See https://github.com/emilk/egui/issues/3625 + let mut builder = self.builder.clone(); + if viewport_id != ViewportId::ROOT && builder.visible.unwrap_or(true) { + builder.visible = Some(false); + self.needs_first_show = true; + } + + match egui_winit::create_window(egui_ctx, event_loop, &builder) { Ok(window) => { windows_id.insert(window.id(), viewport_id); @@ -1102,6 +1128,14 @@ fn render_immediate_viewport( egui_winit.handle_platform_output(window, platform_output); + // Show viewport after the first frame has been painted to prevent white flash. + // See https://github.com/emilk/egui/issues/3625 + if std::mem::take(&mut viewport.needs_first_show) { + if let Some(window) = &viewport.window { + window.set_visible(true); + } + } + handle_viewport_output( &egui_ctx, &viewport_output, @@ -1212,6 +1246,7 @@ fn initialize_or_update_viewport<'a>( info: Default::default(), actions_requested: Vec::new(), viewport_ui_cb, + needs_first_show: false, // Set to true in initialize_window when window is created window: None, egui_winit: None, })