diff --git a/build.rs b/build.rs index 1688225..650bab6 100644 --- a/build.rs +++ b/build.rs @@ -20,6 +20,7 @@ fn gl_generate() { "EGL_EXT_platform_base", "EGL_EXT_platform_x11", "EGL_EXT_platform_wayland", + "EGL_KHR_platform_gbm", "EGL_MESA_platform_gbm", "EGL_WL_bind_wayland_display", "EGL_KHR_image_base", diff --git a/src/backend/egl/display.rs b/src/backend/egl/display.rs index 0f585e7..dd59665 100644 --- a/src/backend/egl/display.rs +++ b/src/backend/egl/display.rs @@ -2,10 +2,12 @@ use std::collections::HashSet; use std::ffi::CStr; +use std::fmt; use std::mem::MaybeUninit; use std::ops::Deref; use std::sync::Arc; +use libc::c_void; use nix::libc::c_int; #[cfg(all(feature = "use_system_lib", feature = "wayland_frontend"))] use wayland_server::{protocol::wl_buffer::WlBuffer, Display}; @@ -61,6 +63,85 @@ pub struct EGLDisplay { logger: slog::Logger, } +fn select_platform_display( + native: &N, + log: &::slog::Logger, +) -> Result<*const c_void, Error> { + let dp_extensions = unsafe { + let p = wrap_egl_call(|| ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32)) + .map_err(Error::InitFailed)?; //TODO EGL_EXT_client_extensions not supported + + // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise + // `eglQueryString` returns an error + if p.is_null() { + return Err(Error::EglExtensionNotSupported(&["EGL_EXT_platform_base"])); + } else { + let p = CStr::from_ptr(p); + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + } + }; + debug!(log, "Supported EGL client extensions: {:?}", dp_extensions); + + for platform in native.supported_platforms() { + debug!(log, "Trying EGL platform: {}", platform.platform_name); + + let log = log.new(o!("platform" => format!("{:?}", platform))); + + let missing_extensions = platform + .required_extensions + .iter() + .filter(|ext| !dp_extensions.iter().any(|x| x == *ext)) + .collect::>(); + + if !missing_extensions.is_empty() { + info!( + log, + "Skipping EGL platform because one or more required extensions are not supported. Missing extensions: {:?}", missing_extensions + ); + continue; + } + + let display = unsafe { + wrap_egl_call(|| { + ffi::egl::GetPlatformDisplayEXT( + platform.platform, + platform.native_display, + platform.attrib_list.as_ptr(), + ) + }) + .map_err(Error::DisplayCreationError) + }; + + let display = match display { + Ok(display) => { + if display == ffi::egl::NO_DISPLAY { + info!(log, "Skipping platform because the display is not supported"); + continue; + } + + display + } + Err(err) => { + info!( + log, + "Skipping platform because of an display creation error: {:?}", err + ); + continue; + } + }; + + info!( + log, + "Successfully selected EGL platform: {}", platform.platform_name + ); + return Ok(display); + } + + crit!(log, "Unable to find suitable EGL platform"); + Err(Error::DisplayNotSupported) +} + impl EGLDisplay { /// Create a new [`EGLDisplay`] from a given [`NativeDisplay`](native::NativeDisplay) pub fn new(native: &N, logger: L) -> Result @@ -71,39 +152,8 @@ impl EGLDisplay { let log = crate::slog_or_fallback(logger.into()).new(o!("smithay_module" => "backend_egl")); ffi::make_sure_egl_is_loaded(); - // the first step is to query the list of extensions without any display, if supported - let dp_extensions = unsafe { - let p = - wrap_egl_call(|| ffi::egl::QueryString(ffi::egl::NO_DISPLAY, ffi::egl::EXTENSIONS as i32)) - .map_err(Error::InitFailed)?; //TODO EGL_EXT_client_extensions not supported - - // this possibility is available only with EGL 1.5 or EGL_EXT_platform_base, otherwise - // `eglQueryString` returns an error - if p.is_null() { - return Err(Error::EglExtensionNotSupported(&["EGL_EXT_platform_base"])); - } else { - let p = CStr::from_ptr(p); - let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); - list.split(' ').map(|e| e.to_string()).collect::>() - } - }; - debug!(log, "Supported EGL client extensions: {:?}", dp_extensions); - - for ext in native.required_extensions() { - if !dp_extensions.iter().any(|x| x == ext) { - return Err(Error::EglExtensionNotSupported(native.required_extensions())); - } - } - - let (platform, native_ptr, attributes) = native.platform_display(); // we create an EGLDisplay - let display = unsafe { - wrap_egl_call(|| ffi::egl::GetPlatformDisplayEXT(platform, native_ptr, attributes.as_ptr())) - .map_err(Error::DisplayCreationError)? - }; - if display == ffi::egl::NO_DISPLAY { - return Err(Error::DisplayNotSupported); - } + let display = select_platform_display(native, &log)?; // We can then query the egl api version let egl_version = { diff --git a/src/backend/egl/native.rs b/src/backend/egl/native.rs index 37c58f2..7a4fca0 100644 --- a/src/backend/egl/native.rs +++ b/src/backend/egl/native.rs @@ -4,7 +4,7 @@ use super::{display::EGLDisplayHandle, ffi, wrap_egl_call, SwapBuffersError}; use nix::libc::{c_int, c_void}; #[cfg(feature = "backend_gbm")] use std::os::unix::io::AsRawFd; -use std::sync::Arc; +use std::{fmt::Debug, marker::PhantomData, sync::Arc}; #[cfg(feature = "backend_winit")] use wayland_egl as wegl; @@ -16,12 +16,35 @@ use winit::window::Window as WinitWindow; #[cfg(feature = "backend_gbm")] use gbm::{AsRaw, Device as GbmDevice}; +/// Type, Raw handle and attributes used to call [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) +pub struct EGLPlatform<'a> { + /// Required extensions to use this platform + pub required_extensions: &'static [&'static str], + /// Human readable name of the platform + pub platform_name: &'static str, + /// Platform type used to call [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) + pub platform: ffi::egl::types::EGLenum, + /// Raw native display handle used to call [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) + pub native_display: *mut c_void, + /// Attributes used to call [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) + pub attrib_list: Vec, + _phantom: PhantomData<&'a c_void>, +} + +impl<'a> Debug for EGLPlatform<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EGLPlatform") + .field("platform_name", &self.platform_name) + .field("required_extensions", &self.required_extensions) + .finish() + } +} + /// Trait describing platform specific functionality to create a valid `EGLDisplay` using the `EGL_EXT_platform_base`(https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) extension. pub trait EGLNativeDisplay: Send { - /// Required extensions to use this platform - fn required_extensions(&self) -> &'static [&'static str]; - /// Type, Raw handle and attributes used to call [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) - fn platform_display(&self) -> (ffi::egl::types::EGLenum, *mut c_void, Vec); + /// List of supported platforms that can be used to create a display using [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) + fn supported_platforms<'a>(&'a self) -> Vec>; + /// Type of surfaces created fn surface_type(&self) -> ffi::EGLint { ffi::egl::WINDOW_BIT as ffi::EGLint @@ -30,43 +53,49 @@ pub trait EGLNativeDisplay: Send { #[cfg(feature = "backend_gbm")] impl EGLNativeDisplay for GbmDevice { - fn required_extensions(&self) -> &'static [&'static str] { - &["EGL_MESA_platform_gbm"] - } - fn platform_display(&self) -> (ffi::egl::types::EGLenum, *mut c_void, Vec) { - ( - ffi::egl::PLATFORM_GBM_MESA, - self.as_raw() as *mut _, - vec![ffi::egl::NONE as ffi::EGLint], - ) + fn supported_platforms<'a>(&'a self) -> Vec> { + vec![ + EGLPlatform { + required_extensions: &["EGL_KHR_platform_gbm"], + platform_name: "PLATFORM_GBM_KHR", + platform: ffi::egl::PLATFORM_GBM_KHR, + native_display: self.as_raw() as *mut _, + attrib_list: vec![ffi::egl::NONE as ffi::EGLint], + _phantom: PhantomData, + }, + EGLPlatform { + required_extensions: &["EGL_MESA_platform_gbm"], + platform_name: "PLATFORM_GBM_MESA", + platform: ffi::egl::PLATFORM_GBM_MESA, + native_display: self.as_raw() as *mut _, + attrib_list: vec![ffi::egl::NONE as ffi::EGLint], + _phantom: PhantomData, + }, + ] } } #[cfg(feature = "backend_winit")] impl EGLNativeDisplay for WinitWindow { - fn required_extensions(&self) -> &'static [&'static str] { - if self.wayland_display().is_some() { - &["EGL_EXT_platform_wayland"] - } else if self.xlib_display().is_some() { - &["EGL_EXT_platform_x11"] - } else { - unreachable!("No backends for winit other then Wayland and X11 are supported") - } - } - - fn platform_display(&self) -> (ffi::egl::types::EGLenum, *mut c_void, Vec) { + fn supported_platforms<'a>(&'a self) -> Vec> { if let Some(display) = self.wayland_display() { - ( - ffi::egl::PLATFORM_WAYLAND_EXT, - display as *mut _, - vec![ffi::egl::NONE as ffi::EGLint], - ) + vec![EGLPlatform { + required_extensions: &["EGL_EXT_platform_wayland"], + platform_name: "PLATFORM_WAYLAND_EXT", + platform: ffi::egl::PLATFORM_WAYLAND_EXT, + native_display: display as *mut _, + attrib_list: vec![ffi::egl::NONE as ffi::EGLint], + _phantom: PhantomData, + }] } else if let Some(display) = self.xlib_display() { - ( - ffi::egl::PLATFORM_X11_EXT, - display as *mut _, - vec![ffi::egl::NONE as ffi::EGLint], - ) + vec![EGLPlatform { + required_extensions: &["EGL_EXT_platform_x11"], + platform_name: "PLATFORM_X11_EXT", + platform: ffi::egl::PLATFORM_X11_EXT, + native_display: display as *mut _, + attrib_list: vec![ffi::egl::NONE as ffi::EGLint], + _phantom: PhantomData, + }] } else { unreachable!("No backends for winit other then Wayland and X11 are supported") }