1use alloc::{string::String, sync::Arc, vec::Vec};
2use core::{ffi, mem::ManuallyDrop, ptr, time::Duration};
3use std::sync::LazyLock;
4
5use glow::HasContext;
6use hashbrown::HashMap;
7use parking_lot::{MappedMutexGuard, Mutex, MutexGuard, RwLock};
8
9const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 6;
11
12const EGL_CONTEXT_FLAGS_KHR: i32 = 0x30FC;
13const EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR: i32 = 0x0001;
14const EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT: i32 = 0x30BF;
15const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8;
16const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
17const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202;
18const EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE: u32 = 0x348F;
19const EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED: u32 = 0x3451;
20const EGL_PLATFORM_SURFACELESS_MESA: u32 = 0x31DD;
21const EGL_GL_COLORSPACE_KHR: u32 = 0x309D;
22const EGL_GL_COLORSPACE_SRGB_KHR: u32 = 0x3089;
23
24#[cfg(not(Emscripten))]
25type EglInstance = khronos_egl::DynamicInstance<khronos_egl::EGL1_4>;
26
27#[cfg(Emscripten)]
28type EglInstance = khronos_egl::Instance<khronos_egl::Static>;
29
30type EglLabel = *const ffi::c_void;
31
32#[allow(clippy::upper_case_acronyms)]
33type EGLDEBUGPROCKHR = Option<
34 unsafe extern "system" fn(
35 error: khronos_egl::Enum,
36 command: *const ffi::c_char,
37 message_type: u32,
38 thread_label: EglLabel,
39 object_label: EglLabel,
40 message: *const ffi::c_char,
41 ),
42>;
43
44const EGL_DEBUG_MSG_CRITICAL_KHR: u32 = 0x33B9;
45const EGL_DEBUG_MSG_ERROR_KHR: u32 = 0x33BA;
46const EGL_DEBUG_MSG_WARN_KHR: u32 = 0x33BB;
47const EGL_DEBUG_MSG_INFO_KHR: u32 = 0x33BC;
48
49type EglDebugMessageControlFun = unsafe extern "system" fn(
50 proc: EGLDEBUGPROCKHR,
51 attrib_list: *const khronos_egl::Attrib,
52) -> ffi::c_int;
53
54unsafe extern "system" fn egl_debug_proc(
55 error: khronos_egl::Enum,
56 command_raw: *const ffi::c_char,
57 message_type: u32,
58 _thread_label: EglLabel,
59 _object_label: EglLabel,
60 message_raw: *const ffi::c_char,
61) {
62 let log_severity = match message_type {
63 EGL_DEBUG_MSG_CRITICAL_KHR | EGL_DEBUG_MSG_ERROR_KHR => log::Level::Error,
64 EGL_DEBUG_MSG_WARN_KHR => log::Level::Warn,
65 EGL_DEBUG_MSG_INFO_KHR => log::Level::Debug,
69 _ => log::Level::Trace,
70 };
71 let command = unsafe { ffi::CStr::from_ptr(command_raw) }.to_string_lossy();
72 let message = if message_raw.is_null() {
73 "".into()
74 } else {
75 unsafe { ffi::CStr::from_ptr(message_raw) }.to_string_lossy()
76 };
77
78 log::log!(log_severity, "EGL '{command}' code 0x{error:x}: {message}",);
79}
80
81#[derive(Clone, Copy, Debug)]
82enum SrgbFrameBufferKind {
83 None,
85 Core,
87 Khr,
89}
90
91fn choose_config(
93 egl: &EglInstance,
94 display: khronos_egl::Display,
95 srgb_kind: SrgbFrameBufferKind,
96) -> Result<(khronos_egl::Config, bool), crate::InstanceError> {
97 let tiers = [
99 (
100 "off-screen",
101 &[
102 khronos_egl::SURFACE_TYPE,
103 khronos_egl::PBUFFER_BIT,
104 khronos_egl::RENDERABLE_TYPE,
105 khronos_egl::OPENGL_ES2_BIT,
106 ][..],
107 ),
108 (
109 "presentation",
110 &[khronos_egl::SURFACE_TYPE, khronos_egl::WINDOW_BIT][..],
111 ),
112 #[cfg(not(target_os = "android"))]
113 (
114 "native-render",
115 &[khronos_egl::NATIVE_RENDERABLE, khronos_egl::TRUE as _][..],
116 ),
117 ];
118
119 let mut attributes = Vec::with_capacity(9);
120 for tier_max in (0..tiers.len()).rev() {
121 let name = tiers[tier_max].0;
122 log::debug!("\tTrying {name}");
123
124 attributes.clear();
125 for &(_, tier_attr) in tiers[..=tier_max].iter() {
126 attributes.extend_from_slice(tier_attr);
127 }
128 match srgb_kind {
130 SrgbFrameBufferKind::None => {}
131 _ => {
132 attributes.push(khronos_egl::ALPHA_SIZE);
133 attributes.push(8);
134 }
135 }
136 attributes.push(khronos_egl::NONE);
137
138 match egl.choose_first_config(display, &attributes) {
139 Ok(Some(config)) => {
140 if tier_max == 1 {
141 log::info!("EGL says it can present to the window but not natively",);
144 }
145 let tier_threshold =
147 if cfg!(target_os = "android") || cfg!(windows) || cfg!(target_env = "ohos") {
148 1
149 } else {
150 2
151 };
152 return Ok((config, tier_max >= tier_threshold));
153 }
154 Ok(None) => {
155 log::debug!("No config found!");
156 }
157 Err(e) => {
158 log::error!("error in choose_first_config: {e:?}");
159 }
160 }
161 }
162
163 Err(crate::InstanceError::new(String::from(
165 "unable to find an acceptable EGL framebuffer configuration",
166 )))
167}
168
169#[derive(Clone, Debug)]
170struct EglContext {
171 instance: Arc<EglInstance>,
172 version: (i32, i32),
173 display: khronos_egl::Display,
174 raw: khronos_egl::Context,
175 pbuffer: Option<khronos_egl::Surface>,
176}
177
178impl EglContext {
179 fn make_current(&self) {
180 self.instance
181 .make_current(self.display, self.pbuffer, self.pbuffer, Some(self.raw))
182 .unwrap();
183 }
184
185 fn unmake_current(&self) {
186 self.instance
187 .make_current(self.display, None, None, None)
188 .unwrap();
189 }
190}
191
192pub struct AdapterContext {
195 glow: Mutex<ManuallyDrop<glow::Context>>,
196 egl: Option<EglContext>,
197}
198
199unsafe impl Sync for AdapterContext {}
200unsafe impl Send for AdapterContext {}
201
202impl AdapterContext {
203 pub fn is_owned(&self) -> bool {
204 self.egl.is_some()
205 }
206
207 pub fn egl_instance(&self) -> Option<&EglInstance> {
211 self.egl.as_ref().map(|egl| &*egl.instance)
212 }
213
214 pub fn raw_display(&self) -> Option<&khronos_egl::Display> {
218 self.egl.as_ref().map(|egl| &egl.display)
219 }
220
221 pub fn egl_version(&self) -> Option<(i32, i32)> {
225 self.egl.as_ref().map(|egl| egl.version)
226 }
227
228 pub fn raw_context(&self) -> *mut ffi::c_void {
229 match self.egl {
230 Some(ref egl) => egl.raw.as_ptr(),
231 None => ptr::null_mut(),
232 }
233 }
234}
235
236impl Drop for AdapterContext {
237 fn drop(&mut self) {
238 struct CurrentGuard<'a>(&'a EglContext);
239 impl Drop for CurrentGuard<'_> {
240 fn drop(&mut self) {
241 self.0.unmake_current();
242 }
243 }
244
245 let _guard = self.egl.as_ref().map(|egl| {
252 egl.make_current();
253 CurrentGuard(egl)
254 });
255 let glow = self.glow.get_mut();
256 unsafe { ManuallyDrop::drop(glow) };
258 }
259}
260
261struct EglContextLock<'a> {
262 instance: &'a Arc<EglInstance>,
263 display: khronos_egl::Display,
264}
265
266pub struct AdapterContextLock<'a> {
268 glow: MutexGuard<'a, ManuallyDrop<glow::Context>>,
269 egl: Option<EglContextLock<'a>>,
270}
271
272impl<'a> core::ops::Deref for AdapterContextLock<'a> {
273 type Target = glow::Context;
274
275 fn deref(&self) -> &Self::Target {
276 &self.glow
277 }
278}
279
280impl<'a> Drop for AdapterContextLock<'a> {
281 fn drop(&mut self) {
282 if let Some(egl) = self.egl.take() {
283 if let Err(err) = egl.instance.make_current(egl.display, None, None, None) {
284 log::error!("Failed to make EGL context current: {err:?}");
285 }
286 }
287 }
288}
289
290impl AdapterContext {
291 pub unsafe fn get_without_egl_lock(&self) -> MappedMutexGuard<'_, glow::Context> {
303 let guard = self
304 .glow
305 .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
306 .expect("Could not lock adapter context. This is most-likely a deadlock.");
307 MutexGuard::map(guard, |glow| &mut **glow)
308 }
309
310 #[track_caller]
313 pub fn lock<'a>(&'a self) -> AdapterContextLock<'a> {
314 let glow = self
315 .glow
316 .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
319 .expect("Could not lock adapter context. This is most-likely a deadlock.");
320
321 let egl = self.egl.as_ref().map(|egl| {
322 egl.make_current();
323 EglContextLock {
324 instance: &egl.instance,
325 display: egl.display,
326 }
327 });
328
329 AdapterContextLock { glow, egl }
330 }
331}
332
333#[derive(Debug)]
334struct Inner {
335 egl: EglContext,
338 #[allow(unused)]
339 version: (i32, i32),
340 supports_native_window: bool,
341 config: khronos_egl::Config,
342 srgb_kind: SrgbFrameBufferKind,
344}
345
346static DISPLAYS_REFERENCE_COUNT: LazyLock<Mutex<HashMap<usize, usize>>> =
350 LazyLock::new(Default::default);
351
352fn initialize_display(
353 egl: &EglInstance,
354 display: khronos_egl::Display,
355) -> Result<(i32, i32), khronos_egl::Error> {
356 let mut guard = DISPLAYS_REFERENCE_COUNT.lock();
357 *guard.entry(display.as_ptr() as usize).or_default() += 1;
358
359 egl.initialize(display)
363}
364
365fn terminate_display(
366 egl: &EglInstance,
367 display: khronos_egl::Display,
368) -> Result<(), khronos_egl::Error> {
369 let key = &(display.as_ptr() as usize);
370 let mut guard = DISPLAYS_REFERENCE_COUNT.lock();
371 let count_ref = guard
372 .get_mut(key)
373 .expect("Attempted to decref a display before incref was called");
374
375 if *count_ref > 1 {
376 *count_ref -= 1;
377
378 Ok(())
379 } else {
380 guard.remove(key);
381
382 egl.terminate(display)
383 }
384}
385
386fn instance_err<E: core::error::Error + Send + Sync + 'static>(
387 message: impl Into<String>,
388) -> impl FnOnce(E) -> crate::InstanceError {
389 move |e| crate::InstanceError::with_source(message.into(), e)
390}
391
392impl Inner {
393 fn create(
394 flags: wgt::InstanceFlags,
395 egl: Arc<EglInstance>,
396 display: khronos_egl::Display,
397 force_gles_minor_version: wgt::Gles3MinorVersion,
398 ) -> Result<Self, crate::InstanceError> {
399 let version = initialize_display(&egl, display)
400 .map_err(instance_err("failed to initialize EGL display connection"))?;
401 let vendor = egl
402 .query_string(Some(display), khronos_egl::VENDOR)
403 .map_err(instance_err("failed to query EGL vendor"))?;
404 let display_extensions = egl
405 .query_string(Some(display), khronos_egl::EXTENSIONS)
406 .map_err(instance_err("failed to query EGL display extensions"))?
407 .to_string_lossy();
408 log::debug!("Display vendor {vendor:?}, version {version:?}",);
409 log::debug!(
410 "Display extensions: {:#?}",
411 display_extensions.split_whitespace().collect::<Vec<_>>()
412 );
413
414 let srgb_kind = if version >= (1, 5) {
415 log::debug!("\tEGL surface: +srgb");
416 SrgbFrameBufferKind::Core
417 } else if display_extensions.contains("EGL_KHR_gl_colorspace") {
418 log::debug!("\tEGL surface: +srgb khr");
419 SrgbFrameBufferKind::Khr
420 } else {
421 log::debug!("\tEGL surface: -srgb");
422 SrgbFrameBufferKind::None
423 };
424
425 if log::max_level() >= log::LevelFilter::Trace {
426 log::trace!("Configurations:");
427 let config_count = egl
428 .get_config_count(display)
429 .map_err(instance_err("failed to get config count"))?;
430 let mut configurations = Vec::with_capacity(config_count);
431 egl.get_configs(display, &mut configurations)
432 .map_err(instance_err("failed to get configs"))?;
433 for &config in configurations.iter() {
434 log::trace!("\tCONFORMANT=0x{:X?}, RENDERABLE=0x{:X?}, NATIVE_RENDERABLE=0x{:X?}, SURFACE_TYPE=0x{:X?}, ALPHA_SIZE={:?}",
435 egl.get_config_attrib(display, config, khronos_egl::CONFORMANT),
436 egl.get_config_attrib(display, config, khronos_egl::RENDERABLE_TYPE),
437 egl.get_config_attrib(display, config, khronos_egl::NATIVE_RENDERABLE),
438 egl.get_config_attrib(display, config, khronos_egl::SURFACE_TYPE),
439 egl.get_config_attrib(display, config, khronos_egl::ALPHA_SIZE),
440 );
441 }
442 }
443
444 let (config, supports_native_window) = choose_config(&egl, display, srgb_kind)?;
445
446 let supports_opengl = if version >= (1, 4) {
447 let client_apis = egl
448 .query_string(Some(display), khronos_egl::CLIENT_APIS)
449 .map_err(instance_err("failed to query EGL client APIs string"))?
450 .to_string_lossy();
451 client_apis
452 .split(' ')
453 .any(|client_api| client_api == "OpenGL")
454 } else {
455 false
456 };
457 egl.bind_api(if supports_opengl {
458 khronos_egl::OPENGL_API
459 } else {
460 khronos_egl::OPENGL_ES_API
461 })
462 .map_err(instance_err("failed to bind API"))?;
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 enum Robustness {
511 Core,
512 Ext,
513 }
514
515 let mut robustness = if version >= (1, 5) {
516 Some(Robustness::Core)
517 } else if display_extensions.contains("EGL_EXT_create_context_robustness") {
518 Some(Robustness::Ext)
519 } else {
520 None
521 };
522
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 gl_context_attributes = gl_context_attributes.clone();
543 gl_context_attributes.extend(&robustness_attributes);
544
545 let mut gles_context_attributes = gles_context_attributes.clone();
546 gles_context_attributes.extend(&robustness_attributes);
547
548 let result = if supports_opengl {
549 egl.create_context(display, config, None, &gl_context_attributes)
550 .or_else(|_| {
551 egl.bind_api(khronos_egl::OPENGL_ES_API)?;
552 egl.create_context(display, config, None, &gles_context_attributes)
553 })
554 } else {
555 egl.create_context(display, config, None, &gles_context_attributes)
556 };
557
558 match (result, robustness) {
559 (Ok(_), robustness) => {
561 match robustness {
562 Some(Robustness::Core) => {
563 log::debug!("\tEGL context: +robust access");
564 }
565 Some(Robustness::Ext) => {
566 log::debug!("\tEGL context: +robust access EXT");
567 }
568 None => {
569 log::debug!("\tEGL context: -robust access");
570 }
571 }
572
573 break result;
574 }
575
576 (Err(khronos_egl::Error::BadAttribute), Some(r)) => {
579 robustness = if matches!(r, Robustness::Core)
582 && display_extensions.contains("EGL_EXT_create_context_robustness")
583 {
584 Some(Robustness::Ext)
585 } else {
586 None
587 };
588
589 continue;
590 }
591
592 _ => break result,
594 }
595 }
596 .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)) => todo!("xcb"),
818 x if client_ext_str.contains("EGL_MESA_platform_surfaceless") => {
819 log::debug!(
820 "No (or unknown) windowing system ({x:?}) present. Using surfaceless platform"
821 );
822 #[allow(clippy::unnecessary_literal_unwrap)]
823 let egl = egl1_5.expect("Failed to get EGL 1.5 for surfaceless");
826 let display = unsafe {
827 egl.get_platform_display(
828 EGL_PLATFORM_SURFACELESS_MESA,
829 khronos_egl::DEFAULT_DISPLAY,
830 &[khronos_egl::ATTRIB_NONE],
831 )
832 }
833 .map_err(instance_err("failed to get MESA surfaceless display"))?;
834 (display, WindowKind::Unknown)
835 }
836 x => {
837 log::debug!(
838 "No (or unknown) windowing system {x:?} and EGL_MESA_platform_surfaceless not available. Using default platform"
839 );
840 let display =
841 unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) }.ok_or_else(|| {
842 crate::InstanceError::new("Failed to get default display".into())
843 })?;
844 (display, WindowKind::Unknown)
845 }
846 };
847
848 if desc.flags.contains(wgt::InstanceFlags::VALIDATION)
849 && client_ext_str.contains("EGL_KHR_debug")
850 {
851 log::debug!("Enabling EGL debug output");
852 let function: EglDebugMessageControlFun = {
853 let addr = egl
854 .get_proc_address("eglDebugMessageControlKHR")
855 .ok_or_else(|| {
856 crate::InstanceError::new(
857 "failed to get `eglDebugMessageControlKHR` proc address".into(),
858 )
859 })?;
860 unsafe { core::mem::transmute(addr) }
861 };
862 let attributes = [
863 EGL_DEBUG_MSG_CRITICAL_KHR as khronos_egl::Attrib,
864 1,
865 EGL_DEBUG_MSG_ERROR_KHR as khronos_egl::Attrib,
866 1,
867 EGL_DEBUG_MSG_WARN_KHR as khronos_egl::Attrib,
868 1,
869 EGL_DEBUG_MSG_INFO_KHR as khronos_egl::Attrib,
870 1,
871 khronos_egl::ATTRIB_NONE,
872 ];
873 unsafe { (function)(Some(egl_debug_proc), attributes.as_ptr()) };
874 }
875
876 let inner = Inner::create(
877 desc.flags,
878 egl,
879 display,
880 desc.backend_options.gl.gles_minor_version,
881 )?;
882
883 Ok(Instance {
884 wsi: WindowSystemInterface { kind: wsi_kind },
885 flags: desc.flags,
886 options: desc.backend_options.gl.clone(),
887 inner: Mutex::new(inner),
888 })
889 }
890
891 #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))]
892 unsafe fn create_surface(
893 &self,
894 display_handle: raw_window_handle::RawDisplayHandle,
895 window_handle: raw_window_handle::RawWindowHandle,
896 ) -> Result<Surface, crate::InstanceError> {
897 use raw_window_handle::RawWindowHandle as Rwh;
898
899 let inner = self.inner.lock();
900
901 match (window_handle, display_handle) {
902 (Rwh::Xlib(_), _) => {}
903 (Rwh::Xcb(_), _) => {}
904 (Rwh::Win32(_), _) => {}
905 (Rwh::AppKit(_), _) => {}
906 (Rwh::OhosNdk(_), _) => {}
907 #[cfg(target_os = "android")]
908 (Rwh::AndroidNdk(handle), _) => {
909 let format = inner
910 .egl
911 .instance
912 .get_config_attrib(
913 inner.egl.display,
914 inner.config,
915 khronos_egl::NATIVE_VISUAL_ID,
916 )
917 .map_err(instance_err("failed to get config NATIVE_VISUAL_ID"))?;
918
919 let ret = unsafe {
920 ndk_sys::ANativeWindow_setBuffersGeometry(
921 handle
922 .a_native_window
923 .as_ptr()
924 .cast::<ndk_sys::ANativeWindow>(),
925 0,
926 0,
927 format,
928 )
929 };
930
931 if ret != 0 {
932 return Err(crate::InstanceError::new(format!(
933 "error {ret} returned from ANativeWindow_setBuffersGeometry",
934 )));
935 }
936 }
937 (Rwh::Wayland(_), _) => {}
938 #[cfg(Emscripten)]
939 (Rwh::Web(_), _) => {}
940 other => {
941 return Err(crate::InstanceError::new(format!(
942 "unsupported window: {other:?}"
943 )));
944 }
945 };
946
947 inner.egl.unmake_current();
948
949 Ok(Surface {
950 egl: inner.egl.clone(),
951 wsi: self.wsi.clone(),
952 config: inner.config,
953 presentable: inner.supports_native_window,
954 raw_window_handle: window_handle,
955 swapchain: RwLock::new(None),
956 srgb_kind: inner.srgb_kind,
957 })
958 }
959
960 unsafe fn enumerate_adapters(
961 &self,
962 _surface_hint: Option<&Surface>,
963 ) -> Vec<crate::ExposedAdapter<super::Api>> {
964 let inner = self.inner.lock();
965 inner.egl.make_current();
966
967 let mut gl = unsafe {
968 glow::Context::from_loader_function(|name| {
969 inner
970 .egl
971 .instance
972 .get_proc_address(name)
973 .map_or(ptr::null(), |p| p as *const _)
974 })
975 };
976
977 if !matches!(inner.srgb_kind, SrgbFrameBufferKind::None) {
980 unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
981 }
982
983 if self.flags.contains(wgt::InstanceFlags::DEBUG) && gl.supports_debug() {
984 log::debug!("Max label length: {}", unsafe {
985 gl.get_parameter_i32(glow::MAX_LABEL_LENGTH)
986 });
987 }
988
989 if self.flags.contains(wgt::InstanceFlags::VALIDATION) && gl.supports_debug() {
990 log::debug!("Enabling GLES debug output");
991 unsafe { gl.enable(glow::DEBUG_OUTPUT) };
992 unsafe { gl.debug_message_callback(super::gl_debug_message_callback) };
993 }
994
995 let gl = ManuallyDrop::new(gl);
999 inner.egl.unmake_current();
1000
1001 unsafe {
1002 super::Adapter::expose(
1003 AdapterContext {
1004 glow: Mutex::new(gl),
1005 egl: Some(inner.egl.clone()),
1007 },
1008 self.options.clone(),
1009 )
1010 }
1011 .into_iter()
1012 .collect()
1013 }
1014}
1015
1016impl super::Adapter {
1017 pub unsafe fn new_external(
1027 fun: impl FnMut(&str) -> *const ffi::c_void,
1028 options: wgt::GlBackendOptions,
1029 ) -> Option<crate::ExposedAdapter<super::Api>> {
1030 let context = unsafe { glow::Context::from_loader_function(fun) };
1031 unsafe {
1032 Self::expose(
1033 AdapterContext {
1034 glow: Mutex::new(ManuallyDrop::new(context)),
1035 egl: None,
1036 },
1037 options,
1038 )
1039 }
1040 }
1041
1042 pub fn adapter_context(&self) -> &AdapterContext {
1043 &self.shared.context
1044 }
1045}
1046
1047impl super::Device {
1048 pub fn context(&self) -> &AdapterContext {
1050 &self.shared.context
1051 }
1052}
1053
1054#[derive(Debug)]
1055pub struct Swapchain {
1056 surface: khronos_egl::Surface,
1057 wl_window: Option<*mut wayland_sys::egl::wl_egl_window>,
1058 framebuffer: glow::Framebuffer,
1059 renderbuffer: glow::Renderbuffer,
1060 extent: wgt::Extent3d,
1062 format: wgt::TextureFormat,
1063 format_desc: super::TextureFormatDesc,
1064 #[allow(unused)]
1065 sample_type: wgt::TextureSampleType,
1066}
1067
1068#[derive(Debug)]
1069pub struct Surface {
1070 egl: EglContext,
1071 wsi: WindowSystemInterface,
1072 config: khronos_egl::Config,
1073 pub(super) presentable: bool,
1074 raw_window_handle: raw_window_handle::RawWindowHandle,
1075 swapchain: RwLock<Option<Swapchain>>,
1076 srgb_kind: SrgbFrameBufferKind,
1077}
1078
1079unsafe impl Send for Surface {}
1080unsafe impl Sync for Surface {}
1081
1082impl Surface {
1083 pub(super) unsafe fn present(
1084 &self,
1085 _suf_texture: super::Texture,
1086 context: &AdapterContext,
1087 ) -> Result<(), crate::SurfaceError> {
1088 let gl = unsafe { context.get_without_egl_lock() };
1089 let swapchain = self.swapchain.read();
1090 let sc = swapchain.as_ref().ok_or(crate::SurfaceError::Other(
1091 "Surface has no swap-chain configured",
1092 ))?;
1093
1094 self.egl
1095 .instance
1096 .make_current(
1097 self.egl.display,
1098 Some(sc.surface),
1099 Some(sc.surface),
1100 Some(self.egl.raw),
1101 )
1102 .map_err(|e| {
1103 log::error!("make_current(surface) failed: {e}");
1104 crate::SurfaceError::Lost
1105 })?;
1106
1107 unsafe { gl.disable(glow::SCISSOR_TEST) };
1108 unsafe { gl.color_mask(true, true, true, true) };
1109
1110 unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) };
1111 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) };
1112
1113 if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) {
1114 unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) };
1117 }
1118
1119 unsafe {
1123 gl.blit_framebuffer(
1124 0,
1125 sc.extent.height as i32,
1126 sc.extent.width as i32,
1127 0,
1128 0,
1129 0,
1130 sc.extent.width as i32,
1131 sc.extent.height as i32,
1132 glow::COLOR_BUFFER_BIT,
1133 glow::NEAREST,
1134 )
1135 };
1136
1137 if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) {
1138 unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
1139 }
1140
1141 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1142
1143 self.egl
1144 .instance
1145 .swap_buffers(self.egl.display, sc.surface)
1146 .map_err(|e| {
1147 log::error!("swap_buffers failed: {e}");
1148 crate::SurfaceError::Lost
1149 })?;
1151 self.egl
1152 .instance
1153 .make_current(self.egl.display, None, None, None)
1154 .map_err(|e| {
1155 log::error!("make_current(null) failed: {e}");
1156 crate::SurfaceError::Lost
1157 })?;
1158
1159 Ok(())
1160 }
1161
1162 unsafe fn unconfigure_impl(
1163 &self,
1164 device: &super::Device,
1165 ) -> Option<(
1166 khronos_egl::Surface,
1167 Option<*mut wayland_sys::egl::wl_egl_window>,
1168 )> {
1169 let gl = &device.shared.context.lock();
1170 match self.swapchain.write().take() {
1171 Some(sc) => {
1172 unsafe { gl.delete_renderbuffer(sc.renderbuffer) };
1173 unsafe { gl.delete_framebuffer(sc.framebuffer) };
1174 Some((sc.surface, sc.wl_window))
1175 }
1176 None => None,
1177 }
1178 }
1179
1180 pub fn supports_srgb(&self) -> bool {
1181 match self.srgb_kind {
1182 SrgbFrameBufferKind::None => false,
1183 _ => true,
1184 }
1185 }
1186}
1187
1188impl crate::Surface for Surface {
1189 type A = super::Api;
1190
1191 unsafe fn configure(
1192 &self,
1193 device: &super::Device,
1194 config: &crate::SurfaceConfiguration,
1195 ) -> Result<(), crate::SurfaceError> {
1196 use raw_window_handle::RawWindowHandle as Rwh;
1197
1198 let (surface, wl_window) = match unsafe { self.unconfigure_impl(device) } {
1199 Some((sc, wl_window)) => {
1200 if let Some(window) = wl_window {
1201 wayland_sys::ffi_dispatch!(
1202 wayland_sys::egl::wayland_egl_handle(),
1203 wl_egl_window_resize,
1204 window,
1205 config.extent.width as i32,
1206 config.extent.height as i32,
1207 0,
1208 0,
1209 );
1210 }
1211
1212 (sc, wl_window)
1213 }
1214 None => {
1215 let mut wl_window = None;
1216 let (mut temp_xlib_handle, mut temp_xcb_handle);
1217 let native_window_ptr = match (self.wsi.kind, self.raw_window_handle) {
1218 (WindowKind::Unknown | WindowKind::X11, Rwh::Xlib(handle)) => {
1219 temp_xlib_handle = handle.window;
1220 ptr::from_mut(&mut temp_xlib_handle).cast::<ffi::c_void>()
1221 }
1222 (WindowKind::AngleX11, Rwh::Xlib(handle)) => handle.window as *mut ffi::c_void,
1223 (WindowKind::Unknown | WindowKind::X11, Rwh::Xcb(handle)) => {
1224 temp_xcb_handle = handle.window;
1225 ptr::from_mut(&mut temp_xcb_handle).cast::<ffi::c_void>()
1226 }
1227 (WindowKind::AngleX11, Rwh::Xcb(handle)) => {
1228 handle.window.get() as *mut ffi::c_void
1229 }
1230 (WindowKind::Unknown, Rwh::AndroidNdk(handle)) => {
1231 handle.a_native_window.as_ptr()
1232 }
1233 (WindowKind::Unknown, Rwh::OhosNdk(handle)) => handle.native_window.as_ptr(),
1234 #[cfg(unix)]
1235 (WindowKind::Wayland, Rwh::Wayland(handle)) => {
1236 let window = wayland_sys::ffi_dispatch!(
1237 wayland_sys::egl::wayland_egl_handle(),
1238 wl_egl_window_create,
1239 handle.surface.as_ptr().cast(),
1240 config.extent.width as i32,
1241 config.extent.height as i32,
1242 );
1243 wl_window = Some(window);
1244 window.cast()
1245 }
1246 #[cfg(Emscripten)]
1247 (WindowKind::Unknown, Rwh::Web(handle)) => handle.id as *mut ffi::c_void,
1248 (WindowKind::Unknown, Rwh::Win32(handle)) => {
1249 handle.hwnd.get() as *mut ffi::c_void
1250 }
1251 (WindowKind::Unknown, Rwh::AppKit(handle)) => {
1252 #[cfg(not(target_os = "macos"))]
1253 let window_ptr = handle.ns_view.as_ptr();
1254 #[cfg(target_os = "macos")]
1255 let window_ptr = {
1256 use objc::{msg_send, runtime::Object, sel, sel_impl};
1257 let layer: *mut Object =
1259 msg_send![handle.ns_view.as_ptr().cast::<Object>(), layer];
1260 layer.cast::<ffi::c_void>()
1261 };
1262 window_ptr
1263 }
1264 _ => {
1265 log::warn!(
1266 "Initialized platform {:?} doesn't work with window {:?}",
1267 self.wsi.kind,
1268 self.raw_window_handle
1269 );
1270 return Err(crate::SurfaceError::Other("incompatible window kind"));
1271 }
1272 };
1273
1274 let mut attributes = vec![
1275 khronos_egl::RENDER_BUFFER,
1276 if cfg!(any(
1280 target_os = "android",
1281 target_os = "macos",
1282 target_env = "ohos"
1283 )) || cfg!(windows)
1284 || self.wsi.kind == WindowKind::AngleX11
1285 {
1286 khronos_egl::BACK_BUFFER
1287 } else {
1288 khronos_egl::SINGLE_BUFFER
1289 },
1290 ];
1291 if config.format.is_srgb() {
1292 match self.srgb_kind {
1293 SrgbFrameBufferKind::None => {}
1294 SrgbFrameBufferKind::Core => {
1295 attributes.push(khronos_egl::GL_COLORSPACE);
1296 attributes.push(khronos_egl::GL_COLORSPACE_SRGB);
1297 }
1298 SrgbFrameBufferKind::Khr => {
1299 attributes.push(EGL_GL_COLORSPACE_KHR as i32);
1300 attributes.push(EGL_GL_COLORSPACE_SRGB_KHR as i32);
1301 }
1302 }
1303 }
1304 attributes.push(khronos_egl::ATTRIB_NONE as i32);
1305
1306 #[cfg(not(Emscripten))]
1307 let egl1_5 = self.egl.instance.upcast::<khronos_egl::EGL1_5>();
1308
1309 #[cfg(Emscripten)]
1310 let egl1_5: Option<&Arc<EglInstance>> = Some(&self.egl.instance);
1311
1312 let raw_result = match egl1_5 {
1314 Some(egl) if self.wsi.kind != WindowKind::Unknown => {
1315 let attributes_usize = attributes
1316 .into_iter()
1317 .map(|v| v as usize)
1318 .collect::<Vec<_>>();
1319 unsafe {
1320 egl.create_platform_window_surface(
1321 self.egl.display,
1322 self.config,
1323 native_window_ptr,
1324 &attributes_usize,
1325 )
1326 }
1327 }
1328 _ => unsafe {
1329 self.egl.instance.create_window_surface(
1330 self.egl.display,
1331 self.config,
1332 native_window_ptr,
1333 Some(&attributes),
1334 )
1335 },
1336 };
1337
1338 match raw_result {
1339 Ok(raw) => (raw, wl_window),
1340 Err(e) => {
1341 log::warn!("Error in create_window_surface: {e:?}");
1342 return Err(crate::SurfaceError::Lost);
1343 }
1344 }
1345 }
1346 };
1347
1348 let format_desc = device.shared.describe_texture_format(config.format);
1349 let gl = &device.shared.context.lock();
1350 let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| {
1351 log::error!("Internal swapchain renderbuffer creation failed: {error}");
1352 crate::DeviceError::OutOfMemory
1353 })?;
1354 unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) };
1355 unsafe {
1356 gl.renderbuffer_storage(
1357 glow::RENDERBUFFER,
1358 format_desc.internal,
1359 config.extent.width as _,
1360 config.extent.height as _,
1361 )
1362 };
1363 let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| {
1364 log::error!("Internal swapchain framebuffer creation failed: {error}");
1365 crate::DeviceError::OutOfMemory
1366 })?;
1367 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) };
1368 unsafe {
1369 gl.framebuffer_renderbuffer(
1370 glow::READ_FRAMEBUFFER,
1371 glow::COLOR_ATTACHMENT0,
1372 glow::RENDERBUFFER,
1373 Some(renderbuffer),
1374 )
1375 };
1376 unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) };
1377 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1378
1379 let mut swapchain = self.swapchain.write();
1380 *swapchain = Some(Swapchain {
1381 surface,
1382 wl_window,
1383 renderbuffer,
1384 framebuffer,
1385 extent: config.extent,
1386 format: config.format,
1387 format_desc,
1388 sample_type: wgt::TextureSampleType::Float { filterable: false },
1389 });
1390
1391 Ok(())
1392 }
1393
1394 unsafe fn unconfigure(&self, device: &super::Device) {
1395 if let Some((surface, wl_window)) = unsafe { self.unconfigure_impl(device) } {
1396 self.egl
1397 .instance
1398 .destroy_surface(self.egl.display, surface)
1399 .unwrap();
1400 if let Some(window) = wl_window {
1401 wayland_sys::ffi_dispatch!(
1402 wayland_sys::egl::wayland_egl_handle(),
1403 wl_egl_window_destroy,
1404 window,
1405 );
1406 }
1407 }
1408 }
1409
1410 unsafe fn acquire_texture(
1411 &self,
1412 _timeout_ms: Option<Duration>, _fence: &super::Fence,
1414 ) -> Result<Option<crate::AcquiredSurfaceTexture<super::Api>>, crate::SurfaceError> {
1415 let swapchain = self.swapchain.read();
1416 let sc = swapchain.as_ref().ok_or(crate::SurfaceError::Other(
1417 "Surface has no swap-chain configured",
1418 ))?;
1419 let texture = super::Texture {
1420 inner: super::TextureInner::Renderbuffer {
1421 raw: sc.renderbuffer,
1422 },
1423 drop_guard: None,
1424 array_layer_count: 1,
1425 mip_level_count: 1,
1426 format: sc.format,
1427 format_desc: sc.format_desc.clone(),
1428 copy_size: crate::CopyExtent {
1429 width: sc.extent.width,
1430 height: sc.extent.height,
1431 depth: 1,
1432 },
1433 };
1434 Ok(Some(crate::AcquiredSurfaceTexture {
1435 texture,
1436 suboptimal: false,
1437 }))
1438 }
1439 unsafe fn discard_texture(&self, _texture: super::Texture) {}
1440}