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