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
20pub(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 Before(winit::window::Window),
40 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 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 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 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 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 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 let surface = unsafe { display.create_window_surface(&config, &attrs)? };
284
285 let context_attributes = ContextAttributesBuilder::new().build(Some(window_handle));
286 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 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 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 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 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 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 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 let surface = unsafe { display.create_window_surface(&config, &attrs)? };
462
463 let context_attributes = ContextAttributesBuilder::new().build(Some(window_handle));
464 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 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 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 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 #[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
568pub(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 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 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 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 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#[cfg(windows)]
732pub(crate) fn warmup() {
733 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
755fn check_wr_gl_version(gl: &dyn gl::Gl) -> Result<(), String> {
757 let mut version = [0; 2];
758 let is_2_or_1;
759 unsafe {
761 gl.get_integer_v(gl::MAJOR_VERSION, &mut version[..1]);
762 is_2_or_1 = gl.get_error() == gl::INVALID_ENUM; 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#[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 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 rbos: [u32; 2],
841 fbo: u32,
842}
843impl GlutinHeadless {
844 fn new(gl: &Rc<dyn gl::Gl>, hidden_window: winit::window::Window) -> Self {
845 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}