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