1#![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
44pub 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 app_creator: Option<AppCreator<'app>>,
57}
58
59struct GlowWinitRunning<'app> {
63 integration: EpiIntegration,
64 app: Box<dyn 'app + App>,
65
66 glutin: Rc<RefCell<GlutinWindowContext>>,
68
69 painter: Rc<RefCell<egui_glow::Painter>>,
71}
72
73struct 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 viewport_ui_cb: Option<Arc<DeferredViewportUiCallback>>,
115
116 gl_surface: Option<glutin::surface::Surface<glutin::surface::WindowSurface>>,
119 window: Option<Arc<Window>>,
120 egui_winit: Option<egui_winit::State>,
121}
122
123impl<'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); let mut glutin_window_context = unsafe {
161 GlutinWindowContext::new(egui_ctx, winit_window_builder, native_options, event_loop)?
162 };
163
164 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(); 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(); 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 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 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 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 running
417 .glutin
418 .borrow_mut()
419 .initialize_all_windows(event_loop);
420 running
421 } else {
422 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 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 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; if clear_before_update {
580 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 let full_output =
615 self.integration
616 .update(self.app.as_mut(), viewport_ui_cb.as_deref(), raw_input);
617
618 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(); 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 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 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 #[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()); integration.maybe_autosave(app.as_mut(), Some(&window));
745
746 if window.is_minimized() == Some(true) {
747 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 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 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 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 viewport.info.events.push(egui::ViewportEvent::Close);
829
830 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 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; }
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 use glutin::prelude::*;
926 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 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 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 let display_builder = glutin_winit::DisplayBuilder::new()
965 .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 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 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 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 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 #[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 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 log::trace!("made context current. setting swap interval for surface");
1211 if let Err(err) = gl_surface.set_swap_interval(¤t_gl_context, self.swap_interval)
1212 {
1213 log::warn!("Failed to set swap interval due to error: {err}");
1214 }
1215
1216 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 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 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: _, },
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 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 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 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 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 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
1424fn 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 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 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(); 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}