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