gfx_backend_gl/window/
egl.rs

1//! EGL-based surface and swapchain.
2
3use crate::{conv, native, GlContainer, PhysicalDevice, Starc};
4use glow::HasContext;
5use hal::{image, window as w};
6use parking_lot::Mutex;
7use std::{os::raw, ptr};
8
9#[derive(Debug)]
10pub struct Swapchain {
11    framebuffer: glow::Framebuffer,
12    renderbuffer: glow::Renderbuffer,
13    /// Extent because the window lies
14    extent: w::Extent2D,
15    format: native::TextureFormat,
16    channel: hal::format::ChannelType,
17}
18
19#[derive(Debug)]
20pub struct Instance {
21    wsi_library: Option<libloading::Library>,
22    inner: Mutex<Inner>,
23}
24
25#[derive(Debug)]
26pub struct Inner {
27    egl: Starc<egl::DynamicInstance<egl::EGL1_4>>,
28    version: (i32, i32),
29    supports_native_window: bool,
30    display: egl::Display,
31    config: egl::Config,
32    context: egl::Context,
33    /// Dummy pbuffer (1x1).
34    /// Required for `eglMakeCurrent` on platforms that doesn't supports `EGL_KHR_surfaceless_context`.
35    pbuffer: Option<egl::Surface>,
36    wl_display: Option<*mut raw::c_void>,
37}
38
39unsafe impl Send for Instance {}
40unsafe impl Sync for Instance {}
41
42const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8;
43const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
44
45type XOpenDisplayFun =
46    unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
47
48type WlDisplayConnectFun =
49    unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
50
51type WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void);
52
53#[cfg(not(any(target_os = "android", target_os = "macos")))]
54type WlEglWindowCreateFun = unsafe extern "system" fn(
55    surface: *const raw::c_void,
56    width: raw::c_int,
57    height: raw::c_int,
58) -> *mut raw::c_void;
59
60type WlEglWindowResizeFun = unsafe extern "system" fn(
61    window: *const raw::c_void,
62    width: raw::c_int,
63    height: raw::c_int,
64    dx: raw::c_int,
65    dy: raw::c_int,
66);
67
68type WlEglWindowDestroyFun = unsafe extern "system" fn(window: *const raw::c_void);
69
70#[cfg(target_os = "android")]
71extern "C" {
72    pub fn ANativeWindow_setBuffersGeometry(
73        window: *mut raw::c_void,
74        width: i32,
75        height: i32,
76        format: i32,
77    ) -> i32;
78}
79
80fn open_x_display() -> Option<(ptr::NonNull<raw::c_void>, libloading::Library)> {
81    log::info!("Loading X11 library to get the current display");
82    unsafe {
83        let library = libloading::Library::new("libX11.so").ok()?;
84        let func: libloading::Symbol<XOpenDisplayFun> = library.get(b"XOpenDisplay").unwrap();
85        let result = func(ptr::null());
86        ptr::NonNull::new(result).map(|ptr| (ptr, library))
87    }
88}
89
90fn test_wayland_display() -> Option<libloading::Library> {
91    /* We try to connect and disconnect here to simply ensure there
92     * is an active wayland display available.
93     */
94    log::info!("Loading Wayland library to get the current display");
95    let library = unsafe {
96        let client_library = libloading::Library::new("libwayland-client.so").ok()?;
97        let wl_display_connect: libloading::Symbol<WlDisplayConnectFun> =
98            client_library.get(b"wl_display_connect").unwrap();
99        let wl_display_disconnect: libloading::Symbol<WlDisplayDisconnectFun> =
100            client_library.get(b"wl_display_disconnect").unwrap();
101        let display = ptr::NonNull::new(wl_display_connect(ptr::null()))?;
102        wl_display_disconnect(display.as_ptr());
103        libloading::Library::new("libwayland-egl.so").ok()?
104    };
105    Some(library)
106}
107
108/// Choose GLES framebuffer configuration.
109fn choose_config(
110    egl: &egl::DynamicInstance<egl::EGL1_4>,
111    display: egl::Display,
112) -> Result<(egl::Config, bool), hal::UnsupportedBackend> {
113    //TODO: EGL_SLOW_CONFIG
114    let tiers = [
115        (
116            "off-screen",
117            &[egl::RENDERABLE_TYPE, egl::OPENGL_ES2_BIT][..],
118        ),
119        ("presentation", &[egl::SURFACE_TYPE, egl::WINDOW_BIT]),
120        #[cfg(not(target_os = "android"))]
121        ("native-render", &[egl::NATIVE_RENDERABLE, egl::TRUE as _]),
122    ];
123
124    let mut attributes = Vec::with_capacity(7);
125    for tier_max in (0..tiers.len()).rev() {
126        let name = tiers[tier_max].0;
127        log::info!("Trying {}", name);
128
129        attributes.clear();
130        for &(_, tier_attr) in tiers[..=tier_max].iter() {
131            attributes.extend_from_slice(tier_attr);
132        }
133        attributes.push(egl::NONE);
134
135        match egl.choose_first_config(display, &attributes) {
136            Ok(Some(config)) => {
137                return Ok((config, tier_max >= 1));
138            }
139            Ok(None) => {
140                log::warn!("No config found!");
141            }
142            Err(e) => {
143                log::error!("error in choose_first_config: {:?}", e);
144            }
145        }
146    }
147
148    Err(hal::UnsupportedBackend)
149}
150
151impl Inner {
152    fn create(
153        egl: Starc<egl::DynamicInstance<egl::EGL1_4>>,
154        display: egl::Display,
155        wsi_library: Option<&libloading::Library>,
156    ) -> Result<Self, hal::UnsupportedBackend> {
157        let version = egl
158            .initialize(display)
159            .map_err(|_| hal::UnsupportedBackend)?;
160        let vendor = egl.query_string(Some(display), egl::VENDOR).unwrap();
161        let display_extensions = egl
162            .query_string(Some(display), egl::EXTENSIONS)
163            .unwrap()
164            .to_string_lossy();
165        log::info!(
166            "Display vendor {:?}, version {:?}, extensions: {:?}",
167            vendor,
168            version,
169            display_extensions
170        );
171
172        if log::max_level() >= log::LevelFilter::Trace {
173            log::trace!("Configurations:");
174            let config_count = egl.get_config_count(display).unwrap();
175            let mut configurations = Vec::with_capacity(config_count);
176            egl.get_configs(display, &mut configurations).unwrap();
177            for &config in configurations.iter() {
178                log::trace!("\tCONFORMANT=0x{:X}, RENDERABLE=0x{:X}, NATIVE_RENDERABLE=0x{:X}, SURFACE_TYPE=0x{:X}",
179                    egl.get_config_attrib(display, config, egl::CONFORMANT).unwrap(),
180                    egl.get_config_attrib(display, config, egl::RENDERABLE_TYPE).unwrap(),
181                    egl.get_config_attrib(display, config, egl::NATIVE_RENDERABLE).unwrap(),
182                    egl.get_config_attrib(display, config, egl::SURFACE_TYPE).unwrap(),
183                );
184            }
185        }
186
187        let (config, supports_native_window) = choose_config(&egl, display)?;
188        egl.bind_api(egl::OPENGL_ES_API).unwrap();
189
190        //TODO: make it so `Device` == EGL Context
191        let mut context_attributes = vec![
192            egl::CONTEXT_CLIENT_VERSION,
193            3, // Request GLES 3.0 or higher
194        ];
195        if cfg!(debug_assertions) && wsi_library.is_none() && !cfg!(target_os = "android") {
196            //TODO: figure out why this is needed
197            context_attributes.push(egl::CONTEXT_OPENGL_DEBUG);
198            context_attributes.push(egl::TRUE as _);
199        }
200        context_attributes.push(egl::NONE as _);
201        let context = match egl.create_context(display, config, None, &context_attributes) {
202            Ok(context) => context,
203            Err(e) => {
204                log::warn!("unable to create GLES 3.x context: {:?}", e);
205                return Err(hal::UnsupportedBackend);
206            }
207        };
208
209        // Testing if context can be binded without surface
210        // and creating dummy pbuffer surface if not.
211        let pbuffer = if version < (1, 5)
212            || !display_extensions.contains("EGL_KHR_surfaceless_context")
213        {
214            let attributes = [egl::WIDTH, 1, egl::HEIGHT, 1, egl::NONE];
215            egl.create_pbuffer_surface(display, config, &attributes)
216                .map(Some)
217                .map_err(|e| {
218                    log::warn!("Error in create_pbuffer_surface: {:?}", e);
219                    hal::UnsupportedBackend
220                })?
221        } else {
222            log::info!("EGL_KHR_surfaceless_context is present. No need to create a dummy pbuffer");
223            None
224        };
225
226        Ok(Self {
227            egl,
228            display,
229            version,
230            supports_native_window,
231            config,
232            context,
233            pbuffer,
234            wl_display: None,
235        })
236    }
237}
238
239impl Drop for Inner {
240    fn drop(&mut self) {
241        if let Err(e) = self.egl.destroy_context(self.display, self.context) {
242            log::warn!("Error in destroy_context: {:?}", e);
243        }
244        if let Err(e) = self.egl.terminate(self.display) {
245            log::warn!("Error in terminate: {:?}", e);
246        }
247    }
248}
249
250impl hal::Instance<crate::Backend> for Instance {
251    fn create(_: &str, _: u32) -> Result<Self, hal::UnsupportedBackend> {
252        let egl = match unsafe { egl::DynamicInstance::<egl::EGL1_4>::load_required() } {
253            Ok(egl) => Starc::new(egl),
254            Err(e) => {
255                log::warn!("Unable to open libEGL.so: {:?}", e);
256                return Err(hal::UnsupportedBackend);
257            }
258        };
259
260        let client_extensions = egl.query_string(None, egl::EXTENSIONS);
261
262        let client_ext_str = match client_extensions {
263            Ok(ext) => ext.to_string_lossy().into_owned(),
264            Err(_) => String::new(),
265        };
266        log::info!("Client extensions: {:?}", client_ext_str);
267
268        let mut wsi_library = None;
269
270        let wayland_library = if client_ext_str.contains(&"EGL_EXT_platform_wayland") {
271            test_wayland_display()
272        } else {
273            None
274        };
275
276        let x11_display_library = if client_ext_str.contains(&"EGL_EXT_platform_x11") {
277            open_x_display()
278        } else {
279            None
280        };
281
282        let display = if let (Some(library), Some(egl)) =
283            (wayland_library, egl.upcast::<egl::EGL1_5>())
284        {
285            log::info!("Using Wayland platform");
286            let display_attributes = [egl::ATTRIB_NONE];
287            wsi_library = Some(library);
288            egl.get_platform_display(
289                EGL_PLATFORM_WAYLAND_KHR,
290                egl::DEFAULT_DISPLAY,
291                &display_attributes,
292            )
293            .unwrap()
294        } else if let (Some((display, library)), Some(egl)) =
295            (x11_display_library, egl.upcast::<egl::EGL1_5>())
296        {
297            log::info!("Using X11 platform");
298            let display_attributes = [egl::ATTRIB_NONE];
299            wsi_library = Some(library);
300            egl.get_platform_display(EGL_PLATFORM_X11_KHR, display.as_ptr(), &display_attributes)
301                .unwrap()
302        } else {
303            log::info!("Using default platform");
304            egl.get_display(egl::DEFAULT_DISPLAY).unwrap()
305        };
306
307        let inner = Inner::create(egl.clone(), display, wsi_library.as_ref())?;
308
309        Ok(Instance {
310            inner: Mutex::new(inner),
311            wsi_library,
312        })
313    }
314
315    fn enumerate_adapters(&self) -> Vec<hal::adapter::Adapter<crate::Backend>> {
316        let inner = self.inner.lock();
317        inner
318            .egl
319            .make_current(
320                inner.display,
321                inner.pbuffer,
322                inner.pbuffer,
323                Some(inner.context),
324            )
325            .unwrap();
326
327        let context = unsafe {
328            glow::Context::from_loader_function(|name| {
329                inner
330                    .egl
331                    .get_proc_address(name)
332                    .map_or(ptr::null(), |p| p as *const _)
333            })
334        };
335        // Create physical device
336        vec![PhysicalDevice::new_adapter(context)]
337    }
338
339    #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))]
340    unsafe fn create_surface(
341        &self,
342        has_handle: &impl raw_window_handle::HasRawWindowHandle,
343    ) -> Result<Surface, w::InitError> {
344        use raw_window_handle::RawWindowHandle as Rwh;
345
346        let mut inner = self.inner.lock();
347        let mut wl_window = None;
348        #[cfg(not(any(target_os = "android", target_os = "macos")))]
349        let (mut temp_xlib_handle, mut temp_xcb_handle);
350        let native_window_ptr = match has_handle.raw_window_handle() {
351            #[cfg(not(any(target_os = "android", target_os = "macos")))]
352            Rwh::Xlib(handle) => {
353                temp_xlib_handle = handle.window;
354                &mut temp_xlib_handle as *mut _ as *mut std::ffi::c_void
355            }
356            #[cfg(not(any(target_os = "android", target_os = "macos")))]
357            Rwh::Xcb(handle) => {
358                temp_xcb_handle = handle.window;
359                &mut temp_xcb_handle as *mut _ as *mut std::ffi::c_void
360            }
361            #[cfg(target_os = "android")]
362            Rwh::Android(handle) => handle.a_native_window as *mut _ as *mut std::ffi::c_void,
363            #[cfg(not(any(target_os = "android", target_os = "macos")))]
364            Rwh::Wayland(handle) => {
365                /* Wayland displays are not sharable between surfaces so if the
366                 * surface we receive from this handle is from a different
367                 * display, we must re-initialize the context.
368                 *
369                 * See gfx-rs/gfx#3545
370                 */
371                if inner
372                    .wl_display
373                    .map(|ptr| ptr != handle.display)
374                    .unwrap_or(true)
375                {
376                    use std::ops::DerefMut;
377                    let display_attributes = [egl::ATTRIB_NONE];
378                    let display = inner
379                        .egl
380                        .upcast::<egl::EGL1_5>()
381                        .unwrap()
382                        .get_platform_display(
383                            EGL_PLATFORM_WAYLAND_KHR,
384                            handle.display,
385                            &display_attributes,
386                        )
387                        .unwrap();
388
389                    let new_inner =
390                        Inner::create(inner.egl.clone(), display, self.wsi_library.as_ref())
391                            .map_err(|_| w::InitError::UnsupportedWindowHandle)?;
392
393                    let old_inner = std::mem::replace(inner.deref_mut(), new_inner);
394                    inner.wl_display = Some(handle.display);
395                    drop(old_inner);
396                }
397
398                let wl_egl_window_create: libloading::Symbol<WlEglWindowCreateFun> = self
399                    .wsi_library
400                    .as_ref()
401                    .expect("unsupported window")
402                    .get(b"wl_egl_window_create")
403                    .unwrap();
404                let result = wl_egl_window_create(handle.surface, 640, 480) as *mut _
405                    as *mut std::ffi::c_void;
406                wl_window = Some(result);
407                result
408            }
409            other => {
410                log::error!("Unsupported window: {:?}", other);
411                return Err(w::InitError::UnsupportedWindowHandle);
412            }
413        };
414
415        let mut attributes = vec![
416            egl::RENDER_BUFFER as usize,
417            if cfg!(target_os = "android") {
418                egl::BACK_BUFFER as usize
419            } else {
420                egl::SINGLE_BUFFER as usize
421            },
422        ];
423        if inner.version >= (1, 5) {
424            // Always enable sRGB in EGL 1.5
425            attributes.push(egl::GL_COLORSPACE as usize);
426            attributes.push(egl::GL_COLORSPACE_SRGB as usize);
427        }
428        attributes.push(egl::ATTRIB_NONE);
429
430        let raw = if let Some(egl) = inner.egl.upcast::<egl::EGL1_5>() {
431            egl.create_platform_window_surface(
432                inner.display,
433                inner.config,
434                native_window_ptr,
435                &attributes,
436            )
437            .map_err(|e| {
438                log::warn!("Error in create_platform_window_surface: {:?}", e);
439                w::InitError::UnsupportedWindowHandle
440            })
441        } else {
442            let attributes_i32: Vec<i32> = attributes.iter().map(|a| (*a as i32).into()).collect();
443            inner
444                .egl
445                .create_window_surface(
446                    inner.display,
447                    inner.config,
448                    native_window_ptr,
449                    Some(&attributes_i32),
450                )
451                .map_err(|e| {
452                    log::warn!("Error in create_platform_window_surface: {:?}", e);
453                    w::InitError::UnsupportedWindowHandle
454                })
455        }?;
456
457        #[cfg(target_os = "android")]
458        {
459            let format = inner
460                .egl
461                .get_config_attrib(inner.display, inner.config, egl::NATIVE_VISUAL_ID)
462                .unwrap();
463
464            let ret = ANativeWindow_setBuffersGeometry(native_window_ptr, 0, 0, format);
465
466            if ret != 0 {
467                log::error!("Error returned from ANativeWindow_setBuffersGeometry");
468                return Err(w::InitError::UnsupportedWindowHandle);
469            }
470        }
471
472        Ok(Surface {
473            egl: inner.egl.clone(),
474            raw,
475            display: inner.display,
476            context: inner.context,
477            presentable: inner.supports_native_window,
478            pbuffer: inner.pbuffer,
479            wl_window,
480            swapchain: None,
481        })
482    }
483
484    unsafe fn destroy_surface(&self, surface: Surface) {
485        let inner = self.inner.lock();
486        inner
487            .egl
488            .destroy_surface(inner.display, surface.raw)
489            .unwrap();
490        if let Some(wl_window) = surface.wl_window {
491            let wl_egl_window_destroy: libloading::Symbol<WlEglWindowDestroyFun> = self
492                .wsi_library
493                .as_ref()
494                .expect("unsupported window")
495                .get(b"wl_egl_window_destroy")
496                .unwrap();
497            wl_egl_window_destroy(wl_window)
498        }
499    }
500
501    unsafe fn create_display_plane_surface(
502        &self,
503        _display_plane: &hal::display::DisplayPlane<crate::Backend>,
504        _plane_stack_index: u32,
505        _transformation: hal::display::SurfaceTransform,
506        _alpha: hal::display::DisplayPlaneAlpha,
507        _image_extent: hal::window::Extent2D,
508    ) -> Result<Surface, hal::display::DisplayPlaneSurfaceError> {
509        unimplemented!();
510    }
511}
512
513#[derive(Debug)]
514pub struct Surface {
515    egl: Starc<egl::DynamicInstance<egl::EGL1_4>>,
516    raw: egl::Surface,
517    display: egl::Display,
518    context: egl::Context,
519    pbuffer: Option<egl::Surface>,
520    presentable: bool,
521    wl_window: Option<*mut raw::c_void>,
522    pub(crate) swapchain: Option<Swapchain>,
523}
524
525unsafe impl Send for Surface {}
526unsafe impl Sync for Surface {}
527
528impl w::PresentationSurface<crate::Backend> for Surface {
529    type SwapchainImage = native::SwapchainImage;
530
531    unsafe fn configure_swapchain(
532        &mut self,
533        device: &crate::Device,
534        config: w::SwapchainConfig,
535    ) -> Result<(), w::SwapchainError> {
536        self.unconfigure_swapchain(device);
537
538        if let Some(window) = self.wl_window {
539            let library = libloading::Library::new("libwayland-egl.so").unwrap();
540            let wl_egl_window_resize: libloading::Symbol<WlEglWindowResizeFun> =
541                library.get(b"wl_egl_window_resize").unwrap();
542            wl_egl_window_resize(
543                window,
544                config.extent.width as i32,
545                config.extent.height as i32,
546                0,
547                0,
548            );
549        }
550
551        let desc = conv::describe_format(config.format).unwrap();
552
553        let gl = &device.share.context;
554        let renderbuffer = gl.create_renderbuffer().unwrap();
555        gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer));
556        gl.renderbuffer_storage(
557            glow::RENDERBUFFER,
558            desc.tex_internal,
559            config.extent.width as _,
560            config.extent.height as _,
561        );
562        let framebuffer = gl.create_framebuffer().unwrap();
563        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer));
564        gl.framebuffer_renderbuffer(
565            glow::READ_FRAMEBUFFER,
566            glow::COLOR_ATTACHMENT0,
567            glow::RENDERBUFFER,
568            Some(renderbuffer),
569        );
570        gl.bind_renderbuffer(glow::RENDERBUFFER, None);
571        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);
572
573        self.swapchain = Some(Swapchain {
574            renderbuffer,
575            framebuffer,
576            extent: config.extent,
577            format: desc.tex_internal,
578            channel: config.format.base_format().1,
579        });
580
581        Ok(())
582    }
583
584    unsafe fn unconfigure_swapchain(&mut self, device: &crate::Device) {
585        let gl = &device.share.context;
586        if let Some(sc) = self.swapchain.take() {
587            gl.delete_renderbuffer(sc.renderbuffer);
588            gl.delete_framebuffer(sc.framebuffer);
589        }
590    }
591
592    unsafe fn acquire_image(
593        &mut self,
594        _timeout_ns: u64,
595    ) -> Result<(Self::SwapchainImage, Option<w::Suboptimal>), w::AcquireError> {
596        let sc = self.swapchain.as_ref().unwrap();
597        let sc_image =
598            native::SwapchainImage::new(sc.renderbuffer, sc.format, sc.extent, sc.channel);
599        Ok((sc_image, None))
600    }
601}
602
603impl w::Surface<crate::Backend> for Surface {
604    fn supports_queue_family(&self, _: &crate::QueueFamily) -> bool {
605        self.presentable
606    }
607
608    fn capabilities(&self, _physical_device: &PhysicalDevice) -> w::SurfaceCapabilities {
609        w::SurfaceCapabilities {
610            present_modes: w::PresentMode::FIFO,                  //TODO
611            composite_alpha_modes: w::CompositeAlphaMode::OPAQUE, //TODO
612            image_count: 2..=2,
613            current_extent: None,
614            extents: w::Extent2D {
615                width: 4,
616                height: 4,
617            }..=w::Extent2D {
618                width: 4096,
619                height: 4096,
620            },
621            max_image_layers: 1,
622            usage: image::Usage::COLOR_ATTACHMENT,
623        }
624    }
625
626    fn supported_formats(
627        &self,
628        _physical_device: &PhysicalDevice,
629    ) -> Option<Vec<hal::format::Format>> {
630        use hal::format::Format;
631        Some(vec![Format::Rgba8Srgb, Format::Bgra8Srgb])
632    }
633}
634
635impl Surface {
636    pub(crate) unsafe fn present(
637        &mut self,
638        _image: native::SwapchainImage,
639        gl: &GlContainer,
640    ) -> Result<Option<w::Suboptimal>, w::PresentError> {
641        let sc = self.swapchain.as_ref().unwrap();
642
643        self.egl
644            .make_current(
645                self.display,
646                Some(self.raw),
647                Some(self.raw),
648                Some(self.context),
649            )
650            .unwrap();
651        gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
652        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer));
653        gl.blit_framebuffer(
654            0,
655            0,
656            sc.extent.width as _,
657            sc.extent.height as _,
658            0,
659            0,
660            sc.extent.width as _,
661            sc.extent.height as _,
662            glow::COLOR_BUFFER_BIT,
663            glow::NEAREST,
664        );
665        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);
666
667        self.egl.swap_buffers(self.display, self.raw).unwrap();
668
669        self.egl
670            .make_current(self.display, self.pbuffer, self.pbuffer, Some(self.context))
671            .unwrap();
672
673        Ok(None)
674    }
675}