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