1use glow::HasContext;
2use once_cell::sync::Lazy;
3use parking_lot::{Mutex, MutexGuard, RwLock};
4
5use std::{collections::HashMap, ffi, os::raw, ptr, rc::Rc, sync::Arc, time::Duration};
6
7const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1;
9
10const EGL_CONTEXT_FLAGS_KHR: i32 = 0x30FC;
11const EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR: i32 = 0x0001;
12const EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT: i32 = 0x30BF;
13const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8;
14const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
15const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202;
16const EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE: u32 = 0x348F;
17const EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED: u32 = 0x3451;
18const EGL_PLATFORM_SURFACELESS_MESA: u32 = 0x31DD;
19const EGL_GL_COLORSPACE_KHR: u32 = 0x309D;
20const EGL_GL_COLORSPACE_SRGB_KHR: u32 = 0x3089;
21
22type XOpenDisplayFun =
23 unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
24
25type XCloseDisplayFun = unsafe extern "system" fn(display: *mut raw::c_void) -> raw::c_int;
26
27type WlDisplayConnectFun =
28 unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
29
30type WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void);
31
32#[cfg(not(Emscripten))]
33type EglInstance = khronos_egl::DynamicInstance<khronos_egl::EGL1_4>;
34
35#[cfg(Emscripten)]
36type EglInstance = khronos_egl::Instance<khronos_egl::Static>;
37
38type WlEglWindowCreateFun = unsafe extern "system" fn(
39 surface: *const raw::c_void,
40 width: raw::c_int,
41 height: raw::c_int,
42) -> *mut raw::c_void;
43
44type WlEglWindowResizeFun = unsafe extern "system" fn(
45 window: *const raw::c_void,
46 width: raw::c_int,
47 height: raw::c_int,
48 dx: raw::c_int,
49 dy: raw::c_int,
50);
51
52type WlEglWindowDestroyFun = unsafe extern "system" fn(window: *const raw::c_void);
53
54type EglLabel = *const raw::c_void;
55
56#[allow(clippy::upper_case_acronyms)]
57type EGLDEBUGPROCKHR = Option<
58 unsafe extern "system" fn(
59 error: khronos_egl::Enum,
60 command: *const raw::c_char,
61 message_type: u32,
62 thread_label: EglLabel,
63 object_label: EglLabel,
64 message: *const raw::c_char,
65 ),
66>;
67
68const EGL_DEBUG_MSG_CRITICAL_KHR: u32 = 0x33B9;
69const EGL_DEBUG_MSG_ERROR_KHR: u32 = 0x33BA;
70const EGL_DEBUG_MSG_WARN_KHR: u32 = 0x33BB;
71const EGL_DEBUG_MSG_INFO_KHR: u32 = 0x33BC;
72
73type EglDebugMessageControlFun = unsafe extern "system" fn(
74 proc: EGLDEBUGPROCKHR,
75 attrib_list: *const khronos_egl::Attrib,
76) -> raw::c_int;
77
78unsafe extern "system" fn egl_debug_proc(
79 error: khronos_egl::Enum,
80 command_raw: *const raw::c_char,
81 message_type: u32,
82 _thread_label: EglLabel,
83 _object_label: EglLabel,
84 message_raw: *const raw::c_char,
85) {
86 let log_severity = match message_type {
87 EGL_DEBUG_MSG_CRITICAL_KHR | EGL_DEBUG_MSG_ERROR_KHR => log::Level::Error,
88 EGL_DEBUG_MSG_WARN_KHR => log::Level::Warn,
89 EGL_DEBUG_MSG_INFO_KHR => log::Level::Info,
90 _ => log::Level::Debug,
91 };
92 let command = unsafe { ffi::CStr::from_ptr(command_raw) }.to_string_lossy();
93 let message = if message_raw.is_null() {
94 "".into()
95 } else {
96 unsafe { ffi::CStr::from_ptr(message_raw) }.to_string_lossy()
97 };
98
99 log::log!(
100 log_severity,
101 "EGL '{}' code 0x{:x}: {}",
102 command,
103 error,
104 message,
105 );
106}
107
108#[derive(Debug)]
113enum DisplayRef {
114 X11(ptr::NonNull<raw::c_void>),
115 Wayland,
116}
117
118impl DisplayRef {
119 fn as_ptr(&self) -> *mut raw::c_void {
121 match *self {
122 Self::X11(ptr) => ptr.as_ptr(),
123 Self::Wayland => unreachable!(),
124 }
125 }
126}
127
128#[derive(Debug)]
134struct DisplayOwner {
135 library: libloading::Library,
136 display: DisplayRef,
137}
138
139impl Drop for DisplayOwner {
140 fn drop(&mut self) {
141 match self.display {
142 DisplayRef::X11(ptr) => unsafe {
143 let func: libloading::Symbol<XCloseDisplayFun> =
144 self.library.get(b"XCloseDisplay").unwrap();
145 func(ptr.as_ptr());
146 },
147 DisplayRef::Wayland => {}
148 }
149 }
150}
151
152fn open_x_display() -> Option<DisplayOwner> {
153 log::debug!("Loading X11 library to get the current display");
154 unsafe {
155 let library = find_library(&["libX11.so.6", "libX11.so"])?;
156 let func: libloading::Symbol<XOpenDisplayFun> = library.get(b"XOpenDisplay").unwrap();
157 let result = func(ptr::null());
158 ptr::NonNull::new(result).map(|ptr| DisplayOwner {
159 display: DisplayRef::X11(ptr),
160 library,
161 })
162 }
163}
164
165unsafe fn find_library(paths: &[&str]) -> Option<libloading::Library> {
166 for path in paths {
167 match unsafe { libloading::Library::new(path) } {
168 Ok(lib) => return Some(lib),
169 _ => continue,
170 };
171 }
172 None
173}
174
175fn test_wayland_display() -> Option<DisplayOwner> {
176 log::debug!("Loading Wayland library to get the current display");
180 let library = unsafe {
181 let client_library = find_library(&["libwayland-client.so.0", "libwayland-client.so"])?;
182 let wl_display_connect: libloading::Symbol<WlDisplayConnectFun> =
183 client_library.get(b"wl_display_connect").unwrap();
184 let wl_display_disconnect: libloading::Symbol<WlDisplayDisconnectFun> =
185 client_library.get(b"wl_display_disconnect").unwrap();
186 let display = ptr::NonNull::new(wl_display_connect(ptr::null()))?;
187 wl_display_disconnect(display.as_ptr());
188 find_library(&["libwayland-egl.so.1", "libwayland-egl.so"])?
189 };
190 Some(DisplayOwner {
191 library,
192 display: DisplayRef::Wayland,
193 })
194}
195
196#[derive(Clone, Copy, Debug)]
197enum SrgbFrameBufferKind {
198 None,
200 Core,
202 Khr,
204}
205
206fn choose_config(
208 egl: &EglInstance,
209 display: khronos_egl::Display,
210 srgb_kind: SrgbFrameBufferKind,
211) -> Result<(khronos_egl::Config, bool), crate::InstanceError> {
212 let tiers = [
214 (
215 "off-screen",
216 &[
217 khronos_egl::SURFACE_TYPE,
218 khronos_egl::PBUFFER_BIT,
219 khronos_egl::RENDERABLE_TYPE,
220 khronos_egl::OPENGL_ES2_BIT,
221 ][..],
222 ),
223 (
224 "presentation",
225 &[khronos_egl::SURFACE_TYPE, khronos_egl::WINDOW_BIT][..],
226 ),
227 #[cfg(not(target_os = "android"))]
228 (
229 "native-render",
230 &[khronos_egl::NATIVE_RENDERABLE, khronos_egl::TRUE as _][..],
231 ),
232 ];
233
234 let mut attributes = Vec::with_capacity(9);
235 for tier_max in (0..tiers.len()).rev() {
236 let name = tiers[tier_max].0;
237 log::debug!("\tTrying {}", name);
238
239 attributes.clear();
240 for &(_, tier_attr) in tiers[..=tier_max].iter() {
241 attributes.extend_from_slice(tier_attr);
242 }
243 match srgb_kind {
245 SrgbFrameBufferKind::None => {}
246 _ => {
247 attributes.push(khronos_egl::ALPHA_SIZE);
248 attributes.push(8);
249 }
250 }
251 attributes.push(khronos_egl::NONE);
252
253 match egl.choose_first_config(display, &attributes) {
254 Ok(Some(config)) => {
255 if tier_max == 1 {
256 log::warn!("EGL says it can present to the window but not natively",);
259 }
260 let tier_threshold = if cfg!(target_os = "android") || cfg!(windows) {
262 1
263 } else {
264 2
265 };
266 return Ok((config, tier_max >= tier_threshold));
267 }
268 Ok(None) => {
269 log::warn!("No config found!");
270 }
271 Err(e) => {
272 log::error!("error in choose_first_config: {:?}", e);
273 }
274 }
275 }
276
277 Err(crate::InstanceError::new(String::from(
279 "unable to find an acceptable EGL framebuffer configuration",
280 )))
281}
282
283#[derive(Clone, Debug)]
284struct EglContext {
285 instance: Arc<EglInstance>,
286 version: (i32, i32),
287 display: khronos_egl::Display,
288 raw: khronos_egl::Context,
289 pbuffer: Option<khronos_egl::Surface>,
290}
291
292impl EglContext {
293 fn make_current(&self) {
294 self.instance
295 .make_current(self.display, self.pbuffer, self.pbuffer, Some(self.raw))
296 .unwrap();
297 }
298 fn unmake_current(&self) {
299 self.instance
300 .make_current(self.display, None, None, None)
301 .unwrap();
302 }
303}
304
305pub struct AdapterContext {
308 glow: Mutex<glow::Context>,
309 egl: Option<EglContext>,
310}
311
312unsafe impl Sync for AdapterContext {}
313unsafe impl Send for AdapterContext {}
314
315impl AdapterContext {
316 pub fn is_owned(&self) -> bool {
317 self.egl.is_some()
318 }
319
320 pub fn egl_instance(&self) -> Option<&EglInstance> {
324 self.egl.as_ref().map(|egl| &*egl.instance)
325 }
326
327 pub fn raw_display(&self) -> Option<&khronos_egl::Display> {
331 self.egl.as_ref().map(|egl| &egl.display)
332 }
333
334 pub fn egl_version(&self) -> Option<(i32, i32)> {
338 self.egl.as_ref().map(|egl| egl.version)
339 }
340
341 pub fn raw_context(&self) -> *mut raw::c_void {
342 match self.egl {
343 Some(ref egl) => egl.raw.as_ptr(),
344 None => ptr::null_mut(),
345 }
346 }
347}
348
349struct EglContextLock<'a> {
350 instance: &'a Arc<EglInstance>,
351 display: khronos_egl::Display,
352}
353
354pub struct AdapterContextLock<'a> {
356 glow: MutexGuard<'a, glow::Context>,
357 egl: Option<EglContextLock<'a>>,
358}
359
360impl<'a> std::ops::Deref for AdapterContextLock<'a> {
361 type Target = glow::Context;
362
363 fn deref(&self) -> &Self::Target {
364 &self.glow
365 }
366}
367
368impl<'a> Drop for AdapterContextLock<'a> {
369 fn drop(&mut self) {
370 if let Some(egl) = self.egl.take() {
371 egl.instance
372 .make_current(egl.display, None, None, None)
373 .unwrap();
374 }
375 }
376}
377
378impl AdapterContext {
379 pub unsafe fn get_without_egl_lock(&self) -> MutexGuard<glow::Context> {
391 self.glow
392 .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
393 .expect("Could not lock adapter context. This is most-likely a deadlock.")
394 }
395
396 #[track_caller]
399 pub fn lock<'a>(&'a self) -> AdapterContextLock<'a> {
400 let glow = self
401 .glow
402 .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
405 .expect("Could not lock adapter context. This is most-likely a deadlock.");
406
407 let egl = self.egl.as_ref().map(|egl| {
408 egl.make_current();
409 EglContextLock {
410 instance: &egl.instance,
411 display: egl.display,
412 }
413 });
414
415 AdapterContextLock { glow, egl }
416 }
417}
418
419#[derive(Debug)]
420struct Inner {
421 egl: EglContext,
424 #[allow(unused)]
425 version: (i32, i32),
426 supports_native_window: bool,
427 config: khronos_egl::Config,
428 #[cfg_attr(Emscripten, allow(dead_code))]
429 wl_display: Option<*mut raw::c_void>,
430 #[cfg_attr(Emscripten, allow(dead_code))]
431 force_gles_minor_version: wgt::Gles3MinorVersion,
432 srgb_kind: SrgbFrameBufferKind,
434}
435
436static DISPLAYS_REFERENCE_COUNT: Lazy<Mutex<HashMap<usize, usize>>> = Lazy::new(Default::default);
440
441fn initialize_display(
442 egl: &EglInstance,
443 display: khronos_egl::Display,
444) -> Result<(i32, i32), khronos_egl::Error> {
445 let mut guard = DISPLAYS_REFERENCE_COUNT.lock();
446 *guard.entry(display.as_ptr() as usize).or_default() += 1;
447
448 egl.initialize(display)
452}
453
454fn terminate_display(
455 egl: &EglInstance,
456 display: khronos_egl::Display,
457) -> Result<(), khronos_egl::Error> {
458 let key = &(display.as_ptr() as usize);
459 let mut guard = DISPLAYS_REFERENCE_COUNT.lock();
460 let count_ref = guard
461 .get_mut(key)
462 .expect("Attempted to decref a display before incref was called");
463
464 if *count_ref > 1 {
465 *count_ref -= 1;
466
467 Ok(())
468 } else {
469 guard.remove(key);
470
471 egl.terminate(display)
472 }
473}
474
475impl Inner {
476 fn create(
477 flags: wgt::InstanceFlags,
478 egl: Arc<EglInstance>,
479 display: khronos_egl::Display,
480 force_gles_minor_version: wgt::Gles3MinorVersion,
481 ) -> Result<Self, crate::InstanceError> {
482 let version = initialize_display(&egl, display).map_err(|e| {
483 crate::InstanceError::with_source(
484 String::from("failed to initialize EGL display connection"),
485 e,
486 )
487 })?;
488 let vendor = egl
489 .query_string(Some(display), khronos_egl::VENDOR)
490 .unwrap();
491 let display_extensions = egl
492 .query_string(Some(display), khronos_egl::EXTENSIONS)
493 .unwrap()
494 .to_string_lossy();
495 log::debug!("Display vendor {:?}, version {:?}", vendor, version,);
496 log::debug!(
497 "Display extensions: {:#?}",
498 display_extensions.split_whitespace().collect::<Vec<_>>()
499 );
500
501 let srgb_kind = if version >= (1, 5) {
502 log::debug!("\tEGL surface: +srgb");
503 SrgbFrameBufferKind::Core
504 } else if display_extensions.contains("EGL_KHR_gl_colorspace") {
505 log::debug!("\tEGL surface: +srgb khr");
506 SrgbFrameBufferKind::Khr
507 } else {
508 log::warn!("\tEGL surface: -srgb");
509 SrgbFrameBufferKind::None
510 };
511
512 if log::max_level() >= log::LevelFilter::Trace {
513 log::trace!("Configurations:");
514 let config_count = egl.get_config_count(display).unwrap();
515 let mut configurations = Vec::with_capacity(config_count);
516 egl.get_configs(display, &mut configurations).unwrap();
517 for &config in configurations.iter() {
518 log::trace!("\tCONFORMANT=0x{:X}, RENDERABLE=0x{:X}, NATIVE_RENDERABLE=0x{:X}, SURFACE_TYPE=0x{:X}, ALPHA_SIZE={}",
519 egl.get_config_attrib(display, config, khronos_egl::CONFORMANT).unwrap(),
520 egl.get_config_attrib(display, config, khronos_egl::RENDERABLE_TYPE).unwrap(),
521 egl.get_config_attrib(display, config, khronos_egl::NATIVE_RENDERABLE).unwrap(),
522 egl.get_config_attrib(display, config, khronos_egl::SURFACE_TYPE).unwrap(),
523 egl.get_config_attrib(display, config, khronos_egl::ALPHA_SIZE).unwrap(),
524 );
525 }
526 }
527
528 let (config, supports_native_window) = choose_config(&egl, display, srgb_kind)?;
529
530 let supports_opengl = if version >= (1, 4) {
531 let client_apis = egl
532 .query_string(Some(display), khronos_egl::CLIENT_APIS)
533 .unwrap()
534 .to_string_lossy();
535 client_apis
536 .split(' ')
537 .any(|client_api| client_api == "OpenGL")
538 } else {
539 false
540 };
541 egl.bind_api(if supports_opengl {
542 khronos_egl::OPENGL_API
543 } else {
544 khronos_egl::OPENGL_ES_API
545 })
546 .unwrap();
547
548 let needs_robustness = true;
549 let mut khr_context_flags = 0;
550 let supports_khr_context = display_extensions.contains("EGL_KHR_create_context");
551
552 let mut context_attributes = vec![
554 khronos_egl::CONTEXT_MAJOR_VERSION,
555 3, ];
557
558 if force_gles_minor_version != wgt::Gles3MinorVersion::Automatic {
559 context_attributes.push(khronos_egl::CONTEXT_MINOR_VERSION);
560 context_attributes.push(match force_gles_minor_version {
561 wgt::Gles3MinorVersion::Version0 => 0,
562 wgt::Gles3MinorVersion::Version1 => 1,
563 wgt::Gles3MinorVersion::Version2 => 2,
564 _ => unreachable!(),
565 });
566 }
567
568 if flags.contains(wgt::InstanceFlags::DEBUG) {
569 if version >= (1, 5) {
570 log::debug!("\tEGL context: +debug");
571 context_attributes.push(khronos_egl::CONTEXT_OPENGL_DEBUG);
572 context_attributes.push(khronos_egl::TRUE as _);
573 } else if supports_khr_context {
574 log::debug!("\tEGL context: +debug KHR");
575 khr_context_flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
576 } else {
577 log::debug!("\tEGL context: -debug");
578 }
579 }
580 if needs_robustness {
581 if version >= (1, 5) && !display_extensions.contains("EGL_ANGLE_") {
585 log::debug!("\tEGL context: +robust access");
586 context_attributes.push(khronos_egl::CONTEXT_OPENGL_ROBUST_ACCESS);
587 context_attributes.push(khronos_egl::TRUE as _);
588 } else if display_extensions.contains("EGL_EXT_create_context_robustness") {
589 log::debug!("\tEGL context: +robust access EXT");
590 context_attributes.push(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT);
591 context_attributes.push(khronos_egl::TRUE as _);
592 } else {
593 log::warn!("\tEGL context: -robust access");
596 }
597
598 }
600 if khr_context_flags != 0 {
601 context_attributes.push(EGL_CONTEXT_FLAGS_KHR);
602 context_attributes.push(khr_context_flags);
603 }
604 context_attributes.push(khronos_egl::NONE);
605 let context = match egl.create_context(display, config, None, &context_attributes) {
606 Ok(context) => context,
607 Err(e) => {
608 return Err(crate::InstanceError::with_source(
609 String::from("unable to create GLES 3.x context"),
610 e,
611 ));
612 }
613 };
614
615 let pbuffer = if version >= (1, 5)
618 || display_extensions.contains("EGL_KHR_surfaceless_context")
619 || cfg!(Emscripten)
620 {
621 log::debug!("\tEGL context: +surfaceless");
622 None
623 } else {
624 let attributes = [
625 khronos_egl::WIDTH,
626 1,
627 khronos_egl::HEIGHT,
628 1,
629 khronos_egl::NONE,
630 ];
631 egl.create_pbuffer_surface(display, config, &attributes)
632 .map(Some)
633 .map_err(|e| {
634 crate::InstanceError::with_source(
635 String::from("error in create_pbuffer_surface"),
636 e,
637 )
638 })?
639 };
640
641 Ok(Self {
642 egl: EglContext {
643 instance: egl,
644 display,
645 raw: context,
646 pbuffer,
647 version,
648 },
649 version,
650 supports_native_window,
651 config,
652 wl_display: None,
653 srgb_kind,
654 force_gles_minor_version,
655 })
656 }
657}
658
659impl Drop for Inner {
660 fn drop(&mut self) {
661 if let Err(e) = self
662 .egl
663 .instance
664 .destroy_context(self.egl.display, self.egl.raw)
665 {
666 log::warn!("Error in destroy_context: {:?}", e);
667 }
668
669 if let Err(e) = terminate_display(&self.egl.instance, self.egl.display) {
670 log::warn!("Error in terminate: {:?}", e);
671 }
672 }
673}
674
675#[derive(Clone, Copy, Debug, PartialEq)]
676enum WindowKind {
677 Wayland,
678 X11,
679 AngleX11,
680 Unknown,
681}
682
683#[derive(Clone, Debug)]
684struct WindowSystemInterface {
685 display_owner: Option<Rc<DisplayOwner>>,
686 kind: WindowKind,
687}
688
689pub struct Instance {
690 wsi: WindowSystemInterface,
691 flags: wgt::InstanceFlags,
692 inner: Mutex<Inner>,
693}
694
695impl Instance {
696 pub fn raw_display(&self) -> khronos_egl::Display {
697 self.inner
698 .try_lock()
699 .expect("Could not lock instance. This is most-likely a deadlock.")
700 .egl
701 .display
702 }
703
704 pub fn egl_version(&self) -> (i32, i32) {
706 self.inner
707 .try_lock()
708 .expect("Could not lock instance. This is most-likely a deadlock.")
709 .version
710 }
711
712 pub fn egl_config(&self) -> khronos_egl::Config {
713 self.inner
714 .try_lock()
715 .expect("Could not lock instance. This is most-likely a deadlock.")
716 .config
717 }
718}
719
720unsafe impl Send for Instance {}
721unsafe impl Sync for Instance {}
722
723impl crate::Instance for Instance {
724 type A = super::Api;
725
726 unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
727 profiling::scope!("Init OpenGL (EGL) Backend");
728 #[cfg(Emscripten)]
729 let egl_result: Result<EglInstance, khronos_egl::Error> =
730 Ok(khronos_egl::Instance::new(khronos_egl::Static));
731
732 #[cfg(not(Emscripten))]
733 let egl_result = if cfg!(windows) {
734 unsafe {
735 khronos_egl::DynamicInstance::<khronos_egl::EGL1_4>::load_required_from_filename(
736 "libEGL.dll",
737 )
738 }
739 } else if cfg!(any(target_os = "macos", target_os = "ios")) {
740 unsafe {
741 khronos_egl::DynamicInstance::<khronos_egl::EGL1_4>::load_required_from_filename(
742 "libEGL.dylib",
743 )
744 }
745 } else {
746 unsafe { khronos_egl::DynamicInstance::<khronos_egl::EGL1_4>::load_required() }
747 };
748 let egl = match egl_result {
749 Ok(egl) => Arc::new(egl),
750 Err(e) => {
751 return Err(crate::InstanceError::with_source(
752 String::from("unable to open libEGL"),
753 e,
754 ));
755 }
756 };
757
758 let client_extensions = egl.query_string(None, khronos_egl::EXTENSIONS);
759
760 let client_ext_str = match client_extensions {
761 Ok(ext) => ext.to_string_lossy().into_owned(),
762 Err(_) => String::new(),
763 };
764 log::debug!(
765 "Client extensions: {:#?}",
766 client_ext_str.split_whitespace().collect::<Vec<_>>()
767 );
768
769 let wayland_library = if client_ext_str.contains("EGL_EXT_platform_wayland") {
770 test_wayland_display()
771 } else {
772 None
773 };
774 let x11_display_library = if client_ext_str.contains("EGL_EXT_platform_x11") {
775 open_x_display()
776 } else {
777 None
778 };
779 let angle_x11_display_library = if client_ext_str.contains("EGL_ANGLE_platform_angle") {
780 open_x_display()
781 } else {
782 None
783 };
784
785 #[cfg(not(Emscripten))]
786 let egl1_5 = egl.upcast::<khronos_egl::EGL1_5>();
787
788 #[cfg(Emscripten)]
789 let egl1_5: Option<&Arc<EglInstance>> = Some(&egl);
790
791 let (display, display_owner, wsi_kind) =
792 if let (Some(library), Some(egl)) = (wayland_library, egl1_5) {
793 log::info!("Using Wayland platform");
794 let display_attributes = [khronos_egl::ATTRIB_NONE];
795 let display = unsafe {
796 egl.get_platform_display(
797 EGL_PLATFORM_WAYLAND_KHR,
798 khronos_egl::DEFAULT_DISPLAY,
799 &display_attributes,
800 )
801 }
802 .unwrap();
803 (display, Some(Rc::new(library)), WindowKind::Wayland)
804 } else if let (Some(display_owner), Some(egl)) = (x11_display_library, egl1_5) {
805 log::info!("Using X11 platform");
806 let display_attributes = [khronos_egl::ATTRIB_NONE];
807 let display = unsafe {
808 egl.get_platform_display(
809 EGL_PLATFORM_X11_KHR,
810 display_owner.display.as_ptr(),
811 &display_attributes,
812 )
813 }
814 .unwrap();
815 (display, Some(Rc::new(display_owner)), WindowKind::X11)
816 } else if let (Some(display_owner), Some(egl)) = (angle_x11_display_library, egl1_5) {
817 log::info!("Using Angle platform with X11");
818 let display_attributes = [
819 EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE as khronos_egl::Attrib,
820 EGL_PLATFORM_X11_KHR as khronos_egl::Attrib,
821 EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as khronos_egl::Attrib,
822 usize::from(desc.flags.contains(wgt::InstanceFlags::VALIDATION)),
823 khronos_egl::ATTRIB_NONE,
824 ];
825 let display = unsafe {
826 egl.get_platform_display(
827 EGL_PLATFORM_ANGLE_ANGLE,
828 display_owner.display.as_ptr(),
829 &display_attributes,
830 )
831 }
832 .unwrap();
833 (display, Some(Rc::new(display_owner)), WindowKind::AngleX11)
834 } else if client_ext_str.contains("EGL_MESA_platform_surfaceless") {
835 log::warn!("No windowing system present. Using surfaceless platform");
836 #[allow(clippy::unnecessary_literal_unwrap)] let egl = egl1_5.expect("Failed to get EGL 1.5 for surfaceless");
838 let display = unsafe {
839 egl.get_platform_display(
840 EGL_PLATFORM_SURFACELESS_MESA,
841 khronos_egl::DEFAULT_DISPLAY,
842 &[khronos_egl::ATTRIB_NONE],
843 )
844 }
845 .unwrap();
846
847 (display, None, WindowKind::Unknown)
848 } else {
849 log::warn!("EGL_MESA_platform_surfaceless not available. Using default platform");
850 let display = unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) }.unwrap();
851 (display, None, WindowKind::Unknown)
852 };
853
854 if desc.flags.contains(wgt::InstanceFlags::VALIDATION)
855 && client_ext_str.contains("EGL_KHR_debug")
856 {
857 log::debug!("Enabling EGL debug output");
858 let function: EglDebugMessageControlFun = {
859 let addr = egl.get_proc_address("eglDebugMessageControlKHR").unwrap();
860 unsafe { std::mem::transmute(addr) }
861 };
862 let attributes = [
863 EGL_DEBUG_MSG_CRITICAL_KHR as khronos_egl::Attrib,
864 1,
865 EGL_DEBUG_MSG_ERROR_KHR as khronos_egl::Attrib,
866 1,
867 EGL_DEBUG_MSG_WARN_KHR as khronos_egl::Attrib,
868 1,
869 EGL_DEBUG_MSG_INFO_KHR as khronos_egl::Attrib,
870 1,
871 khronos_egl::ATTRIB_NONE,
872 ];
873 unsafe { (function)(Some(egl_debug_proc), attributes.as_ptr()) };
874 }
875
876 let inner = Inner::create(desc.flags, egl, display, desc.gles_minor_version)?;
877
878 Ok(Instance {
879 wsi: WindowSystemInterface {
880 display_owner,
881 kind: wsi_kind,
882 },
883 flags: desc.flags,
884 inner: Mutex::new(inner),
885 })
886 }
887
888 #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))]
889 unsafe fn create_surface(
890 &self,
891 display_handle: raw_window_handle::RawDisplayHandle,
892 window_handle: raw_window_handle::RawWindowHandle,
893 ) -> Result<Surface, crate::InstanceError> {
894 use raw_window_handle::RawWindowHandle as Rwh;
895
896 #[cfg_attr(any(target_os = "android", Emscripten), allow(unused_mut))]
897 let mut inner = self.inner.lock();
898
899 match (window_handle, display_handle) {
900 (Rwh::Xlib(_), _) => {}
901 (Rwh::Xcb(_), _) => {}
902 (Rwh::Win32(_), _) => {}
903 (Rwh::AppKit(_), _) => {}
904 #[cfg(target_os = "android")]
905 (Rwh::AndroidNdk(handle), _) => {
906 let format = inner
907 .egl
908 .instance
909 .get_config_attrib(
910 inner.egl.display,
911 inner.config,
912 khronos_egl::NATIVE_VISUAL_ID,
913 )
914 .unwrap();
915
916 let ret = unsafe {
917 ndk_sys::ANativeWindow_setBuffersGeometry(
918 handle.a_native_window.as_ptr() as *mut ndk_sys::ANativeWindow,
919 0,
920 0,
921 format,
922 )
923 };
924
925 if ret != 0 {
926 return Err(crate::InstanceError::new(format!(
927 "error {ret} returned from ANativeWindow_setBuffersGeometry",
928 )));
929 }
930 }
931 #[cfg(not(Emscripten))]
932 (Rwh::Wayland(_), raw_window_handle::RawDisplayHandle::Wayland(display_handle)) => {
933 if inner
934 .wl_display
935 .map(|ptr| ptr != display_handle.display.as_ptr())
936 .unwrap_or(true)
937 {
938 log::warn!("Re-initializing Gles context due to Wayland window");
945
946 use std::ops::DerefMut;
947 let display_attributes = [khronos_egl::ATTRIB_NONE];
948
949 let display = unsafe {
950 inner
951 .egl
952 .instance
953 .upcast::<khronos_egl::EGL1_5>()
954 .unwrap()
955 .get_platform_display(
956 EGL_PLATFORM_WAYLAND_KHR,
957 display_handle.display.as_ptr(),
958 &display_attributes,
959 )
960 }
961 .unwrap();
962
963 let new_inner = Inner::create(
964 self.flags,
965 Arc::clone(&inner.egl.instance),
966 display,
967 inner.force_gles_minor_version,
968 )?;
969
970 let old_inner = std::mem::replace(inner.deref_mut(), new_inner);
971 inner.wl_display = Some(display_handle.display.as_ptr());
972
973 drop(old_inner);
974 }
975 }
976 #[cfg(Emscripten)]
977 (Rwh::Web(_), _) => {}
978 other => {
979 return Err(crate::InstanceError::new(format!(
980 "unsupported window: {other:?}"
981 )));
982 }
983 };
984
985 inner.egl.unmake_current();
986
987 Ok(Surface {
988 egl: inner.egl.clone(),
989 wsi: self.wsi.clone(),
990 config: inner.config,
991 presentable: inner.supports_native_window,
992 raw_window_handle: window_handle,
993 swapchain: RwLock::new(None),
994 srgb_kind: inner.srgb_kind,
995 })
996 }
997
998 unsafe fn destroy_surface(&self, _surface: Surface) {}
999
1000 unsafe fn enumerate_adapters(&self) -> Vec<crate::ExposedAdapter<super::Api>> {
1001 let inner = self.inner.lock();
1002 inner.egl.make_current();
1003
1004 let mut gl = unsafe {
1005 glow::Context::from_loader_function(|name| {
1006 inner
1007 .egl
1008 .instance
1009 .get_proc_address(name)
1010 .map_or(ptr::null(), |p| p as *const _)
1011 })
1012 };
1013
1014 if !matches!(inner.srgb_kind, SrgbFrameBufferKind::None) {
1017 unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
1018 }
1019
1020 if self.flags.contains(wgt::InstanceFlags::DEBUG) && gl.supports_debug() {
1021 log::debug!("Max label length: {}", unsafe {
1022 gl.get_parameter_i32(glow::MAX_LABEL_LENGTH)
1023 });
1024 }
1025
1026 if self.flags.contains(wgt::InstanceFlags::VALIDATION) && gl.supports_debug() {
1027 log::debug!("Enabling GLES debug output");
1028 unsafe { gl.enable(glow::DEBUG_OUTPUT) };
1029 unsafe { gl.debug_message_callback(super::gl_debug_message_callback) };
1030 }
1031
1032 inner.egl.unmake_current();
1033
1034 unsafe {
1035 super::Adapter::expose(AdapterContext {
1036 glow: Mutex::new(gl),
1037 egl: Some(inner.egl.clone()),
1038 })
1039 }
1040 .into_iter()
1041 .collect()
1042 }
1043}
1044
1045impl super::Adapter {
1046 pub unsafe fn new_external(
1054 fun: impl FnMut(&str) -> *const ffi::c_void,
1055 ) -> Option<crate::ExposedAdapter<super::Api>> {
1056 let context = unsafe { glow::Context::from_loader_function(fun) };
1057 unsafe {
1058 Self::expose(AdapterContext {
1059 glow: Mutex::new(context),
1060 egl: None,
1061 })
1062 }
1063 }
1064
1065 pub fn adapter_context(&self) -> &AdapterContext {
1066 &self.shared.context
1067 }
1068}
1069
1070impl super::Device {
1071 pub fn context(&self) -> &AdapterContext {
1073 &self.shared.context
1074 }
1075}
1076
1077#[derive(Debug)]
1078pub struct Swapchain {
1079 surface: khronos_egl::Surface,
1080 wl_window: Option<*mut raw::c_void>,
1081 framebuffer: glow::Framebuffer,
1082 renderbuffer: glow::Renderbuffer,
1083 extent: wgt::Extent3d,
1085 format: wgt::TextureFormat,
1086 format_desc: super::TextureFormatDesc,
1087 #[allow(unused)]
1088 sample_type: wgt::TextureSampleType,
1089}
1090
1091#[derive(Debug)]
1092pub struct Surface {
1093 egl: EglContext,
1094 wsi: WindowSystemInterface,
1095 config: khronos_egl::Config,
1096 pub(super) presentable: bool,
1097 raw_window_handle: raw_window_handle::RawWindowHandle,
1098 swapchain: RwLock<Option<Swapchain>>,
1099 srgb_kind: SrgbFrameBufferKind,
1100}
1101
1102unsafe impl Send for Surface {}
1103unsafe impl Sync for Surface {}
1104
1105impl Surface {
1106 pub(super) unsafe fn present(
1107 &self,
1108 _suf_texture: super::Texture,
1109 context: &AdapterContext,
1110 ) -> Result<(), crate::SurfaceError> {
1111 let gl = unsafe { context.get_without_egl_lock() };
1112 let swapchain = self.swapchain.read();
1113 let sc = swapchain.as_ref().unwrap();
1114
1115 self.egl
1116 .instance
1117 .make_current(
1118 self.egl.display,
1119 Some(sc.surface),
1120 Some(sc.surface),
1121 Some(self.egl.raw),
1122 )
1123 .map_err(|e| {
1124 log::error!("make_current(surface) failed: {}", e);
1125 crate::SurfaceError::Lost
1126 })?;
1127
1128 unsafe { gl.disable(glow::SCISSOR_TEST) };
1129 unsafe { gl.color_mask(true, true, true, true) };
1130
1131 unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) };
1132 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) };
1133
1134 if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) {
1135 unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) };
1138 }
1139
1140 unsafe {
1144 gl.blit_framebuffer(
1145 0,
1146 sc.extent.height as i32,
1147 sc.extent.width as i32,
1148 0,
1149 0,
1150 0,
1151 sc.extent.width as i32,
1152 sc.extent.height as i32,
1153 glow::COLOR_BUFFER_BIT,
1154 glow::NEAREST,
1155 )
1156 };
1157
1158 if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) {
1159 unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
1160 }
1161
1162 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1163
1164 self.egl
1165 .instance
1166 .swap_buffers(self.egl.display, sc.surface)
1167 .map_err(|e| {
1168 log::error!("swap_buffers failed: {}", e);
1169 crate::SurfaceError::Lost
1170 })?;
1172 self.egl
1173 .instance
1174 .make_current(self.egl.display, None, None, None)
1175 .map_err(|e| {
1176 log::error!("make_current(null) failed: {}", e);
1177 crate::SurfaceError::Lost
1178 })?;
1179
1180 Ok(())
1181 }
1182
1183 unsafe fn unconfigure_impl(
1184 &self,
1185 device: &super::Device,
1186 ) -> Option<(khronos_egl::Surface, Option<*mut raw::c_void>)> {
1187 let gl = &device.shared.context.lock();
1188 match self.swapchain.write().take() {
1189 Some(sc) => {
1190 unsafe { gl.delete_renderbuffer(sc.renderbuffer) };
1191 unsafe { gl.delete_framebuffer(sc.framebuffer) };
1192 Some((sc.surface, sc.wl_window))
1193 }
1194 None => None,
1195 }
1196 }
1197
1198 pub fn supports_srgb(&self) -> bool {
1199 match self.srgb_kind {
1200 SrgbFrameBufferKind::None => false,
1201 _ => true,
1202 }
1203 }
1204}
1205
1206impl crate::Surface for Surface {
1207 type A = super::Api;
1208
1209 unsafe fn configure(
1210 &self,
1211 device: &super::Device,
1212 config: &crate::SurfaceConfiguration,
1213 ) -> Result<(), crate::SurfaceError> {
1214 use raw_window_handle::RawWindowHandle as Rwh;
1215
1216 let (surface, wl_window) = match unsafe { self.unconfigure_impl(device) } {
1217 Some(pair) => pair,
1218 None => {
1219 let mut wl_window = None;
1220 let (mut temp_xlib_handle, mut temp_xcb_handle);
1221 #[allow(trivial_casts)]
1222 let native_window_ptr = match (self.wsi.kind, self.raw_window_handle) {
1223 (WindowKind::Unknown | WindowKind::X11, Rwh::Xlib(handle)) => {
1224 temp_xlib_handle = handle.window;
1225 &mut temp_xlib_handle as *mut _ as *mut std::ffi::c_void
1226 }
1227 (WindowKind::AngleX11, Rwh::Xlib(handle)) => {
1228 handle.window as *mut std::ffi::c_void
1229 }
1230 (WindowKind::Unknown | WindowKind::X11, Rwh::Xcb(handle)) => {
1231 temp_xcb_handle = handle.window;
1232 &mut temp_xcb_handle as *mut _ as *mut std::ffi::c_void
1233 }
1234 (WindowKind::AngleX11, Rwh::Xcb(handle)) => {
1235 handle.window.get() as *mut std::ffi::c_void
1236 }
1237 (WindowKind::Unknown, Rwh::AndroidNdk(handle)) => {
1238 handle.a_native_window.as_ptr()
1239 }
1240 (WindowKind::Wayland, Rwh::Wayland(handle)) => {
1241 let library = &self.wsi.display_owner.as_ref().unwrap().library;
1242 let wl_egl_window_create: libloading::Symbol<WlEglWindowCreateFun> =
1243 unsafe { library.get(b"wl_egl_window_create") }.unwrap();
1244 let window =
1245 unsafe { wl_egl_window_create(handle.surface.as_ptr(), 640, 480) }
1246 as *mut _;
1247 wl_window = Some(window);
1248 window
1249 }
1250 #[cfg(Emscripten)]
1251 (WindowKind::Unknown, Rwh::Web(handle)) => handle.id as *mut std::ffi::c_void,
1252 (WindowKind::Unknown, Rwh::Win32(handle)) => {
1253 handle.hwnd.get() as *mut std::ffi::c_void
1254 }
1255 (WindowKind::Unknown, Rwh::AppKit(handle)) => {
1256 #[cfg(not(target_os = "macos"))]
1257 let window_ptr = handle.ns_view.as_ptr();
1258 #[cfg(target_os = "macos")]
1259 let window_ptr = {
1260 use objc::{msg_send, runtime::Object, sel, sel_impl};
1261 let layer: *mut Object =
1263 msg_send![handle.ns_view.as_ptr() as *mut Object, layer];
1264 layer as *mut ffi::c_void
1265 };
1266 window_ptr
1267 }
1268 _ => {
1269 log::warn!(
1270 "Initialized platform {:?} doesn't work with window {:?}",
1271 self.wsi.kind,
1272 self.raw_window_handle
1273 );
1274 return Err(crate::SurfaceError::Other("incompatible window kind"));
1275 }
1276 };
1277
1278 let mut attributes = vec![
1279 khronos_egl::RENDER_BUFFER,
1280 if cfg!(any(target_os = "android", target_os = "macos"))
1284 || cfg!(windows)
1285 || self.wsi.kind == WindowKind::AngleX11
1286 {
1287 khronos_egl::BACK_BUFFER
1288 } else {
1289 khronos_egl::SINGLE_BUFFER
1290 },
1291 ];
1292 if config.format.is_srgb() {
1293 match self.srgb_kind {
1294 SrgbFrameBufferKind::None => {}
1295 SrgbFrameBufferKind::Core => {
1296 attributes.push(khronos_egl::GL_COLORSPACE);
1297 attributes.push(khronos_egl::GL_COLORSPACE_SRGB);
1298 }
1299 SrgbFrameBufferKind::Khr => {
1300 attributes.push(EGL_GL_COLORSPACE_KHR as i32);
1301 attributes.push(EGL_GL_COLORSPACE_SRGB_KHR as i32);
1302 }
1303 }
1304 }
1305 attributes.push(khronos_egl::ATTRIB_NONE as i32);
1306
1307 #[cfg(not(Emscripten))]
1308 let egl1_5 = self.egl.instance.upcast::<khronos_egl::EGL1_5>();
1309
1310 #[cfg(Emscripten)]
1311 let egl1_5: Option<&Arc<EglInstance>> = Some(&self.egl.instance);
1312
1313 let raw_result = match egl1_5 {
1315 Some(egl) if self.wsi.kind != WindowKind::Unknown => {
1316 let attributes_usize = attributes
1317 .into_iter()
1318 .map(|v| v as usize)
1319 .collect::<Vec<_>>();
1320 unsafe {
1321 egl.create_platform_window_surface(
1322 self.egl.display,
1323 self.config,
1324 native_window_ptr,
1325 &attributes_usize,
1326 )
1327 }
1328 }
1329 _ => unsafe {
1330 self.egl.instance.create_window_surface(
1331 self.egl.display,
1332 self.config,
1333 native_window_ptr,
1334 Some(&attributes),
1335 )
1336 },
1337 };
1338
1339 match raw_result {
1340 Ok(raw) => (raw, wl_window),
1341 Err(e) => {
1342 log::warn!("Error in create_window_surface: {:?}", e);
1343 return Err(crate::SurfaceError::Lost);
1344 }
1345 }
1346 }
1347 };
1348
1349 if let Some(window) = wl_window {
1350 let library = &self.wsi.display_owner.as_ref().unwrap().library;
1351 let wl_egl_window_resize: libloading::Symbol<WlEglWindowResizeFun> =
1352 unsafe { library.get(b"wl_egl_window_resize") }.unwrap();
1353 unsafe {
1354 wl_egl_window_resize(
1355 window,
1356 config.extent.width as i32,
1357 config.extent.height as i32,
1358 0,
1359 0,
1360 )
1361 };
1362 }
1363
1364 let format_desc = device.shared.describe_texture_format(config.format);
1365 let gl = &device.shared.context.lock();
1366 let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| {
1367 log::error!("Internal swapchain renderbuffer creation failed: {error}");
1368 crate::DeviceError::OutOfMemory
1369 })?;
1370 unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) };
1371 unsafe {
1372 gl.renderbuffer_storage(
1373 glow::RENDERBUFFER,
1374 format_desc.internal,
1375 config.extent.width as _,
1376 config.extent.height as _,
1377 )
1378 };
1379 let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| {
1380 log::error!("Internal swapchain framebuffer creation failed: {error}");
1381 crate::DeviceError::OutOfMemory
1382 })?;
1383 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) };
1384 unsafe {
1385 gl.framebuffer_renderbuffer(
1386 glow::READ_FRAMEBUFFER,
1387 glow::COLOR_ATTACHMENT0,
1388 glow::RENDERBUFFER,
1389 Some(renderbuffer),
1390 )
1391 };
1392 unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) };
1393 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1394
1395 let mut swapchain = self.swapchain.write();
1396 *swapchain = Some(Swapchain {
1397 surface,
1398 wl_window,
1399 renderbuffer,
1400 framebuffer,
1401 extent: config.extent,
1402 format: config.format,
1403 format_desc,
1404 sample_type: wgt::TextureSampleType::Float { filterable: false },
1405 });
1406
1407 Ok(())
1408 }
1409
1410 unsafe fn unconfigure(&self, device: &super::Device) {
1411 if let Some((surface, wl_window)) = unsafe { self.unconfigure_impl(device) } {
1412 self.egl
1413 .instance
1414 .destroy_surface(self.egl.display, surface)
1415 .unwrap();
1416 if let Some(window) = wl_window {
1417 let library = &self
1418 .wsi
1419 .display_owner
1420 .as_ref()
1421 .expect("unsupported window")
1422 .library;
1423 let wl_egl_window_destroy: libloading::Symbol<WlEglWindowDestroyFun> =
1424 unsafe { library.get(b"wl_egl_window_destroy") }.unwrap();
1425 unsafe { wl_egl_window_destroy(window) };
1426 }
1427 }
1428 }
1429
1430 unsafe fn acquire_texture(
1431 &self,
1432 _timeout_ms: Option<Duration>, _fence: &super::Fence,
1434 ) -> Result<Option<crate::AcquiredSurfaceTexture<super::Api>>, crate::SurfaceError> {
1435 let swapchain = self.swapchain.read();
1436 let sc = swapchain.as_ref().unwrap();
1437 let texture = super::Texture {
1438 inner: super::TextureInner::Renderbuffer {
1439 raw: sc.renderbuffer,
1440 },
1441 drop_guard: None,
1442 array_layer_count: 1,
1443 mip_level_count: 1,
1444 format: sc.format,
1445 format_desc: sc.format_desc.clone(),
1446 copy_size: crate::CopyExtent {
1447 width: sc.extent.width,
1448 height: sc.extent.height,
1449 depth: 1,
1450 },
1451 };
1452 Ok(Some(crate::AcquiredSurfaceTexture {
1453 texture,
1454 suboptimal: false,
1455 }))
1456 }
1457 unsafe fn discard_texture(&self, _texture: super::Texture) {}
1458}