diff --git a/src/backend/gl/src/window/egl.rs b/src/backend/gl/src/window/egl.rs index 2efb5d94b09..a17087b31b7 100644 --- a/src/backend/gl/src/window/egl.rs +++ b/src/backend/gl/src/window/egl.rs @@ -3,6 +3,7 @@ use crate::{conv, native, GlContainer, PhysicalDevice, Starc}; use glow::HasContext; use hal::{image, window as w}; +use parking_lot::Mutex; use std::{os::raw, ptr}; #[derive(Debug)] @@ -17,68 +18,82 @@ pub struct Swapchain { #[derive(Debug)] pub struct Instance { + wsi_library: Option, + inner: Mutex, +} + +#[derive(Debug)] +pub struct Inner { egl: Starc, - display: egl::Display, version: (i32, i32), + supports_native_window: bool, + display: egl::Display, config: egl::Config, context: egl::Context, - supports_native_window: bool, - wsi_library: Option, + wl_display: Option<*mut raw::c_void>, } unsafe impl Send for Instance {} unsafe impl Sync for Instance {} +const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8; +const EGL_PLATFORM_X11_KHR: u32 = 0x31D5; + type XOpenDisplayFun = unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void; -impl hal::Instance for Instance { - fn create(_: &str, _: u32) -> Result { - let egl = match unsafe { egl::DynamicInstance::load() } { - Ok(egl) => Starc::new(egl), - Err(e) => { - log::warn!("Unable to open libEGL.so: {:?}", e); - return Err(hal::UnsupportedBackend); - } - }; +type WlDisplayConnectFun = + unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void; - let client_extensions = egl - .query_string(None, egl::EXTENSIONS) - .map_err(|_| hal::UnsupportedBackend)? - .to_string_lossy(); - log::info!("Client extensions: {:?}", client_extensions); - let client_ext_list = client_extensions.split_whitespace().collect::>(); +type WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void); + +type WlEglWindowCreateFun = unsafe extern "system" fn( + surface: *const raw::c_void, + width: raw::c_int, + height: raw::c_int, +) -> *mut raw::c_void; + +type WlEglWindowResizeFun = unsafe extern "system" fn( + window: *const raw::c_void, + width: raw::c_int, + height: raw::c_int, + dx: raw::c_int, + dy: raw::c_int, +); + +type WlEglWindowDestroyFun = unsafe extern "system" fn(window: *const raw::c_void); + +fn open_x_display() -> Option<(ptr::NonNull, libloading::Library)> { + log::info!("Loading X11 library to get the current display"); + let library = libloading::Library::new("libX11.so").ok()?; + let func: libloading::Symbol = + unsafe { library.get(b"XOpenDisplay").unwrap() }; + let result = unsafe { func(ptr::null()) }; + ptr::NonNull::new(result).map(|ptr| (ptr, library)) +} - let mut wsi_library = None; - let x11_display = if client_ext_list.contains(&"EGL_EXT_platform_x11") { - log::info!("Loading X11 library to get the current display"); - if let Ok(library) = libloading::Library::new("libX11.so") { - let func: libloading::Symbol = - unsafe { library.get(b"XOpenDisplay").unwrap() }; - let result = unsafe { func(ptr::null()) }; - wsi_library = Some(library); - ptr::NonNull::new(result) - } else { - None - } - } else { - None - }; - let display = if let Some(x11_display) = x11_display { - log::info!("Using X11 platform"); - const EGL_PLATFORM_X11_KHR: u32 = 0x31D5; - let display_attributes = [egl::ATTRIB_NONE]; - egl.get_platform_display( - EGL_PLATFORM_X11_KHR, - x11_display.as_ptr(), - &display_attributes, - ) - .unwrap() - } else { - log::info!("Using default platform"); - egl.get_display(egl::DEFAULT_DISPLAY).unwrap() - }; +fn test_wayland_display() -> Option { + /* We try to connect and disconnect here to simply ensure there + * is an active wayland display available. + */ + log::info!("Loading Wayland library to get the current display"); + let client_library = libloading::Library::new("libwayland-client.so").ok()?; + let wl_display_connect: libloading::Symbol = + unsafe { client_library.get(b"wl_display_connect").unwrap() }; + let wl_display_disconnect: libloading::Symbol = + unsafe { client_library.get(b"wl_display_disconnect").unwrap() }; + let display = ptr::NonNull::new(unsafe { wl_display_connect(ptr::null()) })?; + unsafe { wl_display_disconnect(display.as_ptr()) }; + let library = libloading::Library::new("libwayland-egl.so").ok()?; + Some(library) +} +impl Inner { + fn create( + egl: Starc, + display: egl::Display, + wsi_library: Option<&libloading::Library>, + ) -> Result { let version = egl .initialize(display) .map_err(|_| hal::UnsupportedBackend)?; @@ -177,24 +192,102 @@ impl hal::Instance for Instance { } }; - Ok(Instance { + Ok(Self { egl, display, version, + supports_native_window, config, context, - supports_native_window, + wl_display: None, + }) + } +} + +impl Drop for Inner { + fn drop(&mut self) { + if let Err(e) = self.egl.destroy_context(self.display, self.context) { + log::warn!("Error in destroy_context: {:?}", e); + } + if let Err(e) = self.egl.terminate(self.display) { + log::warn!("Error in terminate: {:?}", e); + } + } +} + +impl hal::Instance for Instance { + fn create(_: &str, _: u32) -> Result { + let egl = match unsafe { egl::DynamicInstance::load() } { + Ok(egl) => Starc::new(egl), + Err(e) => { + log::warn!("Unable to open libEGL.so: {:?}", e); + return Err(hal::UnsupportedBackend); + } + }; + + let client_extensions = egl + .query_string(None, egl::EXTENSIONS) + .map_err(|_| hal::UnsupportedBackend)? + .to_string_lossy(); + log::info!("Client extensions: {:?}", client_extensions); + let client_ext_list = client_extensions.split_whitespace().collect::>(); + + let mut wsi_library = None; + + let wayland_display = if client_ext_list.contains(&"EGL_EXT_platform_wayland") { + test_wayland_display() + } else { + None + }; + + let x11_display = if client_ext_list.contains(&"EGL_EXT_platform_x11") { + open_x_display() + } else { + None + }; + + let display = if let Some(library) = wayland_display { + log::info!("Using Wayland platform"); + let display_attributes = [egl::ATTRIB_NONE]; + wsi_library = Some(library); + egl.get_platform_display( + EGL_PLATFORM_WAYLAND_KHR, + egl::DEFAULT_DISPLAY, + &display_attributes, + ) + .unwrap() + } else if let Some((x11_display, library)) = x11_display { + log::info!("Using X11 platform"); + let display_attributes = [egl::ATTRIB_NONE]; + wsi_library = Some(library); + egl.get_platform_display( + EGL_PLATFORM_X11_KHR, + x11_display.as_ptr(), + &display_attributes, + ) + .unwrap() + } else { + log::info!("Using default platform"); + egl.get_display(egl::DEFAULT_DISPLAY).unwrap() + }; + + let inner = Inner::create(egl.clone(), display, wsi_library.as_ref())?; + + Ok(Instance { + inner: Mutex::new(inner), wsi_library, }) } fn enumerate_adapters(&self) -> Vec> { - self.egl - .make_current(self.display, None, None, Some(self.context)) + let inner = self.inner.lock(); + inner + .egl + .make_current(inner.display, None, None, Some(inner.context)) .unwrap(); let context = unsafe { glow::Context::from_loader_function(|name| { - self.egl.get_proc_address(name).unwrap() as *const _ + inner.egl.get_proc_address(name).unwrap() as *const _ }) }; // Create physical device @@ -206,6 +299,8 @@ impl hal::Instance for Instance { has_handle: &impl raw_window_handle::HasRawWindowHandle, ) -> Result { use raw_window_handle::RawWindowHandle as Rwh; + + let mut inner = self.inner.lock(); let mut native_window = match has_handle.raw_window_handle() { #[cfg(not(target_os = "android"))] Rwh::Xlib(handle) => handle.window, @@ -213,28 +308,83 @@ impl hal::Instance for Instance { Rwh::Xcb(handle) => handle.window as _, #[cfg(target_os = "android")] Rwh::Android(handle) => handle.a_native_window, + #[cfg(not(target_os = "android"))] + Rwh::Wayland(handle) => { + /* Wayland displays are not sharable between surfaces so if the + * surface we receive from this handle is from a different + * display, we must re-initialize the context. + * + * See gfx-rs/gfx#3545 + */ + if inner + .wl_display + .map(|ptr| ptr != handle.display) + .unwrap_or(true) + { + use std::ops::DerefMut; + let display_attributes = [egl::ATTRIB_NONE]; + let display = inner + .egl + .get_platform_display( + EGL_PLATFORM_WAYLAND_KHR, + handle.display, + &display_attributes, + ) + .unwrap(); + + let new_inner = + Inner::create(inner.egl.clone(), display, self.wsi_library.as_ref()) + .map_err(|_| w::InitError::UnsupportedWindowHandle)?; + + let old_inner = std::mem::replace(inner.deref_mut(), new_inner); + inner.wl_display = Some(handle.display); + drop(old_inner); + } + + let window = { + let wl_egl_window_create: libloading::Symbol = self + .wsi_library + .as_ref() + .expect("unsupported window") + .get(b"wl_egl_window_create") + .unwrap(); + let result = wl_egl_window_create(handle.surface, 640, 480); + ptr::NonNull::new(result) + }; + window.expect("unsupported window").as_ptr() as _ + } other => panic!("Unsupported window: {:?}", other), }; - let attributes = [ - egl::RENDER_BUFFER as usize, - egl::SINGLE_BUFFER as usize, - // Always enable sRGB - egl::GL_COLORSPACE as usize, - egl::GL_COLORSPACE_SRGB as usize, - egl::ATTRIB_NONE, - ]; - match self.egl.create_platform_window_surface( - self.display, - self.config, - &mut native_window as *mut _ as *mut _, + let mut attributes = vec![egl::RENDER_BUFFER as usize, egl::SINGLE_BUFFER as usize]; + if inner.version >= (1, 5) { + // Always enable sRGB in EGL 1.5 + attributes.push(egl::GL_COLORSPACE as usize); + attributes.push(egl::GL_COLORSPACE_SRGB as usize); + } + attributes.push(egl::ATTRIB_NONE); + + let native_window_ptr = match has_handle.raw_window_handle() { + #[cfg(not(target_os = "android"))] + Rwh::Wayland(_) => native_window as *mut raw::c_void, + _ => &mut native_window as *mut _ as *mut _, + }; + match inner.egl.create_platform_window_surface( + inner.display, + inner.config, + native_window_ptr, &attributes, ) { Ok(raw) => Ok(Surface { - egl: self.egl.clone(), + egl: inner.egl.clone(), raw, - display: self.display, - context: self.context, - presentable: self.supports_native_window, + display: inner.display, + context: inner.context, + presentable: inner.supports_native_window, + wl_window: match has_handle.raw_window_handle() { + #[cfg(not(target_os = "android"))] + Rwh::Wayland(_) => Some(native_window_ptr), + _ => None, + }, swapchain: None, }), Err(e) => { @@ -245,17 +395,19 @@ impl hal::Instance for Instance { } unsafe fn destroy_surface(&self, surface: Surface) { - self.egl.destroy_surface(self.display, surface.raw).unwrap(); - } -} - -impl Drop for Instance { - fn drop(&mut self) { - if let Err(e) = self.egl.destroy_context(self.display, self.context) { - log::warn!("Error in destroy_context: {:?}", e); - } - if let Err(e) = self.egl.terminate(self.display) { - log::warn!("Error in terminate: {:?}", e); + let inner = self.inner.lock(); + inner + .egl + .destroy_surface(inner.display, surface.raw) + .unwrap(); + if let Some(wl_window) = surface.wl_window { + let wl_egl_window_destroy: libloading::Symbol = self + .wsi_library + .as_ref() + .expect("unsupported window") + .get(b"wl_egl_window_destroy") + .unwrap(); + wl_egl_window_destroy(wl_window) } } } @@ -267,6 +419,7 @@ pub struct Surface { display: egl::Display, context: egl::Context, presentable: bool, + wl_window: Option<*mut raw::c_void>, pub(crate) swapchain: Option, } @@ -283,6 +436,19 @@ impl w::PresentationSurface for Surface { ) -> Result<(), w::SwapchainError> { self.unconfigure_swapchain(device); + if let Some(window) = self.wl_window { + let library = libloading::Library::new("libwayland-egl.so").unwrap(); + let wl_egl_window_resize: libloading::Symbol = + library.get(b"wl_egl_window_resize").unwrap(); + wl_egl_window_resize( + window, + config.extent.width as i32, + config.extent.height as i32, + 0, + 0, + ); + } + let desc = conv::describe_format(config.format).unwrap(); let gl = &device.share.context;