zng_view/
gl.rs

1use std::{cell::Cell, error::Error, ffi::CString, fmt, mem, num::NonZeroU32, rc::Rc, thread};
2
3use gleam::gl;
4use glutin::{
5    config::{Api, ConfigSurfaceTypes, ConfigTemplateBuilder},
6    context::{ContextAttributesBuilder, PossiblyCurrentContext},
7    display::{Display, DisplayApiPreference},
8    prelude::*,
9    surface::{Surface, SurfaceAttributesBuilder, WindowSurface},
10};
11use rustc_hash::FxHashSet;
12use winit::{dpi::PhysicalSize, event_loop::ActiveEventLoop};
13use zng_txt::ToTxt as _;
14use zng_view_api::window::{RenderMode, WindowId};
15
16use raw_window_handle::*;
17
18use crate::{AppEvent, AppEventSender, util};
19
20/// Create and track the current OpenGL context.
21pub(crate) struct GlContextManager {
22    current: Rc<Cell<Option<WindowId>>>,
23    unsupported_headed: FxHashSet<TryConfig>,
24    unsupported_headless: FxHashSet<TryConfig>,
25}
26
27impl Default for GlContextManager {
28    fn default() -> Self {
29        Self {
30            current: Rc::new(Cell::new(None)),
31            unsupported_headed: FxHashSet::default(),
32            unsupported_headless: FxHashSet::default(),
33        }
34    }
35}
36
37enum GlWindowCreation {
38    /// Windows requires this.
39    Before(winit::window::Window),
40    /// Other platforms don't. X11 requires this because it needs to set the XVisualID.
41    After(winit::window::WindowAttributes),
42}
43fn winit_create_window(winit_loop: &ActiveEventLoop, window: &winit::window::WindowAttributes) -> winit::window::Window {
44    let mut retries = 0;
45    loop {
46        match winit_loop.create_window(window.clone()) {
47            Ok(w) => break w,
48            Err(e) => {
49                // Some platforms work after a retry
50                // X11: After a GLXBadWindow
51                retries += 1;
52                if retries == 10 {
53                    panic!("cannot create winit window, {e}")
54                } else if retries > 1 {
55                    tracing::error!("cannot create winit window (retry={retries}), {e}");
56                    thread::sleep(std::time::Duration::from_millis(retries * 100));
57                }
58            }
59        }
60    }
61}
62
63impl GlContextManager {
64    /// New window context.
65    pub(crate) fn create_headed(
66        &mut self,
67        id: WindowId,
68        window: winit::window::WindowAttributes,
69        winit_loop: &ActiveEventLoop,
70        render_mode: RenderMode,
71        sender: &AppEventSender,
72        prefer_egl: bool,
73    ) -> (winit::window::Window, GlContext) {
74        let mut errors = vec![];
75
76        for config in TryConfig::iter(render_mode) {
77            if self.unsupported_headed.contains(&config) {
78                errors.push((config, "previous attempt failed, not supported".into()));
79                continue;
80            }
81
82            let window = if cfg!(windows) || matches!(config.mode, RenderMode::Software) {
83                GlWindowCreation::Before(winit_create_window(winit_loop, &window))
84            } else {
85                GlWindowCreation::After(window.clone())
86            };
87
88            let r = util::catch_suppress(std::panic::AssertUnwindSafe(|| match config.mode {
89                RenderMode::Dedicated => self.create_headed_glutin(winit_loop, id, window, config.hardware_acceleration, prefer_egl),
90                RenderMode::Integrated => self.create_headed_glutin(winit_loop, id, window, Some(false), prefer_egl),
91                RenderMode::Software => self.create_headed_swgl(winit_loop, id, window),
92            }));
93
94            let error = match r {
95                Ok(Ok(r)) => return r,
96                Ok(Err(e)) => e,
97                Err(panic) => {
98                    let component = match config.mode {
99                        RenderMode::Dedicated => "glutin (headed, dedicated)",
100                        RenderMode::Integrated => "glutin (headed, integrated)",
101                        RenderMode::Software => "swgl (headed)",
102                    };
103                    let _ = sender.send(AppEvent::Notify(zng_view_api::Event::RecoveredFromComponentPanic {
104                        component: component.into(),
105                        recover: "will try other modes".into(),
106                        panic: panic.to_txt(),
107                    }));
108                    panic.msg.into()
109                }
110            };
111
112            tracing::error!("[{}] {}", config.name(), error);
113            errors.push((config, error));
114
115            self.unsupported_headed.insert(config);
116        }
117
118        let mut msg = "failed to create headed open-gl context:\n".to_owned();
119        for (config, error) in errors {
120            use std::fmt::Write;
121            writeln!(&mut msg, "  {:?}: {}", config.name(), error).unwrap();
122        }
123
124        panic!("{msg}")
125    }
126
127    /// New headless context.
128    pub(crate) fn create_headless(
129        &mut self,
130        id: WindowId,
131        winit_loop: &ActiveEventLoop,
132        render_mode: RenderMode,
133        sender: &AppEventSender,
134        prefer_egl: bool,
135    ) -> GlContext {
136        let mut errors = vec![];
137
138        for config in TryConfig::iter(render_mode) {
139            if self.unsupported_headed.contains(&config) {
140                errors.push((config, "previous attempt failed, not supported".into()));
141                continue;
142            }
143
144            let r = util::catch_suppress(std::panic::AssertUnwindSafe(|| match config.mode {
145                RenderMode::Dedicated => self.create_headless_glutin(id, winit_loop, config.hardware_acceleration, prefer_egl),
146                RenderMode::Integrated => self.create_headless_glutin(id, winit_loop, Some(false), prefer_egl),
147                RenderMode::Software => self.create_headless_swgl(id),
148            }));
149
150            let error = match r {
151                Ok(Ok(ctx)) => return ctx,
152                Ok(Err(e)) => e,
153                Err(panic) => {
154                    let component = match config.mode {
155                        RenderMode::Dedicated => "glutin (headless, dedicated)",
156                        RenderMode::Integrated => "glutin (headless, integrated)",
157                        RenderMode::Software => "swgl (headless)",
158                    };
159                    let _ = sender.send(AppEvent::Notify(zng_view_api::Event::RecoveredFromComponentPanic {
160                        component: component.into(),
161                        recover: "will try other modes".into(),
162                        panic: panic.to_txt(),
163                    }));
164                    panic.msg.into()
165                }
166            };
167
168            tracing::error!("[{}] {}", config.name(), error);
169            errors.push((config, error));
170
171            self.unsupported_headless.insert(config);
172        }
173
174        let mut msg = "failed to create headless open-gl context:\n".to_owned();
175        for (config, error) in errors {
176            use std::fmt::Write;
177            writeln!(&mut msg, "  {:?}: {}", config.name(), error).unwrap();
178        }
179
180        panic!("{msg}")
181    }
182
183    fn create_headed_glutin(
184        &mut self,
185        event_loop: &ActiveEventLoop,
186        id: WindowId,
187        window: GlWindowCreation,
188        hardware: Option<bool>,
189        prefer_egl: bool,
190    ) -> Result<(winit::window::Window, GlContext), Box<dyn Error>> {
191        #[cfg(windows)]
192        let display_pref = {
193            let handle = Some(match &window {
194                GlWindowCreation::Before(w) => w.window_handle().unwrap().as_raw(),
195                GlWindowCreation::After(_) => unreachable!(),
196            });
197            if prefer_egl {
198                DisplayApiPreference::EglThenWgl(handle)
199            } else {
200                DisplayApiPreference::WglThenEgl(handle)
201            }
202        };
203
204        #[cfg(any(
205            target_os = "linux",
206            target_os = "dragonfly",
207            target_os = "freebsd",
208            target_os = "openbsd",
209            target_os = "netbsd",
210        ))]
211        let display_pref = {
212            let handle = Box::new(winit::platform::x11::register_xlib_error_hook);
213            if prefer_egl {
214                DisplayApiPreference::EglThenGlx(handle)
215            } else {
216                DisplayApiPreference::GlxThenEgl(handle)
217            }
218        };
219
220        #[cfg(target_os = "android")]
221        let display_pref = DisplayApiPreference::Egl;
222
223        #[cfg(target_os = "macos")]
224        let display_pref = DisplayApiPreference::Cgl;
225
226        let _ = prefer_egl;
227
228        let display_handle = match &window {
229            GlWindowCreation::Before(w) => w.display_handle().unwrap().as_raw(),
230            GlWindowCreation::After(_) => event_loop.display_handle().unwrap().as_raw(),
231        };
232
233        // SAFETY: we are trusting the `raw_display_handle` from winit here.
234        let display = unsafe { Display::new(display_handle, display_pref) }?;
235
236        let mut template = ConfigTemplateBuilder::new()
237            .with_alpha_size(8)
238            .with_transparency(cfg!(not(target_os = "android")))
239            .with_surface_type(ConfigSurfaceTypes::WINDOW)
240            .prefer_hardware_accelerated(hardware);
241        if let GlWindowCreation::Before(w) = &window {
242            template = template.compatible_with_native_window(w.window_handle().unwrap().as_raw());
243        }
244        let template = template.build();
245
246        // SAFETY: we are holding the `window` reference.
247        let config = unsafe { display.find_configs(template)?.next().ok_or("no display config") }?;
248
249        let window = match window {
250            GlWindowCreation::Before(w) => w,
251            GlWindowCreation::After(w) => {
252                #[cfg(any(
253                    target_os = "linux",
254                    target_os = "dragonfly",
255                    target_os = "freebsd",
256                    target_os = "openbsd",
257                    target_os = "netbsd"
258                ))]
259                let w = {
260                    use glutin::platform::x11::X11GlConfigExt as _;
261                    use winit::platform::x11::WindowAttributesExtX11 as _;
262
263                    if let Some(id) = config.x11_visual() {
264                        w.with_x11_visual(id.visual_id() as _)
265                    } else {
266                        w
267                    }
268                };
269                winit_create_window(event_loop, &w)
270            }
271        };
272
273        let window_handle = window.window_handle().unwrap().as_raw();
274
275        let size = window.inner_size();
276        let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
277            window_handle,
278            NonZeroU32::new(size.width).unwrap(),
279            NonZeroU32::new(size.height).unwrap(),
280        );
281
282        // SAFETY: the window handle is valid.
283        let surface = unsafe { display.create_window_surface(&config, &attrs)? };
284
285        let context_attributes = ContextAttributesBuilder::new().build(Some(window_handle));
286        // SAFETY: the window handle is valid.
287        let context = unsafe { display.create_context(&config, &context_attributes)? };
288
289        self.current.set(Some(id));
290        let context = context.make_current(&surface)?;
291
292        let gl_api = config.api();
293        let gl = if gl_api.contains(Api::OPENGL) {
294            // SAFETY: function pointers are directly from safe glutin here.
295            unsafe {
296                gl::GlFns::load_with(|symbol| {
297                    let symbol = CString::new(symbol).unwrap();
298                    display.get_proc_address(symbol.as_c_str())
299                })
300            }
301        } else if gl_api.contains(Api::GLES3) {
302            // SAFETY: function pointers are directly from safe glutin here.
303            unsafe {
304                gl::GlesFns::load_with(|symbol| {
305                    let symbol = CString::new(symbol).unwrap();
306                    display.get_proc_address(symbol.as_c_str())
307                })
308            }
309        } else {
310            return Err("no OpenGL or GLES3 available".into());
311        };
312
313        check_wr_gl_version(&*gl)?;
314
315        #[cfg(debug_assertions)]
316        let gl = gl::ErrorCheckingGl::wrap(gl.clone());
317
318        let mut context = GlContext {
319            id,
320            current: self.current.clone(),
321            backend: GlBackend::Glutin {
322                context,
323                surface,
324                headless: None,
325            },
326            gl,
327
328            render_mode: if hardware == Some(false) {
329                RenderMode::Integrated
330            } else {
331                RenderMode::Dedicated
332            },
333        };
334
335        context.resize(size);
336
337        Ok((window, context))
338    }
339
340    fn create_headed_swgl(
341        &mut self,
342        event_loop: &ActiveEventLoop,
343        id: WindowId,
344        window: GlWindowCreation,
345    ) -> Result<(winit::window::Window, GlContext), Box<dyn Error>> {
346        #[cfg(not(feature = "software"))]
347        {
348            let _ = (id, window, event_loop);
349            return Err("zng-view not build with \"software\" backend".into());
350        }
351
352        #[cfg(target_os = "android")]
353        {
354            let _ = (id, window, event_loop);
355            return Err("software blit not implemented for Android".into());
356        }
357
358        #[cfg(all(feature = "software", not(target_os = "android")))]
359        {
360            let window = match window {
361                GlWindowCreation::Before(w) => w,
362                GlWindowCreation::After(w) => event_loop.create_window(w)?,
363            };
364
365            // SAFETY: softbuffer context is managed like gl context, it is dropped before the window is dropped.
366            let static_window_ref = unsafe { mem::transmute::<&winit::window::Window, &'static winit::window::Window>(&window) };
367            let blit_context = softbuffer::Context::new(static_window_ref)?;
368            let blit_surface = softbuffer::Surface::new(&blit_context, static_window_ref)?;
369
370            let context = swgl::Context::create();
371            let gl = Rc::new(context);
372
373            // create_headed_glutin returns as current.
374            self.current.set(Some(id));
375            context.make_current();
376
377            let context = GlContext {
378                id,
379                current: self.current.clone(),
380                backend: GlBackend::Swgl {
381                    context,
382                    blit: Some((blit_context, blit_surface)),
383                },
384                gl,
385                render_mode: RenderMode::Software,
386            };
387            Ok((window, context))
388        }
389    }
390
391    fn create_headless_glutin(
392        &mut self,
393        id: WindowId,
394        winit_loop: &ActiveEventLoop,
395        hardware: Option<bool>,
396        prefer_egl: bool,
397    ) -> Result<GlContext, Box<dyn Error>> {
398        let hidden_window = winit::window::WindowAttributes::default()
399            .with_transparent(true)
400            .with_inner_size(PhysicalSize::new(1u32, 1u32))
401            .with_visible(false)
402            .with_decorations(false);
403        let hidden_window = winit_loop.create_window(hidden_window)?;
404
405        let display_handle = winit_loop.display_handle().unwrap().as_raw();
406        let window_handle = hidden_window.window_handle().unwrap().as_raw();
407
408        #[cfg(windows)]
409        let display_pref = if prefer_egl {
410            DisplayApiPreference::EglThenWgl(Some(window_handle))
411        } else {
412            DisplayApiPreference::WglThenEgl(Some(window_handle))
413        };
414
415        #[cfg(any(
416            target_os = "linux",
417            target_os = "dragonfly",
418            target_os = "freebsd",
419            target_os = "openbsd",
420            target_os = "netbsd"
421        ))]
422        let display_pref = {
423            let handle = Box::new(winit::platform::x11::register_xlib_error_hook);
424            if prefer_egl {
425                DisplayApiPreference::EglThenGlx(handle)
426            } else {
427                DisplayApiPreference::GlxThenEgl(handle)
428            }
429        };
430
431        #[cfg(target_os = "android")]
432        let display_pref = DisplayApiPreference::Egl;
433
434        #[cfg(target_os = "macos")]
435        let display_pref = DisplayApiPreference::Cgl;
436
437        let _ = prefer_egl;
438
439        // SAFETY: we are trusting the `raw_display_handle` from winit here.
440        let display = unsafe { Display::new(display_handle, display_pref) }?;
441
442        let template = ConfigTemplateBuilder::new()
443            .with_alpha_size(8)
444            .with_transparency(true)
445            .compatible_with_native_window(window_handle)
446            .with_surface_type(ConfigSurfaceTypes::WINDOW)
447            .prefer_hardware_accelerated(hardware)
448            .build();
449
450        // SAFETY: we are holding the `window` reference.
451        let config = unsafe { display.find_configs(template)?.next().ok_or("no display config") }?;
452
453        let size = hidden_window.inner_size();
454        let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
455            window_handle,
456            NonZeroU32::new(size.width).unwrap(),
457            NonZeroU32::new(size.height).unwrap(),
458        );
459
460        // SAFETY: the window handle is valid.
461        let surface = unsafe { display.create_window_surface(&config, &attrs)? };
462
463        let context_attributes = ContextAttributesBuilder::new().build(Some(window_handle));
464        // SAFETY: the window handle is valid.
465        let context = unsafe { display.create_context(&config, &context_attributes)? };
466
467        self.current.set(Some(id));
468        let context = context.make_current(&surface)?;
469
470        let gl_api = config.api();
471        let gl = if gl_api.contains(Api::OPENGL) {
472            // SAFETY: function pointers are directly from safe glutin here.
473            unsafe {
474                gl::GlFns::load_with(|symbol| {
475                    let symbol = CString::new(symbol).unwrap();
476                    display.get_proc_address(symbol.as_c_str())
477                })
478            }
479        } else if gl_api.contains(Api::GLES3) {
480            // SAFETY: function pointers are directly from safe glutin here.
481            unsafe {
482                gl::GlesFns::load_with(|symbol| {
483                    let symbol = CString::new(symbol).unwrap();
484                    display.get_proc_address(symbol.as_c_str())
485                })
486            }
487        } else {
488            return Err("no OpenGL or GLES3 available".into());
489        };
490
491        check_wr_gl_version(&*gl)?;
492
493        #[cfg(debug_assertions)]
494        let gl = gl::ErrorCheckingGl::wrap(gl.clone());
495
496        let mut context = GlContext {
497            id,
498            current: self.current.clone(),
499            backend: GlBackend::Glutin {
500                context,
501                surface,
502                headless: Some(GlutinHeadless::new(&gl, hidden_window)),
503            },
504            gl,
505
506            render_mode: if hardware == Some(false) {
507                RenderMode::Integrated
508            } else {
509                RenderMode::Dedicated
510            },
511        };
512
513        context.resize(size);
514
515        Ok(context)
516    }
517
518    fn create_headless_swgl(&mut self, id: WindowId) -> Result<GlContext, Box<dyn Error>> {
519        #[cfg(not(feature = "software"))]
520        {
521            let _ = id;
522            return Err("zng-view not build with \"software\" backend".into());
523        }
524
525        #[cfg(feature = "software")]
526        {
527            let context = swgl::Context::create();
528            let gl = Rc::new(context);
529
530            // create_headless_glutin returns as current.
531            self.current.set(Some(id));
532            context.make_current();
533
534            Ok(GlContext {
535                id,
536                current: self.current.clone(),
537                backend: GlBackend::Swgl { context, blit: None },
538                gl,
539                render_mode: RenderMode::Software,
540            })
541        }
542    }
543}
544
545enum GlBackend {
546    Glutin {
547        headless: Option<GlutinHeadless>,
548        context: PossiblyCurrentContext,
549        surface: Surface<WindowSurface>,
550    },
551
552    #[cfg(feature = "software")]
553    Swgl {
554        context: swgl::Context,
555        // is None for headless.
556        #[cfg(not(target_os = "android"))]
557        blit: Option<(
558            softbuffer::Context<&'static winit::window::Window>,
559            softbuffer::Surface<&'static winit::window::Window, &'static winit::window::Window>,
560        )>,
561        #[cfg(target_os = "android")]
562        blit: Option<((), ())>,
563    },
564
565    Dropped,
566}
567
568/// OpenGL context managed by [`GlContextManager`].
569pub(crate) struct GlContext {
570    id: WindowId,
571    current: Rc<Cell<Option<WindowId>>>,
572
573    backend: GlBackend,
574
575    gl: Rc<dyn gl::Gl>,
576    render_mode: RenderMode,
577}
578impl fmt::Debug for GlContext {
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        f.debug_struct("GlContext")
581            .field("id", &self.id)
582            .field("is_current", &self.is_current())
583            .field("render_mode", &self.render_mode)
584            .finish_non_exhaustive()
585    }
586}
587impl GlContext {
588    /// If the context is backed by SWGL.
589    pub(crate) fn is_software(&self) -> bool {
590        #[cfg(feature = "software")]
591        {
592            matches!(&self.backend, GlBackend::Swgl { .. })
593        }
594        #[cfg(not(feature = "software"))]
595        {
596            false
597        }
598    }
599
600    pub fn is_current(&self) -> bool {
601        Some(self.id) == self.current.get()
602    }
603
604    pub(crate) fn gl(&self) -> &Rc<dyn gl::Gl> {
605        &self.gl
606    }
607
608    pub(crate) fn render_mode(&self) -> RenderMode {
609        self.render_mode
610    }
611
612    pub(crate) fn resize(&mut self, size: PhysicalSize<u32>) {
613        assert!(self.is_current());
614
615        match &mut self.backend {
616            GlBackend::Glutin {
617                context,
618                surface,
619                headless,
620            } => {
621                if let Some(h) = headless {
622                    h.resize(&self.gl, size.width as _, size.height as _);
623                } else {
624                    let width = NonZeroU32::new(size.width.max(1)).unwrap();
625                    let height = NonZeroU32::new(size.height.max(1)).unwrap();
626                    surface.resize(context, width, height);
627                }
628            }
629            #[cfg(feature = "software")]
630            GlBackend::Swgl { context, blit } => {
631                // NULL means SWGL manages the buffer, it also retains the buffer if the size did not change.
632                let w = size.width.max(1);
633                let h = size.height.max(1);
634                context.init_default_framebuffer(0, 0, w as i32, h as i32, 0, std::ptr::null_mut());
635
636                #[cfg(not(target_os = "android"))]
637                if let Some((_, surface)) = blit {
638                    surface.resize(NonZeroU32::new(w).unwrap(), NonZeroU32::new(h).unwrap()).unwrap();
639                }
640
641                #[cfg(target_os = "android")]
642                let _ = blit;
643            }
644            GlBackend::Dropped => unreachable!(),
645        }
646    }
647
648    pub(crate) fn make_current(&mut self) {
649        let id = Some(self.id);
650        if self.current.get() != id {
651            self.current.set(id);
652
653            match &self.backend {
654                GlBackend::Glutin { context, surface, .. } => context.make_current(surface).unwrap(),
655                #[cfg(feature = "software")]
656                GlBackend::Swgl { context, .. } => context.make_current(),
657                GlBackend::Dropped => unreachable!(),
658            }
659        }
660    }
661
662    pub(crate) fn swap_buffers(&mut self) {
663        assert!(self.is_current());
664
665        match &mut self.backend {
666            GlBackend::Glutin {
667                context,
668                surface,
669                headless,
670            } => {
671                if headless.is_none() {
672                    surface.swap_buffers(context).unwrap()
673                }
674            }
675            #[cfg(feature = "software")]
676            GlBackend::Swgl { context, blit } => {
677                #[cfg(target_os = "android")]
678                let _ = (context, blit);
679
680                #[cfg(not(target_os = "android"))]
681                if let Some((_, blit_surface)) = blit {
682                    gl::Gl::finish(context);
683                    let (data_ptr, w, h, stride) = context.get_color_buffer(0, true);
684
685                    if w == 0 || h == 0 {
686                        return;
687                    }
688
689                    // SAFETY: we trust SWGL
690                    assert!(stride == w * 4);
691                    let frame = unsafe { std::slice::from_raw_parts(data_ptr as *const u8, w as usize * h as usize * 4) };
692                    // bgra, max_y=0
693                    let frame = frame.chunks_exact(stride as _).rev().flat_map(|row| row.chunks_exact(4));
694                    let mut buffer = blit_surface.buffer_mut().unwrap();
695                    for (argb, bgra) in buffer.iter_mut().zip(frame) {
696                        let blue = bgra[0] as u32;
697                        let green = bgra[1] as u32;
698                        let red = bgra[2] as u32;
699                        let alpha = bgra[3] as u32;
700                        *argb = blue | (green << 8) | (red << 16) | (alpha << 24);
701                    }
702
703                    buffer.present().unwrap();
704                }
705            }
706            GlBackend::Dropped => unreachable!(),
707        }
708    }
709}
710impl Drop for GlContext {
711    fn drop(&mut self) {
712        self.make_current();
713
714        match mem::replace(&mut self.backend, GlBackend::Dropped) {
715            GlBackend::Glutin { headless, .. } => {
716                if let Some(h) = headless {
717                    let _ = h.hidden_window;
718
719                    h.destroy(&self.gl);
720                }
721            }
722            #[cfg(feature = "software")]
723            GlBackend::Swgl { context, .. } => context.destroy(),
724            GlBackend::Dropped => unreachable!(),
725        }
726    }
727}
728
729/// Warmup the OpenGL driver in a throwaway thread, some NVIDIA drivers have a slow startup (500ms~),
730/// hopefully this loads it in parallel while the app is starting up so we don't block creating the first window.
731#[cfg(windows)]
732pub(crate) fn warmup() {
733    // idea copied from here:
734    // https://hero.handmade.network/forums/code-discussion/t/2503-day_235_opengl%2527s_pixel_format_takes_a_long_time#13029
735
736    use windows_sys::Win32::Graphics::{
737        Gdi::*,
738        OpenGL::{self},
739    };
740
741    let _ = std::thread::Builder::new()
742        .name("warmup".to_owned())
743        .stack_size(3 * 64 * 1024)
744        .spawn(|| unsafe {
745            let _span = tracing::trace_span!("open-gl-init").entered();
746            let hdc = GetDC(0);
747            let _ = OpenGL::DescribePixelFormat(hdc, 0, 0, std::ptr::null_mut());
748            ReleaseDC(0, hdc);
749        });
750}
751
752#[cfg(not(windows))]
753pub(crate) fn warmup() {}
754
755// check if equal or newer then 3.1
756fn check_wr_gl_version(gl: &dyn gl::Gl) -> Result<(), String> {
757    let mut version = [0; 2];
758    let is_2_or_1;
759    // SAFETY: get_integer_v API available in all impls
760    unsafe {
761        gl.get_integer_v(gl::MAJOR_VERSION, &mut version[..1]);
762        is_2_or_1 = gl.get_error() == gl::INVALID_ENUM; // MAJOR_VERSION is only 3.0 and above
763        gl.get_integer_v(gl::MINOR_VERSION, &mut version[1..]);
764    };
765
766    if !is_2_or_1 && version[0] >= 3 {
767        let min_minor = match gl.get_type() {
768            gl::GlType::Gl => 1,
769            gl::GlType::Gles => 0,
770        };
771        if version[1] >= min_minor {
772            return Ok(());
773        }
774    }
775    Err(format!(
776        "webrender requires OpenGL >=3.1 or OpenGL ES >=3.0, found {}",
777        gl.get_string(gl::VERSION)
778    ))
779}
780
781/// Glutin, SWGL config to attempt.
782#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
783struct TryConfig {
784    mode: RenderMode,
785    hardware_acceleration: Option<bool>,
786}
787impl TryConfig {
788    fn iter(mode: RenderMode) -> impl Iterator<Item = TryConfig> {
789        let mut configs = Vec::with_capacity(4);
790        let mut try_hardware_none = false;
791        for mode in mode.with_fallbacks() {
792            match mode {
793                RenderMode::Dedicated => {
794                    configs.push(TryConfig {
795                        mode,
796                        hardware_acceleration: Some(true),
797                    });
798                    try_hardware_none = true;
799                }
800                RenderMode::Integrated => configs.push(TryConfig {
801                    mode,
802                    hardware_acceleration: Some(false),
803                }),
804                RenderMode::Software => {
805                    if mem::take(&mut try_hardware_none) {
806                        // some dedicated hardware end-up classified as generic integrated for some reason,
807                        // so we try `None`, after `Some(false)` and before `Software`.
808                        configs.push(TryConfig {
809                            mode: RenderMode::Dedicated,
810                            hardware_acceleration: None,
811                        });
812                    }
813                    configs.push(TryConfig {
814                        mode,
815                        hardware_acceleration: Some(false),
816                    });
817                }
818            }
819        }
820        configs.into_iter()
821    }
822
823    pub fn name(&self) -> &str {
824        match self.hardware_acceleration {
825            Some(true) => "Dedicated",
826            Some(false) => match self.mode {
827                RenderMode::Integrated => "Integrated",
828                RenderMode::Software => "Software",
829                RenderMode::Dedicated => unreachable!(),
830            },
831            None => "Dedicated (generic)",
832        }
833    }
834}
835
836struct GlutinHeadless {
837    hidden_window: winit::window::Window,
838
839    // actual surface.
840    rbos: [u32; 2],
841    fbo: u32,
842}
843impl GlutinHeadless {
844    fn new(gl: &Rc<dyn gl::Gl>, hidden_window: winit::window::Window) -> Self {
845        // create a surface for Webrender:
846
847        let rbos = gl.gen_renderbuffers(2);
848
849        let rbos = [rbos[0], rbos[1]];
850        let fbo = gl.gen_framebuffers(1)[0];
851
852        gl.bind_renderbuffer(gl::RENDERBUFFER, rbos[0]);
853        gl.renderbuffer_storage(gl::RENDERBUFFER, gl::RGBA8, 1, 1);
854
855        gl.bind_renderbuffer(gl::RENDERBUFFER, rbos[1]);
856        gl.renderbuffer_storage(gl::RENDERBUFFER, gl::DEPTH24_STENCIL8, 1, 1);
857
858        gl.viewport(0, 0, 1, 1);
859
860        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
861        gl.framebuffer_renderbuffer(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::RENDERBUFFER, rbos[0]);
862        gl.framebuffer_renderbuffer(gl::FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, gl::RENDERBUFFER, rbos[1]);
863
864        GlutinHeadless { hidden_window, rbos, fbo }
865    }
866
867    fn resize(&self, gl: &Rc<dyn gl::Gl>, width: i32, height: i32) {
868        gl.bind_renderbuffer(gl::RENDERBUFFER, self.rbos[0]);
869        gl.renderbuffer_storage(gl::RENDERBUFFER, gl::RGBA8, width, height);
870
871        gl.bind_renderbuffer(gl::RENDERBUFFER, self.rbos[1]);
872        gl.renderbuffer_storage(gl::RENDERBUFFER, gl::DEPTH24_STENCIL8, width, height);
873
874        gl.viewport(0, 0, width, height);
875    }
876
877    fn destroy(self, gl: &Rc<dyn gl::Gl>) {
878        gl.delete_framebuffers(&[self.fbo]);
879        gl.delete_renderbuffers(&self.rbos);
880    }
881}