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,
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
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: 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 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 #[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); 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(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 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 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 running
400 .glutin
401 .borrow_mut()
402 .initialize_all_windows(event_loop);
403 running
404 } else {
405 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 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; if clear_before_update {
558 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 let full_output =
593 self.integration
594 .update(self.app.as_mut(), viewport_ui_cb.as_deref(), raw_input);
595
596 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(); 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 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 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 #[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()); integration.maybe_autosave(app.as_mut(), Some(&window));
723
724 if window.is_minimized() == Some(true) {
725 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 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 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 viewport.info.events.push(egui::ViewportEvent::Close);
792
793 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 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; }
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 use glutin::prelude::*;
903 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 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 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 let display_builder = glutin_winit::DisplayBuilder::new()
942 .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 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 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 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 #[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 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 log::trace!("made context current. setting swap interval for surface");
1176 if let Err(err) = gl_surface.set_swap_interval(¤t_gl_context, self.swap_interval)
1177 {
1178 log::warn!("Failed to set swap interval due to error: {err}");
1179 }
1180
1181 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 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 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: _, },
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 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 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 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 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 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
1385fn 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 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 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(); 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}