Skip to main content

wgpu_hal/gles/
egl.rs

1use alloc::{string::String, sync::Arc, vec::Vec};
2use core::{ffi, mem::ManuallyDrop, ptr, time::Duration};
3
4use glow::HasContext;
5use hashbrown::HashMap;
6use wgpu_sync::{Lazy, MappedMutexGuard, Mutex, MutexGuard, RwLock};
7
8/// The amount of time to wait while trying to obtain a lock to the adapter context
9const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 6;
10
11const EGL_CONTEXT_FLAGS_KHR: i32 = 0x30FC;
12const EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR: i32 = 0x0001;
13const EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT: i32 = 0x30BF;
14const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8;
15const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
16const EGL_PLATFORM_XCB_EXT: u32 = 0x31DC;
17const EGL_PLATFORM_XCB_SCREEN_EXT: u32 = 0x31DE;
18const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202;
19const EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE: u32 = 0x348F;
20const EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED: u32 = 0x3451;
21const EGL_PLATFORM_SURFACELESS_MESA: u32 = 0x31DD;
22const EGL_GL_COLORSPACE_KHR: u32 = 0x309D;
23const EGL_GL_COLORSPACE_SRGB_KHR: u32 = 0x3089;
24
25#[cfg(not(Emscripten))]
26type EglInstance = khronos_egl::DynamicInstance<khronos_egl::EGL1_4>;
27
28#[cfg(Emscripten)]
29type EglInstance = khronos_egl::Instance<khronos_egl::Static>;
30
31type EglLabel = *const ffi::c_void;
32
33#[allow(clippy::upper_case_acronyms)]
34type EGLDEBUGPROCKHR = Option<
35    unsafe extern "system" fn(
36        error: khronos_egl::Enum,
37        command: *const ffi::c_char,
38        message_type: u32,
39        thread_label: EglLabel,
40        object_label: EglLabel,
41        message: *const ffi::c_char,
42    ),
43>;
44
45const EGL_DEBUG_MSG_CRITICAL_KHR: u32 = 0x33B9;
46const EGL_DEBUG_MSG_ERROR_KHR: u32 = 0x33BA;
47const EGL_DEBUG_MSG_WARN_KHR: u32 = 0x33BB;
48const EGL_DEBUG_MSG_INFO_KHR: u32 = 0x33BC;
49
50type EglDebugMessageControlFun = unsafe extern "system" fn(
51    proc: EGLDEBUGPROCKHR,
52    attrib_list: *const khronos_egl::Attrib,
53) -> ffi::c_int;
54
55unsafe extern "system" fn egl_debug_proc(
56    error: khronos_egl::Enum,
57    command_raw: *const ffi::c_char,
58    message_type: u32,
59    _thread_label: EglLabel,
60    _object_label: EglLabel,
61    message_raw: *const ffi::c_char,
62) {
63    let log_severity = match message_type {
64        EGL_DEBUG_MSG_CRITICAL_KHR | EGL_DEBUG_MSG_ERROR_KHR => log::Level::Error,
65        EGL_DEBUG_MSG_WARN_KHR => log::Level::Warn,
66        // We intentionally suppress info messages down to debug
67        // so that users are not inundated with info messages from
68        // the runtime.
69        EGL_DEBUG_MSG_INFO_KHR => log::Level::Debug,
70        _ => log::Level::Trace,
71    };
72    let command = unsafe { ffi::CStr::from_ptr(command_raw) }.to_string_lossy();
73    let message = if message_raw.is_null() {
74        "".into()
75    } else {
76        unsafe { ffi::CStr::from_ptr(message_raw) }.to_string_lossy()
77    };
78
79    log::log!(log_severity, "EGL '{command}' code 0x{error:x}: {message}",);
80}
81
82#[derive(Clone, Copy, Debug)]
83enum SrgbFrameBufferKind {
84    /// No support for SRGB surface
85    None,
86    /// Using EGL 1.5's support for colorspaces
87    Core,
88    /// Using EGL_KHR_gl_colorspace
89    Khr,
90}
91
92/// Choose GLES framebuffer configuration.
93fn choose_config(
94    egl: &EglInstance,
95    display: khronos_egl::Display,
96    srgb_kind: SrgbFrameBufferKind,
97) -> Result<(khronos_egl::Config, bool), crate::InstanceError> {
98    //TODO: EGL_SLOW_CONFIG
99    let tiers = [
100        (
101            "off-screen",
102            &[
103                khronos_egl::SURFACE_TYPE,
104                khronos_egl::PBUFFER_BIT,
105                khronos_egl::RENDERABLE_TYPE,
106                khronos_egl::OPENGL_ES2_BIT,
107            ][..],
108        ),
109        (
110            "presentation",
111            &[khronos_egl::SURFACE_TYPE, khronos_egl::WINDOW_BIT][..],
112        ),
113        #[cfg(not(target_os = "android"))]
114        (
115            "native-render",
116            &[khronos_egl::NATIVE_RENDERABLE, khronos_egl::TRUE as _][..],
117        ),
118    ];
119
120    let mut attributes = Vec::with_capacity(9);
121    for tier_max in (0..tiers.len()).rev() {
122        let name = tiers[tier_max].0;
123        log::debug!("\tTrying {name}");
124
125        attributes.clear();
126        let mut surface_type = 0;
127        for &(_, tier_attr) in tiers[..=tier_max].iter() {
128            for attribute in tier_attr.chunks_exact(2) {
129                if attribute[0] == khronos_egl::SURFACE_TYPE {
130                    surface_type |= attribute[1];
131                } else {
132                    attributes.extend_from_slice(attribute);
133                }
134            }
135        }
136        // Duplicate EGL attribute keys are undefined and make Mesa return no configs.
137        attributes.extend_from_slice(&[khronos_egl::SURFACE_TYPE, surface_type]);
138        // make sure the Alpha is enough to support sRGB
139        match srgb_kind {
140            SrgbFrameBufferKind::None => {}
141            _ => {
142                attributes.push(khronos_egl::ALPHA_SIZE);
143                attributes.push(8);
144            }
145        }
146        attributes.push(khronos_egl::NONE);
147
148        match egl.choose_first_config(display, &attributes) {
149            Ok(Some(config)) => {
150                if tier_max == 1 {
151                    //Note: this has been confirmed to malfunction on Intel+NV laptops,
152                    // but also on Angle.
153                    log::info!("EGL says it can present to the window but not natively",);
154                }
155                // Android emulator can't natively present either.
156                let tier_threshold =
157                    if cfg!(target_os = "android") || cfg!(windows) || cfg!(target_env = "ohos") {
158                        1
159                    } else {
160                        2
161                    };
162                return Ok((config, tier_max >= tier_threshold));
163            }
164            Ok(None) => {
165                log::debug!("No config found!");
166            }
167            Err(e) => {
168                log::error!("error in choose_first_config: {e:?}");
169            }
170        }
171    }
172
173    // TODO: include diagnostic details that are currently logged
174    Err(crate::InstanceError::new(String::from(
175        "unable to find an acceptable EGL framebuffer configuration",
176    )))
177}
178
179#[derive(Clone, Debug)]
180struct EglContext {
181    instance: Arc<EglInstance>,
182    version: (i32, i32),
183    display: khronos_egl::Display,
184    raw: khronos_egl::Context,
185    pbuffer: Option<khronos_egl::Surface>,
186}
187
188impl EglContext {
189    fn make_current(&self) {
190        self.instance
191            .make_current(self.display, self.pbuffer, self.pbuffer, Some(self.raw))
192            .unwrap();
193    }
194
195    fn unmake_current(&self) {
196        self.instance
197            .make_current(self.display, None, None, None)
198            .unwrap();
199    }
200}
201
202/// A wrapper around a [`glow::Context`] and the required EGL context that uses locking to guarantee
203/// exclusive access when shared with multiple threads.
204#[derive(Debug)]
205pub struct AdapterContext {
206    glow: Mutex<ManuallyDrop<glow::Context>>,
207    egl: Option<EglContext>,
208}
209
210unsafe impl Sync for AdapterContext {}
211unsafe impl Send for AdapterContext {}
212
213impl AdapterContext {
214    pub fn is_owned(&self) -> bool {
215        self.egl.is_some()
216    }
217
218    /// Returns the EGL instance.
219    ///
220    /// This provides access to EGL functions and the ability to load GL and EGL extension functions.
221    pub fn egl_instance(&self) -> Option<&EglInstance> {
222        self.egl.as_ref().map(|egl| &*egl.instance)
223    }
224
225    /// Returns the EGLDisplay corresponding to the adapter context.
226    ///
227    /// Returns [`None`] if the adapter was externally created.
228    pub fn raw_display(&self) -> Option<&khronos_egl::Display> {
229        self.egl.as_ref().map(|egl| &egl.display)
230    }
231
232    /// Returns the EGL version the adapter context was created with.
233    ///
234    /// Returns [`None`] if the adapter was externally created.
235    pub fn egl_version(&self) -> Option<(i32, i32)> {
236        self.egl.as_ref().map(|egl| egl.version)
237    }
238
239    pub fn raw_context(&self) -> *mut ffi::c_void {
240        match self.egl {
241            Some(ref egl) => egl.raw.as_ptr(),
242            None => ptr::null_mut(),
243        }
244    }
245}
246
247impl Drop for AdapterContext {
248    fn drop(&mut self) {
249        struct CurrentGuard<'a>(&'a EglContext);
250        impl Drop for CurrentGuard<'_> {
251            fn drop(&mut self) {
252                self.0.unmake_current();
253            }
254        }
255
256        // Context must be current when dropped. See safety docs on
257        // `glow::HasContext`.
258        //
259        // NOTE: This is only set to `None` by `Adapter::new_external` which
260        // requires the context to be current when anything that may be holding
261        // the `Arc<AdapterShared>` is dropped.
262        let _guard = self.egl.as_ref().map(|egl| {
263            egl.make_current();
264            CurrentGuard(egl)
265        });
266        let glow = self.glow.get_mut();
267        // SAFETY: Field not used after this.
268        unsafe { ManuallyDrop::drop(glow) };
269    }
270}
271
272struct EglContextLock<'a> {
273    instance: &'a Arc<EglInstance>,
274    display: khronos_egl::Display,
275}
276
277/// A guard containing a lock to an [`AdapterContext`], while the GL context is kept current.
278#[expect(missing_debug_implementations)]
279pub struct AdapterContextLock<'a> {
280    glow: MutexGuard<'a, ManuallyDrop<glow::Context>>,
281    egl: Option<EglContextLock<'a>>,
282}
283
284impl<'a> core::ops::Deref for AdapterContextLock<'a> {
285    type Target = glow::Context;
286
287    fn deref(&self) -> &Self::Target {
288        &self.glow
289    }
290}
291
292impl<'a> Drop for AdapterContextLock<'a> {
293    fn drop(&mut self) {
294        if let Some(egl) = self.egl.take() {
295            if let Err(err) = egl.instance.make_current(egl.display, None, None, None) {
296                log::error!("Failed to make EGL context current: {err:?}");
297            }
298        }
299    }
300}
301
302impl AdapterContext {
303    /// Get's the [`glow::Context`] without waiting for a lock
304    ///
305    /// # Safety
306    ///
307    /// This should only be called when you have manually made sure that the current thread has made
308    /// the EGL context current and that no other thread also has the EGL context current.
309    /// Additionally, you must manually make the EGL context **not** current after you are done with
310    /// it, so that future calls to `lock()` will not fail.
311    ///
312    /// > **Note:** Calling this function **will** still lock the [`glow::Context`] which adds an
313    /// > extra safe-guard against accidental concurrent access to the context.
314    pub unsafe fn get_without_egl_lock(&self) -> MappedMutexGuard<'_, glow::Context> {
315        let guard = self
316            .glow
317            .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
318            .expect("Could not lock adapter context. This is most-likely a deadlock.");
319        MutexGuard::map(guard, |glow| &mut **glow)
320    }
321
322    /// Obtain a lock to the EGL context and get handle to the [`glow::Context`] that can be used to
323    /// do rendering.
324    #[track_caller]
325    pub fn lock<'a>(&'a self) -> AdapterContextLock<'a> {
326        let glow = self
327            .glow
328            // Don't lock forever. If it takes longer than 1 second to get the lock we've got a
329            // deadlock and should panic to show where we got stuck
330            .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
331            .expect("Could not lock adapter context. This is most-likely a deadlock.");
332
333        let egl = self.egl.as_ref().map(|egl| {
334            egl.make_current();
335            EglContextLock {
336                instance: &egl.instance,
337                display: egl.display,
338            }
339        });
340
341        AdapterContextLock { glow, egl }
342    }
343}
344
345#[derive(Debug)]
346struct Inner {
347    /// Note: the context contains a dummy pbuffer (1x1).
348    /// Required for `eglMakeCurrent` on platforms that doesn't supports `EGL_KHR_surfaceless_context`.
349    egl: EglContext,
350    version: (i32, i32),
351    supports_native_window: bool,
352    config: khronos_egl::Config,
353    /// Method by which the framebuffer should support srgb
354    srgb_kind: SrgbFrameBufferKind,
355}
356
357#[cfg(send_sync)]
358unsafe impl Send for Inner {}
359#[cfg(send_sync)]
360unsafe impl Sync for Inner {}
361
362// Different calls to `eglGetPlatformDisplay` may return the same `Display`, making it a global
363// state of all our `EglContext`s. This forces us to track the number of such context to prevent
364// terminating the display if it's currently used by another `EglContext`.
365static DISPLAYS_REFERENCE_COUNT: Lazy<Mutex<HashMap<usize, usize>>> = Lazy::new(Default::default);
366
367fn initialize_display(
368    egl: &EglInstance,
369    display: khronos_egl::Display,
370) -> Result<(i32, i32), khronos_egl::Error> {
371    let mut guard = DISPLAYS_REFERENCE_COUNT.lock();
372    *guard.entry(display.as_ptr() as usize).or_default() += 1;
373
374    // We don't need to check the reference count here since according to the `eglInitialize`
375    // documentation, initializing an already initialized EGL display connection has no effect
376    // besides returning the version numbers.
377    egl.initialize(display)
378}
379
380fn terminate_display(
381    egl: &EglInstance,
382    display: khronos_egl::Display,
383) -> Result<(), khronos_egl::Error> {
384    let key = &(display.as_ptr() as usize);
385    let mut guard = DISPLAYS_REFERENCE_COUNT.lock();
386    let count_ref = guard
387        .get_mut(key)
388        .expect("Attempted to decref a display before incref was called");
389
390    if *count_ref > 1 {
391        *count_ref -= 1;
392
393        Ok(())
394    } else {
395        guard.remove(key);
396
397        egl.terminate(display)
398    }
399}
400
401fn instance_err<E: core::error::Error + Send + Sync + 'static>(
402    message: impl Into<String>,
403) -> impl FnOnce(E) -> crate::InstanceError {
404    move |e| crate::InstanceError::with_source(message.into(), e)
405}
406
407impl Inner {
408    fn create(
409        flags: wgt::InstanceFlags,
410        egl: Arc<EglInstance>,
411        display: khronos_egl::Display,
412        force_gles_minor_version: wgt::Gles3MinorVersion,
413    ) -> Result<Self, crate::InstanceError> {
414        let version = initialize_display(&egl, display)
415            .map_err(instance_err("failed to initialize EGL display connection"))?;
416        let vendor = egl
417            .query_string(Some(display), khronos_egl::VENDOR)
418            .map_err(instance_err("failed to query EGL vendor"))?;
419        let display_extensions = egl
420            .query_string(Some(display), khronos_egl::EXTENSIONS)
421            .map_err(instance_err("failed to query EGL display extensions"))?
422            .to_string_lossy();
423        log::debug!("Display vendor {vendor:?}, version {version:?}",);
424        log::debug!(
425            "Display extensions: {:#?}",
426            display_extensions.split_whitespace().collect::<Vec<_>>()
427        );
428
429        let srgb_kind = if version >= (1, 5) {
430            log::debug!("\tEGL surface: +srgb");
431            SrgbFrameBufferKind::Core
432        } else if display_extensions.contains("EGL_KHR_gl_colorspace") {
433            log::debug!("\tEGL surface: +srgb khr");
434            SrgbFrameBufferKind::Khr
435        } else {
436            log::debug!("\tEGL surface: -srgb");
437            SrgbFrameBufferKind::None
438        };
439
440        if log::max_level() >= log::LevelFilter::Trace {
441            log::trace!("Configurations:");
442            let config_count = egl
443                .get_config_count(display)
444                .map_err(instance_err("failed to get config count"))?;
445            let mut configurations = Vec::with_capacity(config_count);
446            egl.get_configs(display, &mut configurations)
447                .map_err(instance_err("failed to get configs"))?;
448            for &config in configurations.iter() {
449                log::trace!("\tCONFORMANT=0x{:X?}, RENDERABLE=0x{:X?}, NATIVE_RENDERABLE=0x{:X?}, SURFACE_TYPE=0x{:X?}, ALPHA_SIZE={:?}",
450                    egl.get_config_attrib(display, config, khronos_egl::CONFORMANT),
451                    egl.get_config_attrib(display, config, khronos_egl::RENDERABLE_TYPE),
452                    egl.get_config_attrib(display, config, khronos_egl::NATIVE_RENDERABLE),
453                    egl.get_config_attrib(display, config, khronos_egl::SURFACE_TYPE),
454                    egl.get_config_attrib(display, config, khronos_egl::ALPHA_SIZE),
455                );
456            }
457        }
458
459        let (config, supports_native_window) = choose_config(&egl, display, srgb_kind)?;
460
461        let supports_opengl = if version >= (1, 4) {
462            let client_apis = egl
463                .query_string(Some(display), khronos_egl::CLIENT_APIS)
464                .map_err(instance_err("failed to query EGL client APIs string"))?
465                .to_string_lossy();
466            client_apis
467                .split(' ')
468                .any(|client_api| client_api == "OpenGL")
469        } else {
470            false
471        };
472
473        let mut khr_context_flags = 0;
474        let supports_khr_context = display_extensions.contains("EGL_KHR_create_context");
475
476        let mut context_attributes = vec![];
477        let mut gl_context_attributes = vec![];
478        let mut gles_context_attributes = vec![];
479        gl_context_attributes.push(khronos_egl::CONTEXT_MAJOR_VERSION);
480        gl_context_attributes.push(3);
481        gl_context_attributes.push(khronos_egl::CONTEXT_MINOR_VERSION);
482        gl_context_attributes.push(3);
483        if supports_opengl && force_gles_minor_version != wgt::Gles3MinorVersion::Automatic {
484            log::warn!("Ignoring specified GLES minor version as OpenGL is used");
485        }
486        gles_context_attributes.push(khronos_egl::CONTEXT_MAJOR_VERSION);
487        gles_context_attributes.push(3); // Request GLES 3.0 or higher
488        if force_gles_minor_version != wgt::Gles3MinorVersion::Automatic {
489            gles_context_attributes.push(khronos_egl::CONTEXT_MINOR_VERSION);
490            gles_context_attributes.push(match force_gles_minor_version {
491                wgt::Gles3MinorVersion::Automatic => unreachable!(),
492                wgt::Gles3MinorVersion::Version0 => 0,
493                wgt::Gles3MinorVersion::Version1 => 1,
494                wgt::Gles3MinorVersion::Version2 => 2,
495            });
496        }
497        if flags.contains(wgt::InstanceFlags::DEBUG) {
498            if version >= (1, 5) {
499                log::debug!("\tEGL context: +debug");
500                context_attributes.push(khronos_egl::CONTEXT_OPENGL_DEBUG);
501                context_attributes.push(khronos_egl::TRUE as _);
502            } else if supports_khr_context {
503                log::debug!("\tEGL context: +debug KHR");
504                khr_context_flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
505            } else {
506                log::debug!("\tEGL context: -debug");
507            }
508        }
509
510        if khr_context_flags != 0 {
511            context_attributes.push(EGL_CONTEXT_FLAGS_KHR);
512            context_attributes.push(khr_context_flags);
513        }
514
515        gl_context_attributes.extend(&context_attributes);
516        gles_context_attributes.extend(&context_attributes);
517
518        let context = {
519            #[derive(Copy, Clone)]
520            enum Robustness {
521                Core,
522                Ext,
523            }
524
525            let robustness = if version >= (1, 5) {
526                Some(Robustness::Core)
527            } else if display_extensions.contains("EGL_EXT_create_context_robustness") {
528                Some(Robustness::Ext)
529            } else {
530                None
531            };
532
533            let create_context = |api, base_attributes: &[khronos_egl::Int]| {
534                egl.bind_api(api)?;
535
536                let mut robustness = robustness;
537                loop {
538                    let robustness_attributes = match robustness {
539                        Some(Robustness::Core) => {
540                            vec![
541                                khronos_egl::CONTEXT_OPENGL_ROBUST_ACCESS,
542                                khronos_egl::TRUE as _,
543                                khronos_egl::NONE,
544                            ]
545                        }
546                        Some(Robustness::Ext) => {
547                            vec![
548                                EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT,
549                                khronos_egl::TRUE as _,
550                                khronos_egl::NONE,
551                            ]
552                        }
553                        None => vec![khronos_egl::NONE],
554                    };
555
556                    let mut context_attributes = base_attributes.to_vec();
557                    context_attributes.extend(&robustness_attributes);
558
559                    match egl.create_context(display, config, None, &context_attributes) {
560                        Ok(context) => {
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                            return Ok(context);
573                        }
574
575                        // Robust access context creation can fail with different error codes
576                        // depending on the EGL path. Retry with a lower robustness level.
577                        Err(
578                            khronos_egl::Error::BadAttribute
579                            | khronos_egl::Error::BadMatch
580                            | khronos_egl::Error::BadConfig,
581                        ) if robustness.is_some() => {
582                            robustness = match robustness {
583                                Some(Robustness::Core)
584                                    if display_extensions
585                                        .contains("EGL_EXT_create_context_robustness") =>
586                                {
587                                    Some(Robustness::Ext)
588                                }
589                                _ => None,
590                            };
591                            continue;
592                        }
593
594                        Err(e) => return Err(e),
595                    }
596                }
597            };
598
599            let result = if supports_opengl {
600                create_context(khronos_egl::OPENGL_API, &gl_context_attributes).or_else(
601                    |gl_error| {
602                        log::debug!("Failed to create desktop OpenGL context: {gl_error}, falling back to OpenGL ES");
603                        create_context(khronos_egl::OPENGL_ES_API, &gles_context_attributes)
604                    },
605                )
606            } else {
607                create_context(khronos_egl::OPENGL_ES_API, &gles_context_attributes)
608            };
609
610            result.map_err(|e| {
611                crate::InstanceError::with_source(
612                    String::from("unable to create OpenGL or GLES 3.x context"),
613                    e,
614                )
615            })
616        }?;
617
618        // Testing if context can be binded without surface
619        // and creating dummy pbuffer surface if not.
620        let pbuffer = if version >= (1, 5)
621            || display_extensions.contains("EGL_KHR_surfaceless_context")
622            || cfg!(Emscripten)
623        {
624            log::debug!("\tEGL context: +surfaceless");
625            None
626        } else {
627            let attributes = [
628                khronos_egl::WIDTH,
629                1,
630                khronos_egl::HEIGHT,
631                1,
632                khronos_egl::NONE,
633            ];
634            egl.create_pbuffer_surface(display, config, &attributes)
635                .map(Some)
636                .map_err(|e| {
637                    crate::InstanceError::with_source(
638                        String::from("error in create_pbuffer_surface"),
639                        e,
640                    )
641                })?
642        };
643
644        Ok(Self {
645            egl: EglContext {
646                instance: egl,
647                display,
648                raw: context,
649                pbuffer,
650                version,
651            },
652            version,
653            supports_native_window,
654            config,
655            srgb_kind,
656        })
657    }
658}
659
660impl Drop for Inner {
661    fn drop(&mut self) {
662        // ERROR: Since EglContext is erroneously Clone, these handles could be copied and
663        // accidentally used elsewhere outside of Inner, despite us assuming ownership and
664        // destroying the handles here.
665        if let Err(e) = self
666            .egl
667            .instance
668            .destroy_context(self.egl.display, self.egl.raw)
669        {
670            log::warn!("Error in destroy_context: {e:?}");
671        }
672
673        if let Err(e) = terminate_display(&self.egl.instance, self.egl.display) {
674            log::warn!("Error in terminate: {e:?}");
675        }
676    }
677}
678
679#[derive(Clone, Copy, Debug, PartialEq)]
680enum WindowKind {
681    Wayland,
682    X11,
683    AngleX11,
684    Unknown,
685}
686
687#[derive(Clone, Debug)]
688struct WindowSystemInterface {
689    kind: WindowKind,
690}
691
692#[derive(Debug)]
693pub struct Instance {
694    wsi: WindowSystemInterface,
695    flags: wgt::InstanceFlags,
696    options: wgt::GlBackendOptions,
697    inner: Mutex<Inner>,
698}
699
700impl Instance {
701    pub fn raw_display(&self) -> khronos_egl::Display {
702        self.inner
703            .try_lock()
704            .expect("Could not lock instance. This is most-likely a deadlock.")
705            .egl
706            .display
707    }
708
709    /// Returns the version of the EGL display.
710    pub fn egl_version(&self) -> (i32, i32) {
711        self.inner
712            .try_lock()
713            .expect("Could not lock instance. This is most-likely a deadlock.")
714            .version
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::Windows(_)) | None, Some(egl))
771                if cfg!(windows)
772                    && client_ext_str.contains("EGL_ANGLE_platform_angle")
773                    && client_ext_str.contains("EGL_ANGLE_platform_angle_d3d") =>
774            {
775                log::debug!("Using Angle platform with D3D11");
776                const EGL_PLATFORM_ANGLE_TYPE_ANGLE: u32 = 0x3203;
777                const EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE: u32 = 0x3208;
778                let display_attributes = [
779                    EGL_PLATFORM_ANGLE_TYPE_ANGLE as khronos_egl::Attrib,
780                    EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE as khronos_egl::Attrib,
781                    EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as khronos_egl::Attrib,
782                    usize::from(desc.flags.contains(wgt::InstanceFlags::VALIDATION)),
783                    khronos_egl::ATTRIB_NONE,
784                ];
785                let display = unsafe {
786                    egl.get_platform_display(
787                        EGL_PLATFORM_ANGLE_ANGLE,
788                        khronos_egl::DEFAULT_DISPLAY,
789                        &display_attributes,
790                    )
791                }
792                .map_err(instance_err("failed to get Angle D3D11 display"))?;
793                (display, WindowKind::Unknown)
794            }
795            (Some(Rdh::Wayland(wayland_display_handle)), Some(egl))
796                if client_ext_str.contains("EGL_EXT_platform_wayland") =>
797            {
798                log::debug!("Using Wayland platform");
799                let display_attributes = [khronos_egl::ATTRIB_NONE];
800                let display = unsafe {
801                    egl.get_platform_display(
802                        EGL_PLATFORM_WAYLAND_KHR,
803                        wayland_display_handle.display.as_ptr(),
804                        &display_attributes,
805                    )
806                }
807                .map_err(instance_err("failed to get Wayland display"))?;
808                (display, WindowKind::Wayland)
809            }
810            (Some(Rdh::Xlib(xlib_display_handle)), Some(egl))
811                if client_ext_str.contains("EGL_EXT_platform_x11") =>
812            {
813                log::debug!("Using X11 platform");
814                let display_attributes = [khronos_egl::ATTRIB_NONE];
815                let display = unsafe {
816                    egl.get_platform_display(
817                        EGL_PLATFORM_X11_KHR,
818                        xlib_display_handle
819                            .display
820                            .map_or(khronos_egl::DEFAULT_DISPLAY, ptr::NonNull::as_ptr),
821                        &display_attributes,
822                    )
823                }
824                .map_err(instance_err("failed to get X11 display"))?;
825                (display, WindowKind::X11)
826            }
827            (Some(Rdh::Xlib(xlib_display_handle)), Some(egl))
828                if client_ext_str.contains("EGL_ANGLE_platform_angle") =>
829            {
830                log::debug!("Using Angle platform with X11");
831                let display_attributes = [
832                    EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE as khronos_egl::Attrib,
833                    EGL_PLATFORM_X11_KHR as khronos_egl::Attrib,
834                    EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as khronos_egl::Attrib,
835                    usize::from(desc.flags.contains(wgt::InstanceFlags::VALIDATION)),
836                    khronos_egl::ATTRIB_NONE,
837                ];
838                let display = unsafe {
839                    egl.get_platform_display(
840                        EGL_PLATFORM_ANGLE_ANGLE,
841                        xlib_display_handle
842                            .display
843                            .map_or(khronos_egl::DEFAULT_DISPLAY, ptr::NonNull::as_ptr),
844                        &display_attributes,
845                    )
846                }
847                .map_err(instance_err("failed to get Angle display"))?;
848                (display, WindowKind::AngleX11)
849            }
850            (Some(Rdh::Xcb(xcb_display_handle)), Some(egl))
851                if client_ext_str.contains("EGL_EXT_platform_xcb") =>
852            {
853                log::debug!("Using XCB platform");
854                let display_attributes = [
855                    EGL_PLATFORM_XCB_SCREEN_EXT as khronos_egl::Attrib,
856                    xcb_display_handle.screen as khronos_egl::Attrib,
857                    khronos_egl::ATTRIB_NONE,
858                ];
859                let display = unsafe {
860                    egl.get_platform_display(
861                        EGL_PLATFORM_XCB_EXT,
862                        xcb_display_handle
863                            .connection
864                            .map_or(khronos_egl::DEFAULT_DISPLAY, ptr::NonNull::as_ptr),
865                        &display_attributes,
866                    )
867                }
868                .map_err(instance_err("failed to get XCB display"))?;
869                (display, WindowKind::X11)
870            }
871            x if client_ext_str.contains("EGL_MESA_platform_surfaceless") => {
872                log::debug!(
873                    "No (or unknown) windowing system ({x:?}) present. Using surfaceless platform"
874                );
875                #[allow(
876                    clippy::unnecessary_literal_unwrap,
877                    reason = "this is only a literal on Emscripten"
878                )]
879                // TODO: This extension is also supported on EGL 1.4 with EGL_EXT_platform_base: https://registry.khronos.org/EGL/extensions/MESA/EGL_MESA_platform_surfaceless.txt
880                let egl = egl1_5.expect("Failed to get EGL 1.5 for surfaceless");
881                let display = unsafe {
882                    egl.get_platform_display(
883                        EGL_PLATFORM_SURFACELESS_MESA,
884                        khronos_egl::DEFAULT_DISPLAY,
885                        &[khronos_egl::ATTRIB_NONE],
886                    )
887                }
888                .map_err(instance_err("failed to get MESA surfaceless display"))?;
889                (display, WindowKind::Unknown)
890            }
891            x => {
892                log::debug!(
893                    "No (or unknown) windowing system {x:?} and EGL_MESA_platform_surfaceless not available. Using default platform"
894                );
895                let display =
896                    unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) }.ok_or_else(|| {
897                        crate::InstanceError::new("Failed to get default display".into())
898                    })?;
899                (display, WindowKind::Unknown)
900            }
901        };
902
903        if desc.flags.contains(wgt::InstanceFlags::VALIDATION)
904            && client_ext_str.contains("EGL_KHR_debug")
905        {
906            log::debug!("Enabling EGL debug output");
907            let function: EglDebugMessageControlFun = {
908                let addr = egl
909                    .get_proc_address("eglDebugMessageControlKHR")
910                    .ok_or_else(|| {
911                        crate::InstanceError::new(
912                            "failed to get `eglDebugMessageControlKHR` proc address".into(),
913                        )
914                    })?;
915                unsafe { core::mem::transmute(addr) }
916            };
917            let attributes = [
918                EGL_DEBUG_MSG_CRITICAL_KHR as khronos_egl::Attrib,
919                1,
920                EGL_DEBUG_MSG_ERROR_KHR as khronos_egl::Attrib,
921                1,
922                EGL_DEBUG_MSG_WARN_KHR as khronos_egl::Attrib,
923                1,
924                EGL_DEBUG_MSG_INFO_KHR as khronos_egl::Attrib,
925                1,
926                khronos_egl::ATTRIB_NONE,
927            ];
928            unsafe { (function)(Some(egl_debug_proc), attributes.as_ptr()) };
929        }
930
931        let inner = Inner::create(
932            desc.flags,
933            egl,
934            display,
935            desc.backend_options.gl.gles_minor_version,
936        )?;
937
938        Ok(Instance {
939            wsi: WindowSystemInterface { kind: wsi_kind },
940            flags: desc.flags,
941            options: desc.backend_options.gl.clone(),
942            inner: Mutex::new(inner),
943        })
944    }
945
946    unsafe fn create_surface(
947        &self,
948        display_handle: raw_window_handle::RawDisplayHandle,
949        window_handle: raw_window_handle::RawWindowHandle,
950    ) -> Result<Surface, crate::InstanceError> {
951        use raw_window_handle::RawWindowHandle as Rwh;
952
953        let inner = self.inner.lock();
954
955        match (window_handle, display_handle) {
956            (Rwh::Xlib(_), _) => {}
957            (Rwh::Xcb(_), _) => {}
958            (Rwh::Win32(_), _) => {}
959            (Rwh::AppKit(_), _) => {}
960            (Rwh::OhosNdk(_), _) => {}
961            #[cfg(target_os = "android")]
962            (Rwh::AndroidNdk(handle), _) => {
963                let format = inner
964                    .egl
965                    .instance
966                    .get_config_attrib(
967                        inner.egl.display,
968                        inner.config,
969                        khronos_egl::NATIVE_VISUAL_ID,
970                    )
971                    .map_err(instance_err("failed to get config NATIVE_VISUAL_ID"))?;
972
973                let ret = unsafe {
974                    ndk_sys::ANativeWindow_setBuffersGeometry(
975                        handle
976                            .a_native_window
977                            .as_ptr()
978                            .cast::<ndk_sys::ANativeWindow>(),
979                        0,
980                        0,
981                        format,
982                    )
983                };
984
985                if ret != 0 {
986                    return Err(crate::InstanceError::new(format!(
987                        "error {ret} returned from ANativeWindow_setBuffersGeometry",
988                    )));
989                }
990            }
991            (Rwh::Wayland(_), _) => {}
992            #[cfg(Emscripten)]
993            (Rwh::Web(_), _) => {}
994            other => {
995                return Err(crate::InstanceError::new(format!(
996                    "unsupported window: {other:?}"
997                )));
998            }
999        };
1000
1001        inner.egl.unmake_current();
1002
1003        Ok(Surface {
1004            egl: inner.egl.clone(),
1005            wsi: self.wsi.clone(),
1006            config: inner.config,
1007            presentable: inner.supports_native_window,
1008            raw_window_handle: window_handle,
1009            swapchain: RwLock::new(None),
1010            srgb_kind: inner.srgb_kind,
1011        })
1012    }
1013
1014    unsafe fn enumerate_adapters(
1015        &self,
1016        _surface_hint: Option<&Surface>,
1017    ) -> Vec<crate::ExposedAdapter<super::Api>> {
1018        let inner = self.inner.lock();
1019        inner.egl.make_current();
1020
1021        let mut gl = unsafe {
1022            glow::Context::from_loader_function(|name| {
1023                inner
1024                    .egl
1025                    .instance
1026                    .get_proc_address(name)
1027                    .map_or(ptr::null(), |p| p as *const _)
1028            })
1029        };
1030
1031        // In contrast to OpenGL ES, OpenGL requires explicitly enabling sRGB conversions,
1032        // as otherwise the user has to do the sRGB conversion.
1033        if !matches!(inner.srgb_kind, SrgbFrameBufferKind::None) {
1034            unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
1035        }
1036
1037        if self.flags.contains(wgt::InstanceFlags::DEBUG) && gl.supports_debug() {
1038            log::debug!("Max label length: {}", unsafe {
1039                gl.get_parameter_i32(glow::MAX_LABEL_LENGTH)
1040            });
1041        }
1042
1043        if self.flags.contains(wgt::InstanceFlags::VALIDATION) && gl.supports_debug() {
1044            log::debug!("Enabling GLES debug output");
1045            unsafe { gl.enable(glow::DEBUG_OUTPUT) };
1046            unsafe { gl.debug_message_callback(super::gl_debug_message_callback) };
1047        }
1048
1049        // Wrap in ManuallyDrop to make it easier to "current" the GL context before dropping this
1050        // GLOW context, which could also happen if a panic occurs after we uncurrent the context
1051        // below but before AdapterContext is constructed.
1052        let gl = ManuallyDrop::new(gl);
1053        inner.egl.unmake_current();
1054
1055        unsafe {
1056            super::Adapter::expose(
1057                AdapterContext {
1058                    glow: Mutex::new(gl),
1059                    // ERROR: Copying owned reference handles here, be careful to not drop them!
1060                    egl: Some(inner.egl.clone()),
1061                },
1062                self.options.clone(),
1063            )
1064        }
1065        .into_iter()
1066        .collect()
1067    }
1068}
1069
1070impl super::Adapter {
1071    /// Creates a new external adapter using the specified loader function.
1072    ///
1073    /// # Safety
1074    ///
1075    /// - The underlying OpenGL ES context must be current.
1076    /// - The underlying OpenGL ES context must be current when interfacing with any objects returned by
1077    ///   wgpu-hal from this adapter.
1078    /// - The underlying OpenGL ES context must be current when dropping this adapter and when
1079    ///   dropping any objects returned from this adapter.
1080    pub unsafe fn new_external(
1081        fun: impl FnMut(&str) -> *const ffi::c_void,
1082        options: wgt::GlBackendOptions,
1083    ) -> Option<crate::ExposedAdapter<super::Api>> {
1084        let context = unsafe { glow::Context::from_loader_function(fun) };
1085        unsafe {
1086            Self::expose(
1087                AdapterContext {
1088                    glow: Mutex::new(ManuallyDrop::new(context)),
1089                    egl: None,
1090                },
1091                options,
1092            )
1093        }
1094    }
1095
1096    pub fn adapter_context(&self) -> &AdapterContext {
1097        &self.shared.context
1098    }
1099}
1100
1101impl super::Device {
1102    /// Returns the underlying EGL context.
1103    pub fn context(&self) -> &AdapterContext {
1104        &self.shared.context
1105    }
1106}
1107
1108#[derive(Debug)]
1109pub struct Swapchain {
1110    surface: khronos_egl::Surface,
1111    wl_window: Option<*mut ffi::c_void>,
1112    framebuffer: glow::Framebuffer,
1113    renderbuffer: glow::Renderbuffer,
1114    /// Extent because the window lies
1115    extent: wgt::Extent3d,
1116    format: wgt::TextureFormat,
1117    format_desc: super::TextureFormatDesc,
1118    #[allow(unused)]
1119    sample_type: wgt::TextureSampleType,
1120}
1121
1122#[derive(Debug)]
1123pub struct Surface {
1124    egl: EglContext,
1125    wsi: WindowSystemInterface,
1126    config: khronos_egl::Config,
1127    pub(super) presentable: bool,
1128    raw_window_handle: raw_window_handle::RawWindowHandle,
1129    swapchain: RwLock<Option<Swapchain>>,
1130    srgb_kind: SrgbFrameBufferKind,
1131}
1132
1133unsafe impl Send for Surface {}
1134unsafe impl Sync for Surface {}
1135
1136impl Surface {
1137    pub(super) unsafe fn present(
1138        &self,
1139        _suf_texture: super::Texture,
1140        context: &AdapterContext,
1141    ) -> Result<(), crate::SurfaceError> {
1142        let gl = unsafe { context.get_without_egl_lock() };
1143        let swapchain = self.swapchain.read();
1144        let sc = swapchain.as_ref().ok_or(crate::SurfaceError::Other(
1145            "Surface has no swap-chain configured",
1146        ))?;
1147
1148        self.egl
1149            .instance
1150            .make_current(
1151                self.egl.display,
1152                Some(sc.surface),
1153                Some(sc.surface),
1154                Some(self.egl.raw),
1155            )
1156            .map_err(|e| {
1157                log::error!("make_current(surface) failed: {e}");
1158                crate::SurfaceError::Lost
1159            })?;
1160
1161        unsafe { gl.disable(glow::SCISSOR_TEST) };
1162        unsafe { gl.color_mask(true, true, true, true) };
1163
1164        unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) };
1165        unsafe { gl.draw_buffers(&[glow::BACK]) };
1166        unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) };
1167
1168        if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) {
1169            // Disable sRGB conversions for `glBlitFramebuffer` as behavior does diverge between
1170            // drivers and formats otherwise and we want to ensure no sRGB conversions happen.
1171            unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) };
1172        }
1173
1174        // Note the Y-flipping here. GL's presentation is not flipped,
1175        // but main rendering is. Therefore, we Y-flip the output positions
1176        // in the shader, and also this blit.
1177        unsafe {
1178            gl.blit_framebuffer(
1179                0,
1180                sc.extent.height as i32,
1181                sc.extent.width as i32,
1182                0,
1183                0,
1184                0,
1185                sc.extent.width as i32,
1186                sc.extent.height as i32,
1187                glow::COLOR_BUFFER_BIT,
1188                glow::NEAREST,
1189            )
1190        };
1191
1192        if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) {
1193            unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
1194        }
1195
1196        unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1197
1198        self.egl
1199            .instance
1200            .swap_buffers(self.egl.display, sc.surface)
1201            .map_err(|e| {
1202                log::error!("swap_buffers failed: {e}");
1203                crate::SurfaceError::Lost
1204                // TODO: should we unset the current context here?
1205            })?;
1206        self.egl
1207            .instance
1208            .make_current(self.egl.display, None, None, None)
1209            .map_err(|e| {
1210                log::error!("make_current(null) failed: {e}");
1211                crate::SurfaceError::Lost
1212            })?;
1213
1214        Ok(())
1215    }
1216
1217    unsafe fn unconfigure_impl(
1218        &self,
1219        device: &super::Device,
1220    ) -> Option<(khronos_egl::Surface, Option<*mut ffi::c_void>)> {
1221        let gl = &device.shared.context.lock();
1222        match self.swapchain.write().take() {
1223            Some(sc) => {
1224                unsafe { gl.delete_renderbuffer(sc.renderbuffer) };
1225                unsafe { gl.delete_framebuffer(sc.framebuffer) };
1226                Some((sc.surface, sc.wl_window))
1227            }
1228            None => None,
1229        }
1230    }
1231
1232    pub fn supports_srgb(&self) -> bool {
1233        match self.srgb_kind {
1234            SrgbFrameBufferKind::None => false,
1235            _ => true,
1236        }
1237    }
1238}
1239
1240impl crate::Surface for Surface {
1241    type A = super::Api;
1242
1243    unsafe fn configure(
1244        &self,
1245        device: &super::Device,
1246        config: &crate::SurfaceConfiguration,
1247    ) -> Result<(), crate::SurfaceError> {
1248        use raw_window_handle::RawWindowHandle as Rwh;
1249
1250        let (surface, wl_window) = match unsafe { self.unconfigure_impl(device) } {
1251            Some((sc, wl_window)) => {
1252                #[cfg(unix)]
1253                if let Some(window) = wl_window {
1254                    wayland_sys::ffi_dispatch!(
1255                        wayland_sys::egl::wayland_egl_handle(),
1256                        wl_egl_window_resize,
1257                        window.cast(),
1258                        config.extent.width as i32,
1259                        config.extent.height as i32,
1260                        0,
1261                        0,
1262                    );
1263                }
1264
1265                (sc, wl_window)
1266            }
1267            None => {
1268                #[cfg_attr(not(unix), expect(unused_mut))]
1269                let mut wl_window = None;
1270                let (mut temp_xlib_handle, mut temp_xcb_handle);
1271                let native_window_ptr = match (self.wsi.kind, self.raw_window_handle) {
1272                    (WindowKind::Unknown | WindowKind::X11, Rwh::Xlib(handle)) => {
1273                        temp_xlib_handle = handle.window;
1274                        ptr::from_mut(&mut temp_xlib_handle).cast::<ffi::c_void>()
1275                    }
1276                    (WindowKind::AngleX11, Rwh::Xlib(handle)) => handle.window as *mut ffi::c_void,
1277                    (WindowKind::Unknown | WindowKind::X11, Rwh::Xcb(handle)) => {
1278                        temp_xcb_handle = handle.window;
1279                        ptr::from_mut(&mut temp_xcb_handle).cast::<ffi::c_void>()
1280                    }
1281                    (WindowKind::AngleX11, Rwh::Xcb(handle)) => {
1282                        handle.window.get() as *mut ffi::c_void
1283                    }
1284                    (WindowKind::Unknown, Rwh::AndroidNdk(handle)) => {
1285                        handle.a_native_window.as_ptr()
1286                    }
1287                    (WindowKind::Unknown, Rwh::OhosNdk(handle)) => handle.native_window.as_ptr(),
1288                    #[cfg(unix)]
1289                    (WindowKind::Wayland, Rwh::Wayland(handle)) => {
1290                        let window = wayland_sys::ffi_dispatch!(
1291                            wayland_sys::egl::wayland_egl_handle(),
1292                            wl_egl_window_create,
1293                            handle.surface.as_ptr().cast(),
1294                            config.extent.width as i32,
1295                            config.extent.height as i32,
1296                        );
1297                        wl_window = Some(window.cast());
1298                        window.cast()
1299                    }
1300                    #[cfg(Emscripten)]
1301                    (WindowKind::Unknown, Rwh::Web(handle)) => handle.id as *mut ffi::c_void,
1302                    (WindowKind::Unknown, Rwh::Win32(handle)) => {
1303                        handle.hwnd.get() as *mut ffi::c_void
1304                    }
1305                    (WindowKind::Unknown, Rwh::AppKit(handle)) => {
1306                        #[cfg(not(target_os = "macos"))]
1307                        let window_ptr = handle.ns_view.as_ptr();
1308                        #[cfg(target_os = "macos")]
1309                        let window_ptr = {
1310                            use objc2::msg_send;
1311                            use objc2::runtime::AnyObject;
1312                            // ns_view always have a layer and don't need to verify that it exists.
1313                            let layer: *mut AnyObject =
1314                                msg_send![handle.ns_view.as_ptr().cast::<AnyObject>(), layer];
1315                            layer.cast::<ffi::c_void>()
1316                        };
1317                        window_ptr
1318                    }
1319                    _ => {
1320                        log::warn!(
1321                            "Initialized platform {:?} doesn't work with window {:?}",
1322                            self.wsi.kind,
1323                            self.raw_window_handle
1324                        );
1325                        return Err(crate::SurfaceError::Other("incompatible window kind"));
1326                    }
1327                };
1328
1329                let mut attributes = vec![
1330                    khronos_egl::RENDER_BUFFER,
1331                    // We don't want any of the buffering done by the driver, because we
1332                    // manage a swapchain on our side.
1333                    // Some drivers just fail on surface creation seeing `EGL_SINGLE_BUFFER`.
1334                    if cfg!(any(
1335                        target_os = "android",
1336                        target_os = "macos",
1337                        target_env = "ohos"
1338                    )) || cfg!(windows)
1339                        || self.wsi.kind == WindowKind::AngleX11
1340                    {
1341                        khronos_egl::BACK_BUFFER
1342                    } else {
1343                        khronos_egl::SINGLE_BUFFER
1344                    },
1345                ];
1346                if config.format.has_srgb_suffix() {
1347                    match self.srgb_kind {
1348                        SrgbFrameBufferKind::None => {}
1349                        SrgbFrameBufferKind::Core => {
1350                            attributes.push(khronos_egl::GL_COLORSPACE);
1351                            attributes.push(khronos_egl::GL_COLORSPACE_SRGB);
1352                        }
1353                        SrgbFrameBufferKind::Khr => {
1354                            attributes.push(EGL_GL_COLORSPACE_KHR as i32);
1355                            attributes.push(EGL_GL_COLORSPACE_SRGB_KHR as i32);
1356                        }
1357                    }
1358                }
1359                attributes.push(khronos_egl::ATTRIB_NONE as i32);
1360
1361                #[cfg(not(Emscripten))]
1362                let egl1_5 = self.egl.instance.upcast::<khronos_egl::EGL1_5>();
1363
1364                #[cfg(Emscripten)]
1365                let egl1_5: Option<&Arc<EglInstance>> = Some(&self.egl.instance);
1366
1367                // Careful, we can still be in 1.4 version even if `upcast` succeeds
1368                let raw_result = match egl1_5 {
1369                    Some(egl) if self.wsi.kind != WindowKind::Unknown => {
1370                        let attributes_usize = attributes
1371                            .into_iter()
1372                            .map(|v| v as usize)
1373                            .collect::<Vec<_>>();
1374                        unsafe {
1375                            egl.create_platform_window_surface(
1376                                self.egl.display,
1377                                self.config,
1378                                native_window_ptr,
1379                                &attributes_usize,
1380                            )
1381                        }
1382                    }
1383                    _ => unsafe {
1384                        self.egl.instance.create_window_surface(
1385                            self.egl.display,
1386                            self.config,
1387                            native_window_ptr,
1388                            Some(&attributes),
1389                        )
1390                    },
1391                };
1392
1393                match raw_result {
1394                    Ok(raw) => (raw, wl_window),
1395                    Err(e) => {
1396                        log::warn!("Error in create_window_surface: {e:?}");
1397                        return Err(crate::SurfaceError::Lost);
1398                    }
1399                }
1400            }
1401        };
1402
1403        let format_desc = device.shared.describe_texture_format(config.format);
1404        let gl = &device.shared.context.lock();
1405        let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| {
1406            log::error!("Internal swapchain renderbuffer creation failed: {error}");
1407            crate::DeviceError::OutOfMemory
1408        })?;
1409        unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) };
1410        unsafe {
1411            gl.renderbuffer_storage(
1412                glow::RENDERBUFFER,
1413                format_desc.internal,
1414                config.extent.width as _,
1415                config.extent.height as _,
1416            )
1417        };
1418        let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| {
1419            log::error!("Internal swapchain framebuffer creation failed: {error}");
1420            crate::DeviceError::OutOfMemory
1421        })?;
1422        unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) };
1423        unsafe {
1424            gl.framebuffer_renderbuffer(
1425                glow::READ_FRAMEBUFFER,
1426                glow::COLOR_ATTACHMENT0,
1427                glow::RENDERBUFFER,
1428                Some(renderbuffer),
1429            )
1430        };
1431        unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) };
1432        unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1433
1434        let mut swapchain = self.swapchain.write();
1435        *swapchain = Some(Swapchain {
1436            surface,
1437            wl_window,
1438            renderbuffer,
1439            framebuffer,
1440            extent: config.extent,
1441            format: config.format,
1442            format_desc,
1443            sample_type: wgt::TextureSampleType::Float { filterable: false },
1444        });
1445
1446        Ok(())
1447    }
1448
1449    unsafe fn unconfigure(&self, device: &super::Device) {
1450        if let Some((surface, wl_window)) = unsafe { self.unconfigure_impl(device) } {
1451            self.egl
1452                .instance
1453                .destroy_surface(self.egl.display, surface)
1454                .unwrap();
1455            if let Some(_window) = wl_window {
1456                #[cfg(unix)]
1457                wayland_sys::ffi_dispatch!(
1458                    wayland_sys::egl::wayland_egl_handle(),
1459                    wl_egl_window_destroy,
1460                    _window.cast(),
1461                );
1462            }
1463        }
1464    }
1465
1466    unsafe fn acquire_texture(
1467        &self,
1468        _timeout_ms: Option<Duration>, //TODO
1469        _fence: &super::Fence,
1470    ) -> Result<crate::AcquiredSurfaceTexture<super::Api>, crate::SurfaceError> {
1471        let swapchain = self.swapchain.read();
1472        let sc = swapchain.as_ref().ok_or(crate::SurfaceError::Other(
1473            "Surface has no swap-chain configured",
1474        ))?;
1475        let texture = super::Texture {
1476            inner: super::TextureInner::Renderbuffer {
1477                raw: sc.renderbuffer,
1478            },
1479            drop_guard: None,
1480            array_layer_count: 1,
1481            mip_level_count: 1,
1482            format: sc.format,
1483            format_desc: sc.format_desc.clone(),
1484            copy_size: crate::CopyExtent {
1485                width: sc.extent.width,
1486                height: sc.extent.height,
1487                depth: 1,
1488            },
1489        };
1490        Ok(crate::AcquiredSurfaceTexture {
1491            texture,
1492            suboptimal: false,
1493        })
1494    }
1495    unsafe fn discard_texture(&self, _texture: super::Texture) {}
1496}