eframe/native/
glow_integration.rs

1//! Note that this file contains code very similar to [`super::wgpu_integration`].
2//! When making changes to one you often also want to apply it to the other.
3//!
4//! This is also very complex code, and not very pretty.
5//! There is a bunch of improvements we could do,
6//! like removing a bunch of `unwraps`.
7
8#![allow(clippy::undocumented_unsafe_blocks)]
9
10use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant};
11
12use egui_winit::ActionRequested;
13use glutin::{
14    config::GlConfig as _,
15    context::NotCurrentGlContext as _,
16    display::GetGlDisplay as _,
17    prelude::{GlDisplay as _, PossiblyCurrentGlContext as _},
18    surface::GlSurface as _,
19};
20use raw_window_handle::HasWindowHandle as _;
21use winit::{
22    event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
23    window::{Window, WindowId},
24};
25
26use ahash::HashMap;
27use egui::{
28    DeferredViewportUiCallback, ImmediateViewport, OrderedViewportIdMap, ViewportBuilder,
29    ViewportClass, ViewportId, ViewportIdPair, ViewportInfo, ViewportOutput,
30};
31#[cfg(feature = "accesskit")]
32use egui_winit::accesskit_winit;
33
34use crate::{
35    App, AppCreator, CreationContext, NativeOptions, Result, Storage,
36    native::epi_integration::EpiIntegration,
37};
38
39use super::{
40    epi_integration, event_loop_context,
41    winit_integration::{EventResult, UserEvent, WinitApp, create_egui_context},
42};
43
44// ----------------------------------------------------------------------------
45// Types:
46
47pub struct GlowWinitApp<'app> {
48    repaint_proxy: Arc<egui::mutex::Mutex<EventLoopProxy<UserEvent>>>,
49    app_name: String,
50    native_options: NativeOptions,
51    running: Option<GlowWinitRunning<'app>>,
52
53    // Note that since this `AppCreator` is FnOnce we are currently unable to support
54    // re-initializing the `GlowWinitRunning` state on Android if the application
55    // suspends and resumes.
56    app_creator: Option<AppCreator<'app>>,
57}
58
59/// State that is initialized when the application is first starts running via
60/// a Resumed event. On Android this ensures that any graphics state is only
61/// initialized once the application has an associated `SurfaceView`.
62struct GlowWinitRunning<'app> {
63    integration: EpiIntegration,
64    app: Box<dyn 'app + App>,
65
66    // These needs to be shared with the immediate viewport renderer, hence the Rc/Arc/RefCells:
67    glutin: Rc<RefCell<GlutinWindowContext>>,
68
69    // NOTE: one painter shared by all viewports.
70    painter: Rc<RefCell<egui_glow::Painter>>,
71}
72
73/// This struct will contain both persistent and temporary glutin state.
74///
75/// Platform Quirks:
76/// * Microsoft Windows: requires that we create a window before opengl context.
77/// * Android: window and surface should be destroyed when we receive a suspend event. recreate on resume event.
78///
79/// winit guarantees that we will get a Resumed event on startup on all platforms.
80/// * Before Resumed event: `gl_config`, `gl_context` can be created at any time. on windows, a window must be created to get `gl_context`.
81/// * Resumed: `gl_surface` will be created here. `window` will be re-created here for android.
82/// * Suspended: on android, we drop window + surface.  on other platforms, we don't get Suspended event.
83///
84/// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of
85/// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended.
86struct GlutinWindowContext {
87    egui_ctx: egui::Context,
88
89    swap_interval: glutin::surface::SwapInterval,
90    gl_config: glutin::config::Config,
91
92    max_texture_side: Option<usize>,
93
94    current_gl_context: Option<glutin::context::PossiblyCurrentContext>,
95    not_current_gl_context: Option<glutin::context::NotCurrentContext>,
96
97    viewports: OrderedViewportIdMap<Viewport>,
98    viewport_from_window: HashMap<WindowId, ViewportId>,
99    window_from_viewport: OrderedViewportIdMap<WindowId>,
100
101    focused_viewport: Option<ViewportId>,
102}
103
104struct Viewport {
105    ids: ViewportIdPair,
106    class: ViewportClass,
107    builder: ViewportBuilder,
108    deferred_commands: Vec<egui::viewport::ViewportCommand>,
109    info: ViewportInfo,
110    actions_requested: Vec<egui_winit::ActionRequested>,
111
112    /// The user-callback that shows the ui.
113    /// None for immediate viewports.
114    viewport_ui_cb: Option<Arc<DeferredViewportUiCallback>>,
115
116    // These three live and die together.
117    // TODO(emilk): clump them together into one struct!
118    gl_surface: Option<glutin::surface::Surface<glutin::surface::WindowSurface>>,
119    window: Option<Arc<Window>>,
120    egui_winit: Option<egui_winit::State>,
121}
122
123// ----------------------------------------------------------------------------
124
125impl<'app> GlowWinitApp<'app> {
126    pub fn new(
127        event_loop: &EventLoop<UserEvent>,
128        app_name: &str,
129        native_options: NativeOptions,
130        app_creator: AppCreator<'app>,
131    ) -> Self {
132        profiling::function_scope!();
133        Self {
134            repaint_proxy: Arc::new(egui::mutex::Mutex::new(event_loop.create_proxy())),
135            app_name: app_name.to_owned(),
136            native_options,
137            running: None,
138            app_creator: Some(app_creator),
139        }
140    }
141
142    #[expect(unsafe_code)]
143    fn create_glutin_windowed_context(
144        egui_ctx: &egui::Context,
145        event_loop: &ActiveEventLoop,
146        storage: Option<&dyn Storage>,
147        native_options: &mut NativeOptions,
148    ) -> Result<(GlutinWindowContext, egui_glow::Painter)> {
149        profiling::function_scope!();
150        let window_settings = epi_integration::load_window_settings(storage);
151
152        let winit_window_builder = epi_integration::viewport_builder(
153            egui_ctx.zoom_factor(),
154            event_loop,
155            native_options,
156            window_settings,
157        )
158        .with_visible(false); // Start hidden until we render the first frame to fix white flash on startup (https://github.com/emilk/egui/pull/3631)
159
160        let mut glutin_window_context = unsafe {
161            GlutinWindowContext::new(egui_ctx, winit_window_builder, native_options, event_loop)?
162        };
163
164        // Creates the window - must come before we create our glow context
165        glutin_window_context.initialize_window(ViewportId::ROOT, event_loop)?;
166
167        {
168            let viewport = &glutin_window_context.viewports[&ViewportId::ROOT];
169            let window = viewport.window.as_ref().unwrap(); // Can't fail - we just called `initialize_all_viewports`
170            epi_integration::apply_window_settings(window, window_settings);
171        }
172
173        let gl = unsafe {
174            profiling::scope!("glow::Context::from_loader_function");
175            Arc::new(glow::Context::from_loader_function(|s| {
176                let s = std::ffi::CString::new(s)
177                    .expect("failed to construct C string from string for gl proc address");
178
179                glutin_window_context.get_proc_address(&s)
180            }))
181        };
182
183        let painter = egui_glow::Painter::new(
184            gl,
185            "",
186            native_options.shader_version,
187            native_options.dithering,
188        )?;
189
190        Ok((glutin_window_context, painter))
191    }
192
193    fn init_run_state(
194        &mut self,
195        event_loop: &ActiveEventLoop,
196    ) -> Result<&mut GlowWinitRunning<'app>> {
197        profiling::function_scope!();
198
199        let storage = if let Some(file) = &self.native_options.persistence_path {
200            epi_integration::create_storage_with_file(file)
201        } else {
202            epi_integration::create_storage(
203                self.native_options
204                    .viewport
205                    .app_id
206                    .as_ref()
207                    .unwrap_or(&self.app_name),
208            )
209        };
210
211        let egui_ctx = create_egui_context(storage.as_deref());
212
213        let (mut glutin, painter) = Self::create_glutin_windowed_context(
214            &egui_ctx,
215            event_loop,
216            storage.as_deref(),
217            &mut self.native_options,
218        )?;
219        let gl = painter.gl().clone();
220
221        let max_texture_side = painter.max_texture_side();
222        glutin.max_texture_side = Some(max_texture_side);
223        for viewport in glutin.viewports.values_mut() {
224            if let Some(egui_winit) = viewport.egui_winit.as_mut() {
225                egui_winit.set_max_texture_side(max_texture_side);
226            }
227        }
228
229        let painter = Rc::new(RefCell::new(painter));
230
231        let integration = EpiIntegration::new(
232            egui_ctx,
233            &glutin.window(ViewportId::ROOT),
234            &self.app_name,
235            &self.native_options,
236            storage,
237            Some(gl.clone()),
238            Some(Box::new({
239                let painter = painter.clone();
240                move |native| painter.borrow_mut().register_native_texture(native)
241            })),
242            #[cfg(feature = "wgpu")]
243            None,
244        );
245
246        {
247            let event_loop_proxy = self.repaint_proxy.clone();
248            integration
249                .egui_ctx
250                .set_request_repaint_callback(move |info| {
251                    log::trace!("request_repaint_callback: {info:?}");
252                    let when = Instant::now() + info.delay;
253                    let cumulative_pass_nr = info.current_cumulative_pass_nr;
254                    event_loop_proxy
255                        .lock()
256                        .send_event(UserEvent::RequestRepaint {
257                            viewport_id: info.viewport_id,
258                            when,
259                            cumulative_pass_nr,
260                        })
261                        .ok();
262                });
263        }
264
265        #[cfg(feature = "accesskit")]
266        {
267            let event_loop_proxy = self.repaint_proxy.lock().clone();
268            let viewport = glutin.viewports.get_mut(&ViewportId::ROOT).unwrap(); // we always have a root
269            if let Viewport {
270                window: Some(window),
271                egui_winit: Some(egui_winit),
272                ..
273            } = viewport
274            {
275                egui_winit.init_accesskit(event_loop, window, event_loop_proxy);
276            }
277        }
278
279        if self
280            .native_options
281            .viewport
282            .mouse_passthrough
283            .unwrap_or(false)
284            && let Err(err) = glutin.window(ViewportId::ROOT).set_cursor_hittest(false)
285        {
286            log::warn!("set_cursor_hittest(false) failed: {err}");
287        }
288
289        let app_creator = std::mem::take(&mut self.app_creator)
290            .expect("Single-use AppCreator has unexpectedly already been taken");
291
292        let app: Box<dyn 'app + App> = {
293            // Use latest raw_window_handle for eframe compatibility
294            use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
295
296            let get_proc_address = |addr: &_| glutin.get_proc_address(addr);
297            let window = glutin.window(ViewportId::ROOT);
298            let cc = CreationContext {
299                egui_ctx: integration.egui_ctx.clone(),
300                integration_info: integration.frame.info().clone(),
301                storage: integration.frame.storage(),
302                gl: Some(gl),
303                get_proc_address: Some(&get_proc_address),
304                #[cfg(feature = "wgpu")]
305                wgpu_render_state: None,
306                raw_display_handle: window.display_handle().map(|h| h.as_raw()),
307                raw_window_handle: window.window_handle().map(|h| h.as_raw()),
308            };
309            profiling::scope!("app_creator");
310            app_creator(&cc).map_err(crate::Error::AppCreation)?
311        };
312
313        let glutin = Rc::new(RefCell::new(glutin));
314
315        {
316            // Create weak pointers so that we don't keep
317            // state alive for too long.
318            let glutin = Rc::downgrade(&glutin);
319            let painter = Rc::downgrade(&painter);
320            let beginning = integration.beginning;
321
322            egui::Context::set_immediate_viewport_renderer(move |egui_ctx, immediate_viewport| {
323                if let (Some(glutin), Some(painter)) = (glutin.upgrade(), painter.upgrade()) {
324                    render_immediate_viewport(
325                        egui_ctx,
326                        &glutin,
327                        &painter,
328                        beginning,
329                        immediate_viewport,
330                    );
331                } else {
332                    log::warn!("render_sync_callback called after window closed");
333                }
334            });
335        }
336
337        Ok(self.running.insert(GlowWinitRunning {
338            integration,
339            app,
340            glutin,
341            painter,
342        }))
343    }
344}
345
346impl WinitApp for GlowWinitApp<'_> {
347    fn egui_ctx(&self) -> Option<&egui::Context> {
348        self.running.as_ref().map(|r| &r.integration.egui_ctx)
349    }
350
351    fn window(&self, window_id: WindowId) -> Option<Arc<Window>> {
352        let running = self.running.as_ref()?;
353        let glutin = running.glutin.borrow();
354        let viewport_id = *glutin.viewport_from_window.get(&window_id)?;
355        if let Some(viewport) = glutin.viewports.get(&viewport_id) {
356            viewport.window.clone()
357        } else {
358            None
359        }
360    }
361
362    fn window_id_from_viewport_id(&self, id: ViewportId) -> Option<WindowId> {
363        self.running
364            .as_ref()?
365            .glutin
366            .borrow()
367            .window_from_viewport
368            .get(&id)
369            .copied()
370    }
371
372    fn save(&mut self) {
373        log::debug!("WinitApp::save called");
374        if let Some(running) = self.running.as_mut() {
375            profiling::function_scope!();
376
377            // This is used because of the "save on suspend" logic on Android. Once the application is suspended, there is no window associated to it, which was causing panics when `.window().expect()` was used.
378            let window_opt = running.glutin.borrow().window_opt(ViewportId::ROOT);
379
380            running
381                .integration
382                .save(running.app.as_mut(), window_opt.as_deref());
383        }
384    }
385
386    fn save_and_destroy(&mut self) {
387        if let Some(mut running) = self.running.take() {
388            profiling::function_scope!();
389
390            running.integration.save(
391                running.app.as_mut(),
392                Some(&running.glutin.borrow().window(ViewportId::ROOT)),
393            );
394            running.app.on_exit(Some(running.painter.borrow().gl()));
395            running.painter.borrow_mut().destroy();
396        }
397    }
398
399    fn run_ui_and_paint(
400        &mut self,
401        event_loop: &ActiveEventLoop,
402        window_id: WindowId,
403    ) -> Result<EventResult> {
404        if let Some(running) = &mut self.running {
405            running.run_ui_and_paint(event_loop, window_id)
406        } else {
407            Ok(EventResult::Wait)
408        }
409    }
410
411    fn resumed(&mut self, event_loop: &ActiveEventLoop) -> crate::Result<EventResult> {
412        log::debug!("Event::Resumed");
413
414        let running = if let Some(running) = &mut self.running {
415            // Not the first resume event. Create all outstanding windows.
416            running
417                .glutin
418                .borrow_mut()
419                .initialize_all_windows(event_loop);
420            running
421        } else {
422            // First resume event. Create our root window etc.
423            self.init_run_state(event_loop)?
424        };
425        let window_id = running.glutin.borrow().window_from_viewport[&ViewportId::ROOT];
426        Ok(EventResult::RepaintNow(window_id))
427    }
428
429    fn suspended(&mut self, _: &ActiveEventLoop) -> crate::Result<EventResult> {
430        if let Some(running) = &mut self.running {
431            running.glutin.borrow_mut().on_suspend()?;
432        }
433        Ok(EventResult::Save)
434    }
435
436    fn device_event(
437        &mut self,
438        _: &ActiveEventLoop,
439        _: winit::event::DeviceId,
440        event: winit::event::DeviceEvent,
441    ) -> crate::Result<EventResult> {
442        if let winit::event::DeviceEvent::MouseMotion { delta } = event
443            && let Some(running) = &mut self.running
444        {
445            let mut glutin = running.glutin.borrow_mut();
446            if let Some(viewport) = glutin
447                .focused_viewport
448                .and_then(|viewport| glutin.viewports.get_mut(&viewport))
449            {
450                if let Some(egui_winit) = viewport.egui_winit.as_mut() {
451                    egui_winit.on_mouse_motion(delta);
452                }
453
454                if let Some(window) = viewport.window.as_ref() {
455                    return Ok(EventResult::RepaintNext(window.id()));
456                }
457            }
458        }
459
460        Ok(EventResult::Wait)
461    }
462
463    fn window_event(
464        &mut self,
465        _: &ActiveEventLoop,
466        window_id: WindowId,
467        event: winit::event::WindowEvent,
468    ) -> Result<EventResult> {
469        if let Some(running) = &mut self.running {
470            Ok(running.on_window_event(window_id, &event))
471        } else {
472            Ok(EventResult::Exit)
473        }
474    }
475
476    #[cfg(feature = "accesskit")]
477    fn on_accesskit_event(&mut self, event: accesskit_winit::Event) -> crate::Result<EventResult> {
478        use super::winit_integration;
479
480        if let Some(running) = &self.running {
481            let mut glutin = running.glutin.borrow_mut();
482            if let Some(viewport_id) = glutin.viewport_from_window.get(&event.window_id).copied()
483                && let Some(viewport) = glutin.viewports.get_mut(&viewport_id)
484                && let Some(egui_winit) = &mut viewport.egui_winit
485            {
486                return Ok(winit_integration::on_accesskit_window_event(
487                    egui_winit,
488                    event.window_id,
489                    &event.window_event,
490                ));
491            }
492        }
493
494        Ok(EventResult::Wait)
495    }
496}
497
498impl GlowWinitRunning<'_> {
499    fn run_ui_and_paint(
500        &mut self,
501        event_loop: &ActiveEventLoop,
502        window_id: WindowId,
503    ) -> Result<EventResult> {
504        profiling::function_scope!();
505
506        let Some(viewport_id) = self
507            .glutin
508            .borrow()
509            .viewport_from_window
510            .get(&window_id)
511            .copied()
512        else {
513            return Ok(EventResult::Wait);
514        };
515
516        profiling::finish_frame!();
517
518        let mut frame_timer = crate::stopwatch::Stopwatch::new();
519        frame_timer.start();
520
521        {
522            let glutin = self.glutin.borrow();
523            let viewport = &glutin.viewports[&viewport_id];
524            let is_immediate = viewport.viewport_ui_cb.is_none();
525            if is_immediate && viewport_id != ViewportId::ROOT {
526                // This will only happen if this is an immediate viewport.
527                // That means that the viewport cannot be rendered by itself and needs his parent to be rendered.
528                if let Some(parent_viewport) = glutin.viewports.get(&viewport.ids.parent)
529                    && let Some(window) = parent_viewport.window.as_ref()
530                {
531                    return Ok(EventResult::RepaintNext(window.id()));
532                }
533                return Ok(EventResult::Wait);
534            }
535        }
536
537        let (raw_input, viewport_ui_cb) = {
538            let mut glutin = self.glutin.borrow_mut();
539            let egui_ctx = glutin.egui_ctx.clone();
540            let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else {
541                return Ok(EventResult::Wait);
542            };
543            let Some(window) = viewport.window.as_ref() else {
544                return Ok(EventResult::Wait);
545            };
546            egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window, false);
547
548            let Some(egui_winit) = viewport.egui_winit.as_mut() else {
549                return Ok(EventResult::Wait);
550            };
551            let mut raw_input = egui_winit.take_egui_input(window);
552            let viewport_ui_cb = viewport.viewport_ui_cb.clone();
553
554            self.integration.pre_update();
555
556            raw_input.time = Some(self.integration.beginning.elapsed().as_secs_f64());
557            raw_input.viewports = glutin
558                .viewports
559                .iter()
560                .map(|(id, viewport)| (*id, viewport.info.clone()))
561                .collect();
562
563            (raw_input, viewport_ui_cb)
564        };
565
566        // HACK: In order to get the right clear_color, the system theme needs to be set, which
567        // usually only happens in the `update` call. So we call Options::begin_pass early
568        // to set the right theme. Without this there would be a black flash on the first frame.
569        self.integration
570            .egui_ctx
571            .options_mut(|opt| opt.begin_pass(&raw_input));
572        let clear_color = self
573            .app
574            .clear_color(&self.integration.egui_ctx.style().visuals);
575
576        let has_many_viewports = self.glutin.borrow().viewports.len() > 1;
577        let clear_before_update = !has_many_viewports; // HACK: for some reason, an early clear doesn't "take" on Mac with multiple viewports.
578
579        if clear_before_update {
580            // clear before we call update, so users can paint between clear-color and egui windows:
581
582            let mut glutin = self.glutin.borrow_mut();
583            let GlutinWindowContext {
584                viewports,
585                current_gl_context,
586                not_current_gl_context,
587                ..
588            } = &mut *glutin;
589            let viewport = &viewports[&viewport_id];
590            let Some(window) = viewport.window.as_ref() else {
591                return Ok(EventResult::Wait);
592            };
593            let Some(gl_surface) = viewport.gl_surface.as_ref() else {
594                return Ok(EventResult::Wait);
595            };
596
597            let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
598
599            {
600                frame_timer.pause();
601                change_gl_context(current_gl_context, not_current_gl_context, gl_surface);
602                frame_timer.resume();
603            }
604
605            self.painter
606                .borrow()
607                .clear(screen_size_in_pixels, clear_color);
608        }
609
610        // ------------------------------------------------------------
611        // The update function, which could call immediate viewports,
612        // so make sure we don't hold any locks here required by the immediate viewports rendeer.
613
614        let full_output =
615            self.integration
616                .update(self.app.as_mut(), viewport_ui_cb.as_deref(), raw_input);
617
618        // ------------------------------------------------------------
619
620        let Self {
621            integration,
622            app,
623            glutin,
624            painter,
625            ..
626        } = self;
627
628        let mut glutin = glutin.borrow_mut();
629        let mut painter = painter.borrow_mut();
630
631        let egui::FullOutput {
632            platform_output,
633            textures_delta,
634            shapes,
635            pixels_per_point,
636            viewport_output,
637        } = full_output;
638
639        glutin.remove_viewports_not_in(&viewport_output);
640
641        let GlutinWindowContext {
642            viewports,
643            current_gl_context,
644            not_current_gl_context,
645            ..
646        } = &mut *glutin;
647
648        let Some(viewport) = viewports.get_mut(&viewport_id) else {
649            return Ok(EventResult::Wait);
650        };
651
652        viewport.info.events.clear(); // they should have been processed
653        let window = viewport.window.clone().unwrap();
654        let gl_surface = viewport.gl_surface.as_ref().unwrap();
655        let egui_winit = viewport.egui_winit.as_mut().unwrap();
656
657        egui_winit.handle_platform_output(&window, platform_output);
658
659        let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
660
661        {
662            // We may need to switch contexts again, because of immediate viewports:
663            frame_timer.pause();
664            change_gl_context(current_gl_context, not_current_gl_context, gl_surface);
665            frame_timer.resume();
666        }
667
668        let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
669
670        if !clear_before_update {
671            painter.clear(screen_size_in_pixels, clear_color);
672        }
673
674        painter.paint_and_update_textures(
675            screen_size_in_pixels,
676            pixels_per_point,
677            &clipped_primitives,
678            &textures_delta,
679        );
680
681        {
682            for action in viewport.actions_requested.drain(..) {
683                match action {
684                    ActionRequested::Screenshot(user_data) => {
685                        let screenshot = painter.read_screen_rgba(screen_size_in_pixels);
686                        egui_winit
687                            .egui_input_mut()
688                            .events
689                            .push(egui::Event::Screenshot {
690                                viewport_id,
691                                user_data,
692                                image: screenshot.into(),
693                            });
694                    }
695                    ActionRequested::Cut => {
696                        egui_winit.egui_input_mut().events.push(egui::Event::Cut);
697                    }
698                    ActionRequested::Copy => {
699                        egui_winit.egui_input_mut().events.push(egui::Event::Copy);
700                    }
701                    ActionRequested::Paste => {
702                        if let Some(contents) = egui_winit.clipboard_text() {
703                            let contents = contents.replace("\r\n", "\n");
704                            if !contents.is_empty() {
705                                egui_winit
706                                    .egui_input_mut()
707                                    .events
708                                    .push(egui::Event::Paste(contents));
709                            }
710                        }
711                    }
712                }
713            }
714
715            integration.post_rendering(&window);
716        }
717
718        {
719            // vsync - don't count as frame-time:
720            frame_timer.pause();
721            profiling::scope!("swap_buffers");
722            let context = current_gl_context
723                .as_ref()
724                .ok_or(egui_glow::PainterError::from(
725                    "failed to get current context to swap buffers".to_owned(),
726                ))?;
727
728            gl_surface.swap_buffers(context)?;
729            frame_timer.resume();
730        }
731
732        // give it time to settle:
733        #[cfg(feature = "__screenshot")]
734        if integration.egui_ctx.cumulative_pass_nr() == 2
735            && let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO")
736        {
737            save_screenshot_and_exit(&path, &painter, screen_size_in_pixels);
738        }
739
740        glutin.handle_viewport_output(event_loop, &integration.egui_ctx, &viewport_output);
741
742        integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time
743
744        integration.maybe_autosave(app.as_mut(), Some(&window));
745
746        if window.is_minimized() == Some(true) {
747            // On Mac, a minimized Window uses up all CPU:
748            // https://github.com/emilk/egui/issues/325
749            profiling::scope!("minimized_sleep");
750            std::thread::sleep(std::time::Duration::from_millis(10));
751        }
752
753        if integration.should_close() {
754            Ok(EventResult::CloseRequested)
755        } else {
756            Ok(EventResult::Wait)
757        }
758    }
759
760    fn on_window_event(
761        &mut self,
762        window_id: WindowId,
763        event: &winit::event::WindowEvent,
764    ) -> EventResult {
765        let mut glutin = self.glutin.borrow_mut();
766        let viewport_id = glutin.viewport_from_window.get(&window_id).copied();
767
768        // On Windows, if a window is resized by the user, it should repaint synchronously, inside the
769        // event handler.
770        //
771        // If this is not done, the compositor will assume that the window does not want to redraw,
772        // and continue ahead.
773        //
774        // In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver
775        // new frames to the compositor in time.
776        //
777        // The flickering is technically glutin or glow's fault, but we should be responding properly
778        // to resizes anyway, as doing so avoids dropping frames.
779        //
780        // See: https://github.com/emilk/egui/issues/903
781        let mut repaint_asap = false;
782
783        match event {
784            winit::event::WindowEvent::Focused(focused) => {
785                let focused = if cfg!(target_os = "macos")
786                    && let Some(viewport_id) = viewport_id
787                    && let Some(viewport) = glutin.viewports.get(&viewport_id)
788                    && let Some(window) = &viewport.window
789                {
790                    // TODO(emilk): remove this work-around once we update winit
791                    // https://github.com/rust-windowing/winit/issues/4371
792                    // https://github.com/emilk/egui/issues/7588
793                    window.has_focus()
794                } else {
795                    *focused
796                };
797
798                glutin.focused_viewport = focused.then_some(viewport_id).flatten();
799            }
800
801            winit::event::WindowEvent::Resized(physical_size) => {
802                // Resize with 0 width and height is used by winit to signal a minimize event on Windows.
803                // See: https://github.com/rust-windowing/winit/issues/208
804                // This solves an issue where the app would panic when minimizing on Windows.
805                if 0 < physical_size.width
806                    && 0 < physical_size.height
807                    && let Some(viewport_id) = viewport_id
808                {
809                    repaint_asap = true;
810                    glutin.resize(viewport_id, *physical_size);
811                }
812            }
813
814            winit::event::WindowEvent::CloseRequested => {
815                if viewport_id == Some(ViewportId::ROOT) && self.integration.should_close() {
816                    log::debug!(
817                        "Received WindowEvent::CloseRequested for main viewport - shutting down."
818                    );
819                    return EventResult::CloseRequested;
820                }
821
822                log::debug!("Received WindowEvent::CloseRequested for viewport {viewport_id:?}");
823
824                if let Some(viewport_id) = viewport_id
825                    && let Some(viewport) = glutin.viewports.get_mut(&viewport_id)
826                {
827                    // Tell viewport it should close:
828                    viewport.info.events.push(egui::ViewportEvent::Close);
829
830                    // We may need to repaint both us and our parent to close the window,
831                    // and perhaps twice (once to notice the close-event, once again to enforce it).
832                    // `request_repaint_of` does a double-repaint though:
833                    self.integration.egui_ctx.request_repaint_of(viewport_id);
834                    self.integration
835                        .egui_ctx
836                        .request_repaint_of(viewport.ids.parent);
837                }
838            }
839            _ => {}
840        }
841
842        if self.integration.should_close() {
843            return EventResult::CloseRequested;
844        }
845
846        let mut event_response = egui_winit::EventResponse {
847            consumed: false,
848            repaint: false,
849        };
850        if let Some(viewport_id) = viewport_id {
851            if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) {
852                if let (Some(window), Some(egui_winit)) =
853                    (&viewport.window, &mut viewport.egui_winit)
854                {
855                    event_response = self.integration.on_window_event(window, egui_winit, event);
856                }
857            } else {
858                log::trace!("Ignoring event: no viewport for {viewport_id:?}");
859            }
860        } else {
861            log::trace!("Ignoring event: no viewport_id");
862        }
863
864        if event_response.repaint {
865            if repaint_asap {
866                EventResult::RepaintNow(window_id)
867            } else {
868                EventResult::RepaintNext(window_id)
869            }
870        } else {
871            EventResult::Wait
872        }
873    }
874}
875
876fn change_gl_context(
877    current_gl_context: &mut Option<glutin::context::PossiblyCurrentContext>,
878    not_current_gl_context: &mut Option<glutin::context::NotCurrentContext>,
879    gl_surface: &glutin::surface::Surface<glutin::surface::WindowSurface>,
880) {
881    profiling::function_scope!();
882
883    if !cfg!(target_os = "windows") {
884        // According to https://github.com/emilk/egui/issues/4289
885        // we cannot do this early-out on Windows.
886        // TODO(emilk): optimize context switching on Windows too.
887        // See https://github.com/emilk/egui/issues/4173
888
889        if let Some(current_gl_context) = current_gl_context {
890            profiling::scope!("is_current");
891            if gl_surface.is_current(current_gl_context) {
892                return; // Early-out to save a lot of time.
893            }
894        }
895    }
896
897    let not_current = if let Some(not_current_context) = not_current_gl_context.take() {
898        not_current_context
899    } else {
900        profiling::scope!("make_not_current");
901        current_gl_context
902            .take()
903            .unwrap()
904            .make_not_current()
905            .unwrap()
906    };
907
908    profiling::scope!("make_current");
909    *current_gl_context = Some(not_current.make_current(gl_surface).unwrap());
910}
911
912impl GlutinWindowContext {
913    #[expect(unsafe_code)]
914    unsafe fn new(
915        egui_ctx: &egui::Context,
916        viewport_builder: ViewportBuilder,
917        native_options: &NativeOptions,
918        event_loop: &ActiveEventLoop,
919    ) -> Result<Self> {
920        profiling::function_scope!();
921
922        // There is a lot of complexity with opengl creation,
923        // so prefer extensive logging to get all the help we can to debug issues.
924
925        use glutin::prelude::*;
926        // convert native options to glutin options
927        let hardware_acceleration = match native_options.hardware_acceleration {
928            crate::HardwareAcceleration::Required => Some(true),
929            crate::HardwareAcceleration::Preferred => None,
930            crate::HardwareAcceleration::Off => Some(false),
931        };
932        let swap_interval = if native_options.vsync {
933            glutin::surface::SwapInterval::Wait(NonZeroU32::MIN)
934        } else {
935            glutin::surface::SwapInterval::DontWait
936        };
937        /*  opengl setup flow goes like this:
938            1. we create a configuration for opengl "Display" / "Config" creation
939            2. choose between special extensions like glx or egl or wgl and use them to create config/display
940            3. opengl context configuration
941            4. opengl context creation
942        */
943        // start building config for gl display
944        let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
945            .prefer_hardware_accelerated(hardware_acceleration)
946            .with_depth_size(native_options.depth_buffer)
947            .with_stencil_size(native_options.stencil_buffer)
948            .with_transparency(native_options.viewport.transparent.unwrap_or(false));
949        // we don't know if multi sampling option is set. so, check if its more than 0.
950        let config_template_builder = if native_options.multisampling > 0 {
951            config_template_builder.with_multisampling(
952                native_options
953                    .multisampling
954                    .try_into()
955                    .expect("failed to fit multisamples option of native_options into u8"),
956            )
957        } else {
958            config_template_builder
959        };
960
961        log::debug!("trying to create glutin Display with config: {config_template_builder:?}");
962
963        // Create GL display. This may probably create a window too on most platforms. Definitely on `MS windows`. Never on Android.
964        let display_builder = glutin_winit::DisplayBuilder::new()
965            // we might want to expose this option to users in the future. maybe using an env var or using native_options.
966            //
967            // The justification for FallbackEgl over PreferEgl is at https://github.com/emilk/egui/pull/2526#issuecomment-1400229576 .
968            .with_preference(glutin_winit::ApiPreference::FallbackEgl)
969            .with_window_attributes(Some(egui_winit::create_winit_window_attributes(
970                egui_ctx,
971                viewport_builder.clone(),
972            )));
973
974        let (window, gl_config) = {
975            profiling::scope!("DisplayBuilder::build");
976
977            display_builder
978                .build(
979                    event_loop,
980                    config_template_builder.clone(),
981                    |mut config_iterator| {
982                        let config = config_iterator.next().expect(
983                            "failed to find a matching configuration for creating glutin config",
984                        );
985                        log::debug!(
986                            "using the first config from config picker closure. config: {config:?}"
987                        );
988                        config
989                    },
990                )
991                .map_err(|e| crate::Error::NoGlutinConfigs(config_template_builder.build(), e))?
992        };
993        if let Some(window) = &window {
994            egui_winit::apply_viewport_builder_to_window(egui_ctx, window, &viewport_builder);
995        }
996
997        let gl_display = gl_config.display();
998        log::debug!(
999            "successfully created GL Display with version: {} and supported features: {:?}",
1000            gl_display.version_string(),
1001            gl_display.supported_features()
1002        );
1003        let glutin_raw_window_handle = window.as_ref().map(|w| {
1004            w.window_handle()
1005                .expect("Failed to get window handle")
1006                .as_raw()
1007        });
1008        log::debug!("creating gl context using raw window handle: {glutin_raw_window_handle:?}");
1009
1010        // create gl context. if core context cannot be created, try gl es context as fallback.
1011        let context_attributes =
1012            glutin::context::ContextAttributesBuilder::new().build(glutin_raw_window_handle);
1013        let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
1014            .with_context_api(glutin::context::ContextApi::Gles(None))
1015            .build(glutin_raw_window_handle);
1016
1017        let gl_context_result = unsafe {
1018            profiling::scope!("create_context");
1019            gl_config
1020                .display()
1021                .create_context(&gl_config, &context_attributes)
1022        };
1023
1024        let gl_context = match gl_context_result {
1025            Ok(it) => it,
1026            Err(err) => {
1027                log::warn!(
1028                    "Failed to create context using default context attributes {context_attributes:?} due to error: {err}"
1029                );
1030                log::debug!(
1031                    "Retrying with fallback context attributes: {fallback_context_attributes:?}"
1032                );
1033                unsafe {
1034                    gl_config
1035                        .display()
1036                        .create_context(&gl_config, &fallback_context_attributes)?
1037                }
1038            }
1039        };
1040        let not_current_gl_context = Some(gl_context);
1041
1042        let mut viewport_from_window = HashMap::default();
1043        let mut window_from_viewport = OrderedViewportIdMap::default();
1044        let mut viewport_info = ViewportInfo::default();
1045        if let Some(window) = &window {
1046            viewport_from_window.insert(window.id(), ViewportId::ROOT);
1047            window_from_viewport.insert(ViewportId::ROOT, window.id());
1048            egui_winit::update_viewport_info(&mut viewport_info, egui_ctx, window, true);
1049
1050            // Tell egui right away about native_pixels_per_point etc,
1051            // so that the app knows about it during app creation:
1052            let pixels_per_point = egui_winit::pixels_per_point(egui_ctx, window);
1053
1054            egui_ctx.input_mut(|i| {
1055                i.raw
1056                    .viewports
1057                    .insert(ViewportId::ROOT, viewport_info.clone());
1058
1059                i.pixels_per_point = pixels_per_point;
1060            });
1061        }
1062
1063        let mut viewports = OrderedViewportIdMap::default();
1064        viewports.insert(
1065            ViewportId::ROOT,
1066            Viewport {
1067                ids: ViewportIdPair::ROOT,
1068                class: ViewportClass::Root,
1069                builder: viewport_builder,
1070                deferred_commands: vec![],
1071                info: viewport_info,
1072                actions_requested: Default::default(),
1073                viewport_ui_cb: None,
1074                gl_surface: None,
1075                window: window.map(Arc::new),
1076                egui_winit: None,
1077            },
1078        );
1079
1080        // the fun part with opengl gl is that we never know whether there is an error. the context creation might have failed, but
1081        // it could keep working until we try to make surface current or swap buffers or something else. future glutin improvements might
1082        // help us start from scratch again if we fail context creation and go back to preferEgl or try with different config etc..
1083        // https://github.com/emilk/egui/pull/2541#issuecomment-1370767582
1084
1085        let mut slf = Self {
1086            egui_ctx: egui_ctx.clone(),
1087            swap_interval,
1088            gl_config,
1089            current_gl_context: None,
1090            not_current_gl_context,
1091            viewports,
1092            viewport_from_window,
1093            max_texture_side: None,
1094            window_from_viewport,
1095            focused_viewport: Some(ViewportId::ROOT),
1096        };
1097
1098        slf.initialize_window(ViewportId::ROOT, event_loop)?;
1099
1100        Ok(slf)
1101    }
1102
1103    /// Create a surface, window, and winit integration for all viewports lacking any of that.
1104    ///
1105    /// Errors will be logged.
1106    fn initialize_all_windows(&mut self, event_loop: &ActiveEventLoop) {
1107        profiling::function_scope!();
1108
1109        let viewports: Vec<ViewportId> = self.viewports.keys().copied().collect();
1110
1111        for viewport_id in viewports {
1112            if let Err(err) = self.initialize_window(viewport_id, event_loop) {
1113                log::error!("Failed to initialize a window for viewport {viewport_id:?}: {err}");
1114            }
1115        }
1116    }
1117
1118    /// Create a surface, window, and winit integration for the viewport, if missing.
1119    #[expect(unsafe_code)]
1120    pub(crate) fn initialize_window(
1121        &mut self,
1122        viewport_id: ViewportId,
1123        event_loop: &ActiveEventLoop,
1124    ) -> Result {
1125        profiling::function_scope!();
1126
1127        let viewport = self
1128            .viewports
1129            .get_mut(&viewport_id)
1130            .expect("viewport doesn't exist");
1131
1132        let window = if let Some(window) = &mut viewport.window {
1133            window
1134        } else {
1135            log::debug!("Creating a window for viewport {viewport_id:?}");
1136            let window_attributes = egui_winit::create_winit_window_attributes(
1137                &self.egui_ctx,
1138                viewport.builder.clone(),
1139            );
1140            if window_attributes.transparent()
1141                && self.gl_config.supports_transparency() == Some(false)
1142            {
1143                log::error!("Cannot create transparent window: the GL config does not support it");
1144            }
1145            let window =
1146                glutin_winit::finalize_window(event_loop, window_attributes, &self.gl_config)?;
1147            egui_winit::apply_viewport_builder_to_window(
1148                &self.egui_ctx,
1149                &window,
1150                &viewport.builder,
1151            );
1152
1153            egui_winit::update_viewport_info(&mut viewport.info, &self.egui_ctx, &window, true);
1154            viewport.window.insert(Arc::new(window))
1155        };
1156
1157        viewport.egui_winit.get_or_insert_with(|| {
1158            log::debug!("Initializing egui_winit for viewport {viewport_id:?}");
1159            egui_winit::State::new(
1160                self.egui_ctx.clone(),
1161                viewport_id,
1162                event_loop,
1163                Some(window.scale_factor() as f32),
1164                event_loop.system_theme(),
1165                self.max_texture_side,
1166            )
1167        });
1168
1169        if viewport.gl_surface.is_none() {
1170            log::debug!("Creating a gl_surface for viewport {viewport_id:?}");
1171
1172            // surface attributes
1173            let (width_px, height_px): (u32, u32) = window.inner_size().into();
1174            let width_px = NonZeroU32::new(width_px).unwrap_or(NonZeroU32::MIN);
1175            let height_px = NonZeroU32::new(height_px).unwrap_or(NonZeroU32::MIN);
1176            let surface_attributes = {
1177                glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
1178                    .build(
1179                        window
1180                            .window_handle()
1181                            .expect("Failed to get display handle")
1182                            .as_raw(),
1183                        width_px,
1184                        height_px,
1185                    )
1186            };
1187
1188            log::trace!("creating surface with attributes: {surface_attributes:?}");
1189            let gl_surface = unsafe {
1190                self.gl_config
1191                    .display()
1192                    .create_window_surface(&self.gl_config, &surface_attributes)?
1193            };
1194
1195            log::trace!("surface created successfully: {gl_surface:?}. making context current");
1196
1197            let not_current_gl_context =
1198                if let Some(not_current_context) = self.not_current_gl_context.take() {
1199                    not_current_context
1200                } else {
1201                    self.current_gl_context
1202                        .take()
1203                        .unwrap()
1204                        .make_not_current()
1205                        .unwrap()
1206                };
1207            let current_gl_context = not_current_gl_context.make_current(&gl_surface)?;
1208
1209            // try setting swap interval. but its not absolutely necessary, so don't panic on failure.
1210            log::trace!("made context current. setting swap interval for surface");
1211            if let Err(err) = gl_surface.set_swap_interval(&current_gl_context, self.swap_interval)
1212            {
1213                log::warn!("Failed to set swap interval due to error: {err}");
1214            }
1215
1216            // we will reach this point only once in most platforms except android.
1217            // create window/surface/make context current once and just use them forever.
1218
1219            viewport.gl_surface = Some(gl_surface);
1220
1221            self.current_gl_context = Some(current_gl_context);
1222        }
1223
1224        self.viewport_from_window.insert(window.id(), viewport_id);
1225        self.window_from_viewport.insert(viewport_id, window.id());
1226
1227        Ok(())
1228    }
1229
1230    /// only applies for android. but we basically drop surface + window and make context not current
1231    fn on_suspend(&mut self) -> Result {
1232        log::debug!("received suspend event. dropping window and surface");
1233        for viewport in self.viewports.values_mut() {
1234            viewport.gl_surface = None;
1235            viewport.window = None;
1236        }
1237        if let Some(current) = self.current_gl_context.take() {
1238            log::debug!("context is current, so making it non-current");
1239            self.not_current_gl_context = Some(current.make_not_current()?);
1240        } else {
1241            log::debug!("context is already not current??? could be duplicate suspend event");
1242        }
1243        Ok(())
1244    }
1245
1246    fn viewport(&self, viewport_id: ViewportId) -> &Viewport {
1247        self.viewports
1248            .get(&viewport_id)
1249            .expect("viewport doesn't exist")
1250    }
1251
1252    fn window_opt(&self, viewport_id: ViewportId) -> Option<Arc<Window>> {
1253        self.viewport(viewport_id).window.clone()
1254    }
1255
1256    fn window(&self, viewport_id: ViewportId) -> Arc<Window> {
1257        self.window_opt(viewport_id)
1258            .expect("winit window doesn't exist")
1259    }
1260
1261    fn resize(&mut self, viewport_id: ViewportId, physical_size: winit::dpi::PhysicalSize<u32>) {
1262        let width_px = NonZeroU32::new(physical_size.width).unwrap_or(NonZeroU32::MIN);
1263        let height_px = NonZeroU32::new(physical_size.height).unwrap_or(NonZeroU32::MIN);
1264
1265        if let Some(viewport) = self.viewports.get(&viewport_id)
1266            && let Some(gl_surface) = &viewport.gl_surface
1267        {
1268            change_gl_context(
1269                &mut self.current_gl_context,
1270                &mut self.not_current_gl_context,
1271                gl_surface,
1272            );
1273            gl_surface.resize(
1274                self.current_gl_context
1275                    .as_ref()
1276                    .expect("failed to get current context to resize surface"),
1277                width_px,
1278                height_px,
1279            );
1280        }
1281    }
1282
1283    fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
1284        self.gl_config.display().get_proc_address(addr)
1285    }
1286
1287    pub(crate) fn remove_viewports_not_in(
1288        &mut self,
1289        viewport_output: &OrderedViewportIdMap<ViewportOutput>,
1290    ) {
1291        // GC old viewports
1292        self.viewports
1293            .retain(|id, _| viewport_output.contains_key(id));
1294        self.viewport_from_window
1295            .retain(|_, id| viewport_output.contains_key(id));
1296        self.window_from_viewport
1297            .retain(|id, _| viewport_output.contains_key(id));
1298    }
1299
1300    fn handle_viewport_output(
1301        &mut self,
1302        event_loop: &ActiveEventLoop,
1303        egui_ctx: &egui::Context,
1304        viewport_output: &OrderedViewportIdMap<ViewportOutput>,
1305    ) {
1306        profiling::function_scope!();
1307
1308        for (
1309            viewport_id,
1310            ViewportOutput {
1311                parent,
1312                class,
1313                builder,
1314                viewport_ui_cb,
1315                mut commands,
1316                repaint_delay: _, // ignored - we listened to the repaint callback instead
1317            },
1318        ) in viewport_output.clone()
1319        {
1320            let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent);
1321
1322            let viewport = initialize_or_update_viewport(
1323                &mut self.viewports,
1324                ids,
1325                class,
1326                builder,
1327                viewport_ui_cb,
1328            );
1329
1330            if let Some(window) = &viewport.window {
1331                let old_inner_size = window.inner_size();
1332
1333                viewport.deferred_commands.append(&mut commands);
1334
1335                egui_winit::process_viewport_commands(
1336                    egui_ctx,
1337                    &mut viewport.info,
1338                    std::mem::take(&mut viewport.deferred_commands),
1339                    window,
1340                    &mut viewport.actions_requested,
1341                );
1342
1343                // For Wayland : https://github.com/emilk/egui/issues/4196
1344                if cfg!(target_os = "linux") {
1345                    let new_inner_size = window.inner_size();
1346                    if new_inner_size != old_inner_size {
1347                        self.resize(viewport_id, new_inner_size);
1348                    }
1349                }
1350            }
1351        }
1352
1353        // Create windows for any new viewports:
1354        self.initialize_all_windows(event_loop);
1355
1356        self.remove_viewports_not_in(viewport_output);
1357    }
1358}
1359
1360fn initialize_or_update_viewport(
1361    viewports: &mut OrderedViewportIdMap<Viewport>,
1362    ids: ViewportIdPair,
1363    class: ViewportClass,
1364    mut builder: ViewportBuilder,
1365    viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
1366) -> &mut Viewport {
1367    profiling::function_scope!();
1368
1369    use std::collections::btree_map::Entry;
1370
1371    if builder.icon.is_none() {
1372        // Inherit icon from parent
1373        builder.icon = viewports
1374            .get_mut(&ids.parent)
1375            .and_then(|vp| vp.builder.icon.clone());
1376    }
1377
1378    match viewports.entry(ids.this) {
1379        Entry::Vacant(entry) => {
1380            // New viewport:
1381            log::debug!("Creating new viewport {:?} ({:?})", ids.this, builder.title);
1382            entry.insert(Viewport {
1383                ids,
1384                class,
1385                builder,
1386                deferred_commands: vec![],
1387                info: Default::default(),
1388                actions_requested: Default::default(),
1389                viewport_ui_cb,
1390                window: None,
1391                egui_winit: None,
1392                gl_surface: None,
1393            })
1394        }
1395
1396        Entry::Occupied(mut entry) => {
1397            // Patch an existing viewport:
1398            let viewport = entry.get_mut();
1399
1400            viewport.ids.parent = ids.parent;
1401            viewport.class = class;
1402            viewport.viewport_ui_cb = viewport_ui_cb;
1403
1404            let (mut delta_commands, recreate) = viewport.builder.patch(builder);
1405
1406            if recreate {
1407                log::debug!(
1408                    "Recreating window for viewport {:?} ({:?})",
1409                    ids.this,
1410                    viewport.builder.title
1411                );
1412                viewport.window = None;
1413                viewport.egui_winit = None;
1414                viewport.gl_surface = None;
1415            }
1416
1417            viewport.deferred_commands.append(&mut delta_commands);
1418
1419            entry.into_mut()
1420        }
1421    }
1422}
1423
1424/// This is called (via a callback) by user code to render immediate viewports,
1425/// i.e. viewport that are directly nested inside a parent viewport.
1426fn render_immediate_viewport(
1427    egui_ctx: &egui::Context,
1428    glutin: &RefCell<GlutinWindowContext>,
1429    painter: &RefCell<egui_glow::Painter>,
1430    beginning: Instant,
1431    immediate_viewport: ImmediateViewport<'_>,
1432) {
1433    profiling::function_scope!();
1434
1435    let ImmediateViewport {
1436        ids,
1437        builder,
1438        mut viewport_ui_cb,
1439    } = immediate_viewport;
1440
1441    let viewport_id = ids.this;
1442
1443    {
1444        let mut glutin = glutin.borrow_mut();
1445
1446        initialize_or_update_viewport(
1447            &mut glutin.viewports,
1448            ids,
1449            ViewportClass::Immediate,
1450            builder,
1451            None,
1452        );
1453
1454        let ret = event_loop_context::with_current_event_loop(|event_loop| {
1455            glutin.initialize_window(viewport_id, event_loop)
1456        });
1457
1458        if let Some(Err(err)) = ret {
1459            log::error!(
1460                "Failed to initialize a window for immediate viewport {viewport_id:?}: {err}"
1461            );
1462            return;
1463        }
1464    }
1465
1466    let input = {
1467        let mut glutin = glutin.borrow_mut();
1468
1469        let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else {
1470            return;
1471        };
1472        let (Some(egui_winit), Some(window)) = (&mut viewport.egui_winit, &viewport.window) else {
1473            return;
1474        };
1475        egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window, false);
1476
1477        let mut raw_input = egui_winit.take_egui_input(window);
1478        raw_input.viewports = glutin
1479            .viewports
1480            .iter()
1481            .map(|(id, viewport)| (*id, viewport.info.clone()))
1482            .collect();
1483        raw_input.time = Some(beginning.elapsed().as_secs_f64());
1484        raw_input
1485    };
1486
1487    // ---------------------------------------------------
1488    // Call the user ui-code, which could re-entrantly call this function again!
1489    // No locks may be hold while calling this function.
1490
1491    let egui::FullOutput {
1492        platform_output,
1493        textures_delta,
1494        shapes,
1495        pixels_per_point,
1496        viewport_output,
1497    } = egui_ctx.run(input, |ctx| {
1498        viewport_ui_cb(ctx);
1499    });
1500
1501    // ---------------------------------------------------
1502
1503    let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
1504
1505    let mut glutin = glutin.borrow_mut();
1506
1507    let GlutinWindowContext {
1508        current_gl_context,
1509        not_current_gl_context,
1510        viewports,
1511        ..
1512    } = &mut *glutin;
1513
1514    let Some(viewport) = viewports.get_mut(&viewport_id) else {
1515        return;
1516    };
1517
1518    viewport.info.events.clear(); // they should have been processed
1519
1520    let (Some(egui_winit), Some(window), Some(gl_surface)) = (
1521        &mut viewport.egui_winit,
1522        &viewport.window,
1523        &viewport.gl_surface,
1524    ) else {
1525        return;
1526    };
1527
1528    let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
1529
1530    change_gl_context(current_gl_context, not_current_gl_context, gl_surface);
1531
1532    let current_gl_context = current_gl_context.as_ref().unwrap();
1533
1534    if !gl_surface.is_current(current_gl_context) {
1535        log::error!(
1536            "egui::show_viewport_immediate: viewport {:?} ({:?}) was not created on main thread.",
1537            viewport.ids.this,
1538            viewport.builder.title
1539        );
1540    }
1541
1542    egui_glow::painter::clear(
1543        painter.borrow().gl(),
1544        screen_size_in_pixels,
1545        [0.0, 0.0, 0.0, 0.0],
1546    );
1547
1548    painter.borrow_mut().paint_and_update_textures(
1549        screen_size_in_pixels,
1550        pixels_per_point,
1551        &clipped_primitives,
1552        &textures_delta,
1553    );
1554
1555    {
1556        profiling::scope!("swap_buffers");
1557        if let Err(err) = gl_surface.swap_buffers(current_gl_context) {
1558            log::error!("swap_buffers failed: {err}");
1559        }
1560    }
1561
1562    egui_winit.handle_platform_output(window, platform_output);
1563
1564    event_loop_context::with_current_event_loop(|event_loop| {
1565        glutin.handle_viewport_output(event_loop, egui_ctx, &viewport_output);
1566    });
1567}
1568
1569#[cfg(feature = "__screenshot")]
1570fn save_screenshot_and_exit(
1571    path: &str,
1572    painter: &egui_glow::Painter,
1573    screen_size_in_pixels: [u32; 2],
1574) {
1575    assert!(
1576        path.ends_with(".png"),
1577        "Expected EFRAME_SCREENSHOT_TO to end with '.png', got {path:?}"
1578    );
1579    let screenshot = painter.read_screen_rgba(screen_size_in_pixels);
1580    image::save_buffer(
1581        path,
1582        screenshot.as_raw(),
1583        screenshot.width() as u32,
1584        screenshot.height() as u32,
1585        image::ColorType::Rgba8,
1586    )
1587    .unwrap_or_else(|err| {
1588        panic!("Failed to save screenshot to {path:?}: {err}");
1589    });
1590    log::info!("Screenshot saved to {path:?}.");
1591
1592    #[expect(clippy::exit)]
1593    std::process::exit(0);
1594}