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