diff --git a/Cargo.toml b/Cargo.toml index 0d8ad815..fa2a0c04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ x11-dl = { version = "2.21.0" } calloop = "0.14.4" percent-encoding = "2.3.2" bytemuck = { version = "1.25.0", features = ["extern_crate_alloc"] } +libloading = "0.8.9" # Libloading 0.9 is out but its MSRV is 1.88 [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.62.2", features = [ diff --git a/src/gl.rs b/src/gl.rs index 5acae154..072d4a7d 100644 --- a/src/gl.rs +++ b/src/gl.rs @@ -1,7 +1,7 @@ use std::ffi::{c_void, CStr, CString}; use std::marker::PhantomData; -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct GlConfig { pub version: (u8, u8), pub profile: Profile, diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index d808362e..89ed90fd 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -341,7 +341,7 @@ impl WindowImpl for BaseviewWindow { window.register_drag_drop(drop_target.as_interface())?; #[cfg(feature = "opengl")] - if let Some(gl_config) = self.gl_config.clone() { + if let Some(gl_config) = self.gl_config { let gl_context = gl::GlContextInner::create(window, gl_config)?; let Ok(()) = self.window_state.gl_context.set(Rc::new(gl_context)) else { diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 98fdd21c..64489513 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -58,6 +58,8 @@ pub enum PlatformError { #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), #[cfg(feature = "opengl")] + EGl(crate::wrappers::egl::EglError), + #[cfg(feature = "opengl")] Gl(super::gl::CreationFailedError), } @@ -87,6 +89,8 @@ impl Display for PlatformError { PlatformError::XLib(e) => e.fmt(f), #[cfg(feature = "opengl")] PlatformError::Gl(e) => e.fmt(f), + #[cfg(feature = "opengl")] + PlatformError::EGl(e) => e.fmt(f), } } } @@ -100,6 +104,8 @@ impl std::error::Error for PlatformError { PlatformError::Handler(e) => Some(e.source()), #[cfg(feature = "opengl")] PlatformError::XLib(e) => Some(e), + #[cfg(feature = "opengl")] + PlatformError::EGl(e) => Some(e), _ => None, } } @@ -221,6 +227,13 @@ impl From for PlatformError { } } +#[cfg(feature = "opengl")] +impl From for PlatformError { + fn from(value: crate::wrappers::egl::EglError) -> Self { + Self::EGl(value) + } +} + pub trait CookieExt { fn check_warn(self); } diff --git a/src/platform/x11/gl.rs b/src/platform/x11/gl.rs index d6557569..fe2f4dfb 100644 --- a/src/platform/x11/gl.rs +++ b/src/platform/x11/gl.rs @@ -1,13 +1,20 @@ use super::*; use crate::gl::*; use crate::wrappers::glx::*; -use crate::wrappers::xlib::{XErrorHandler, XLibError}; +use crate::wrappers::xlib::XLibError; +use std::error::Error; +use crate::platform::gl::egl::EglGlContext; +use crate::platform::gl::glx::GlxGlContext; use crate::platform::x11::xcb_window::XcbWindow; -use std::ffi::{c_ulong, c_void, CStr}; +use crate::wrappers::egl::{EglConfig, EglDisplay, EglError, EglVersion, MissingSymbolError}; +use std::ffi::{c_void, CStr}; use std::rc::Rc; use x11_dl::error::OpenError; -use x11_dl::glx::GLXContext; +use x11rb::protocol::xproto::Visualid; + +mod egl; +mod glx; #[derive(Debug)] pub enum CreationFailedError { @@ -18,6 +25,13 @@ pub enum CreationFailedError { ContextCreationFailed, X11Error(XLibError), OpenError(OpenError), + EGLLoadError(libloading::Error), + EGLMissingSymbol(MissingSymbolError), + EglError(EglError), + EglNoDisplay, + EglUnsupportedVersion(EglVersion), + EglUnknownVisualId(Visualid), + EglInvalidVisualId(i32, TryFromIntError), } impl Display for CreationFailedError { @@ -34,24 +48,48 @@ impl Display for CreationFailedError { CreationFailedError::ContextCreationFailed => f.write_str("Faile to create GL context"), CreationFailedError::X11Error(e) => e.fmt(f), CreationFailedError::OpenError(e) => e.fmt(f), + CreationFailedError::EGLLoadError(e) => { + write!(f, "Could not load EGL library: {e}, {:?}", e.source()) + } + CreationFailedError::EGLMissingSymbol(e) => e.fmt(f), + CreationFailedError::EglError(e) => e.fmt(f), + CreationFailedError::EglNoDisplay => f.write_str("EGL returned no valid display"), + CreationFailedError::EglUnsupportedVersion(e) => { + write!(f, "Unsupported EGL version: {}.{} (EGL 1.5 is required)", e.major, e.minor) + } + CreationFailedError::EglInvalidVisualId(id, e) => { + write!(f, "Invalid Visual ID ({id}) returned by EGL: {e}") + } + CreationFailedError::EglUnknownVisualId(id) => { + write!(f, "Unknown Visual ID returned by EGL: {id}") + } } } } +impl From for CreationFailedError { + fn from(err: EglError) -> Self { + CreationFailedError::EglError(err) + } +} + pub type GlContext = Rc; -pub struct GlContextInner { - glx: Glx, - window: NonZeroU32, - connection: Rc, - context: GLXContext, +pub enum GlContextInner { + Glx(GlxGlContext), + Egl(EglGlContext), } /// The frame buffer configuration along with the general OpenGL configuration to somewhat minimize /// misuse. pub struct FbConfig { gl_config: GlConfig, - fb_config: GlxFbConfig, + fb_config: FbConfigInner, +} + +enum FbConfigInner { + Glx { glx: Glx, config: GlxFbConfig }, + Egl { display: EglDisplay, config: EglConfig }, } /// The configuration a window should be created with after calling @@ -71,99 +109,60 @@ impl GlContextInner { /// /// Use [Self::get_fb_config_and_visual] to create both of these things. pub fn create( - window: &XcbWindow, connection: Rc, config: FbConfig, + window: &XcbWindow, connection: &Rc, fb_config: FbConfig, ) -> Result> { - let glx = Glx::open()?; - - let xlib_connection = connection.conn.xlib_connection(); - - XErrorHandler::handle(xlib_connection, |error_handler| { - let Some(create_context) = glx.get_glx_create_context_attribs_arb() else { - return Err(CreationFailedError::GetProcAddressFailed.into()); + let inner = + match fb_config.fb_config { + FbConfigInner::Glx { glx, config } => GlContextInner::Glx(GlxGlContext::create( + window, + connection, + fb_config.gl_config, + config, + glx, + )?), + FbConfigInner::Egl { display, config } => GlContextInner::Egl( + EglGlContext::create(window, &fb_config.gl_config, config, display)?, + ), }; - let context = create_context.call( - xlib_connection, - &config.gl_config, - config.fb_config, - error_handler, - )?; - - Ok(Rc::new(GlContextInner { - glx, - window: window.id(), - connection: Rc::clone(&connection), - context, - })) - }) + Ok(Rc::new(inner)) } /// Find a matching framebuffer config and window visual for the given OpenGL configuration. /// This needs to be passed to [Self::create] along with a handle to a window that was created /// using the visual also returned from this function. pub fn get_fb_config_and_visual( - connection: &X11Connection, config: GlConfig, + connection: &Rc, config: GlConfig, ) -> Result<(FbConfig, WindowConfig)> { - let glx = Glx::open()?; - - let xlib_connection = connection.conn.xlib_connection(); - - XErrorHandler::handle(xlib_connection, |error_handler| { - let fb_config = glx.choose_best_fb_config(xlib_connection, &config, error_handler)?; - - // Now that we have a matching framebuffer config, we need to know which visual matches - // this config so the window is compatible with the OpenGL context we're about to create - let visual = - glx.get_visual_from_fb_config(xlib_connection, fb_config, error_handler)?; - - Ok(( - FbConfig { fb_config, gl_config: config }, - WindowConfig { depth: visual.depth as u8, visual: visual.visualid as u32 }, - )) - }) + EglGlContext::get_fb_config_and_visual(connection, &config) + .or_else(|_| GlxGlContext::get_fb_config_and_visual(connection, &config)) } pub unsafe fn make_current(&self) -> Result<()> { - XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { - self.glx.make_current( - self.connection.conn.xlib_connection(), - self.window_id(), - self.context, - error_handler, - ) - }) + match self { + GlContextInner::Glx(glx) => glx.make_current(), + GlContextInner::Egl(egl) => egl.make_current(), + } } pub unsafe fn make_not_current(&self) -> Result<()> { - XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { - self.glx.clear_current(self.connection.conn.xlib_connection(), error_handler) - }) - } - - fn window_id(&self) -> c_ulong { - self.window.get().into() + match self { + GlContextInner::Glx(glx) => glx.make_not_current(), + GlContextInner::Egl(egl) => egl.make_not_current(), + } } pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { - match self.glx.get_proc_address(symbol) { - Some(ptr) => ptr.as_ptr(), - None => std::ptr::null(), + match self { + GlContextInner::Glx(glx) => glx.get_proc_address(symbol), + GlContextInner::Egl(egl) => egl.get_proc_address(symbol), } } pub fn swap_buffers(&self) -> Result<()> { - XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { - self.glx.swap_buffers( - self.connection.conn.xlib_connection(), - self.window_id(), - error_handler, - ) - }) - } -} - -impl Drop for GlContextInner { - fn drop(&mut self) { - unsafe { self.glx.destroy_context(self.connection.conn.xlib_connection(), self.context) } + match self { + GlContextInner::Glx(glx) => glx.swap_buffers(), + GlContextInner::Egl(egl) => egl.swap_buffers(), + } } } diff --git a/src/platform/x11/gl/egl.rs b/src/platform/x11/gl/egl.rs new file mode 100644 index 00000000..f00514a0 --- /dev/null +++ b/src/platform/x11/gl/egl.rs @@ -0,0 +1,77 @@ +use crate::gl::GlConfig; +use crate::platform::gl::{CreationFailedError, FbConfig, FbConfigInner, WindowConfig}; +use crate::platform::x11::xcb_window::XcbWindow; +use crate::platform::{PlatformError, X11Connection}; +use crate::wrappers::egl::{Egl, EglConfig, EglContext, EglDisplay, EglSurface}; +use std::ffi::{c_void, CStr}; +use std::rc::Rc; +use x11rb::protocol::xproto::Visualid; + +pub struct EglGlContext { + surface: EglSurface, + context: EglContext, +} + +impl EglGlContext { + pub(crate) fn create( + window: &XcbWindow, gl_config: &GlConfig, egl_config: EglConfig, display: EglDisplay, + ) -> Result { + let surface = display.create_surface(egl_config, window.id().get(), gl_config)?; + let context = display + .egl() + .with_opengl(|bound| display.create_context(egl_config, bound, gl_config))??; + + Ok(Self { surface, context }) + } +} + +impl EglGlContext { + pub fn get_fb_config_and_visual( + connection: &Rc, gl_config: &GlConfig, + ) -> Result<(FbConfig, WindowConfig), PlatformError> { + let egl = Egl::open()?; + let display = egl.create_display(connection)?; + + let config = display.choose_config(gl_config)?.ok_or(CreationFailedError::EglNoDisplay)?; + let visual = config.get_visual_id(&display)?; + + let depth = Self::find_visual_depth_for_id(connection, visual) + .ok_or(CreationFailedError::EglUnknownVisualId(visual))?; + + let window_config = WindowConfig { depth, visual }; + let fb_config = + FbConfig { gl_config: *gl_config, fb_config: FbConfigInner::Egl { display, config } }; + + Ok((fb_config, window_config)) + } + + fn find_visual_depth_for_id(connection: &X11Connection, visual_id: Visualid) -> Option { + connection + .default_screen() + .allowed_depths + .iter() + .find(|d| d.visuals.iter().any(|v| v.visual_id == visual_id)) + .map(|d| d.depth) + } + + pub fn make_current(&self) -> Result<(), PlatformError> { + self.context.make_current(&self.surface)?; + + Ok(()) + } + + pub fn make_not_current(&self) -> Result<(), PlatformError> { + self.surface.display().egl().with_opengl(|gl| self.context.make_not_current(gl))??; + + Ok(()) + } + + pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { + self.surface.display().egl().get_proc_address(symbol) + } + + pub fn swap_buffers(&self) -> Result<(), PlatformError> { + self.surface.swap_buffers()?; + Ok(()) + } +} diff --git a/src/platform/x11/gl/glx.rs b/src/platform/x11/gl/glx.rs new file mode 100644 index 00000000..6eb68b38 --- /dev/null +++ b/src/platform/x11/gl/glx.rs @@ -0,0 +1,107 @@ +use super::*; +use crate::gl::GlConfig; +use crate::platform::gl::CreationFailedError; +use crate::platform::x11::xcb_window::XcbWindow; +use crate::platform::X11Connection; +use crate::wrappers::glx::{Glx, GlxFbConfig}; +use crate::wrappers::xlib::XErrorHandler; +use std::ffi::{c_ulong, c_void, CStr}; +use std::num::NonZeroU32; +use std::rc::Rc; +use x11_dl::glx::GLXContext; + +pub struct GlxGlContext { + glx: Glx, + window: NonZeroU32, + connection: Rc, + context: GLXContext, +} + +impl GlxGlContext { + pub fn create( + window: &XcbWindow, connection: &Rc, gl_config: GlConfig, + fb_config: GlxFbConfig, glx: Glx, + ) -> Result { + let xlib_connection = connection.conn.xlib_connection(); + + XErrorHandler::handle(xlib_connection, |error_handler| { + let Some(create_context) = glx.get_glx_create_context_attribs_arb() else { + return Err(CreationFailedError::GetProcAddressFailed.into()); + }; + + let context = + create_context.call(xlib_connection, &gl_config, fb_config, error_handler)?; + + Ok(Self { glx, window: window.id(), connection: Rc::clone(connection), context }) + }) + } + + pub fn get_fb_config_and_visual( + connection: &X11Connection, config: &GlConfig, + ) -> Result<(FbConfig, WindowConfig)> { + let glx = Glx::open()?; + + let xlib_connection = connection.conn.xlib_connection(); + + XErrorHandler::handle(xlib_connection, |error_handler| { + let fb_config = glx.choose_best_fb_config(xlib_connection, config, error_handler)?; + + // Now that we have a matching framebuffer config, we need to know which visual matches + // this config so the window is compatible with the OpenGL context we're about to create + let visual = + glx.get_visual_from_fb_config(xlib_connection, fb_config, error_handler)?; + + Ok(( + FbConfig { + fb_config: FbConfigInner::Glx { config: fb_config, glx }, + gl_config: *config, + }, + WindowConfig { depth: visual.depth as u8, visual: visual.visualid as u32 }, + )) + }) + } + + pub unsafe fn make_current(&self) -> Result<()> { + XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { + self.glx.make_current( + self.connection.conn.xlib_connection(), + self.window_id(), + self.context, + error_handler, + ) + }) + } + + pub unsafe fn make_not_current(&self) -> Result<()> { + XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { + self.glx.clear_current(self.connection.conn.xlib_connection(), error_handler) + }) + } + + fn window_id(&self) -> c_ulong { + self.window.get().into() + } + + pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { + match self.glx.get_proc_address(symbol) { + Some(ptr) => ptr.as_ptr(), + None => std::ptr::null(), + } + } + + pub fn swap_buffers(&self) -> Result<()> { + XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { + self.glx.swap_buffers( + self.connection.conn.xlib_connection(), + self.window_id(), + error_handler, + ) + }) + } +} + +impl Drop for GlxGlContext { + fn drop(&mut self) { + unsafe { self.glx.destroy_context(self.connection.conn.xlib_connection(), self.context) } + } +} diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index 2682d63d..bdaa7199 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -19,7 +19,7 @@ pub(crate) struct WindowVisualConfig { impl WindowVisualConfig { #[cfg(feature = "opengl")] pub fn find_best_visual_config_for_gl( - connection: &X11Connection, gl_config: Option, + connection: &std::rc::Rc, gl_config: Option, ) -> Result { let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 203f911d..f3938ff2 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -85,14 +85,14 @@ impl WindowInner { let size_hints = get_size_hints(&sizing_strategy, physical_size, initial_scale_factor); + let connection = Rc::new(xcb_connection); + #[cfg(feature = "opengl")] let visual_info = - WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; + WindowVisualConfig::find_best_visual_config_for_gl(&connection, options.gl_config)?; #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; - - let connection = Rc::new(xcb_connection); + let visual_info = WindowVisualConfig::find_best_visual_config(&connection)?; let will_have_parent = options.parent.is_some() || options.wait_for_parent; @@ -126,11 +126,7 @@ impl WindowInner { None => None, Some(fb_config) => { // Because of the visual negotation we had to take some extra steps to create this context - Some(super::gl::GlContextInner::create( - &xcb_window, - Rc::clone(&connection), - fb_config, - )?) + Some(super::gl::GlContextInner::create(&xcb_window, &connection, fb_config)?) } }; diff --git a/src/wrappers.rs b/src/wrappers.rs index 570c0588..b145f05c 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -23,6 +23,10 @@ pub mod xkbcommon; #[cfg(all(target_os = "linux", feature = "opengl"))] pub mod glx; +/// Wrappers and utilities around EGL. +#[cfg(all(target_os = "linux", feature = "opengl"))] +pub mod egl; + /// Wrappers and utilities around the Win32 API. #[cfg(target_os = "windows")] pub mod win32; diff --git a/src/wrappers/egl.rs b/src/wrappers/egl.rs new file mode 100644 index 00000000..df8717b9 --- /dev/null +++ b/src/wrappers/egl.rs @@ -0,0 +1,58 @@ +use crate::platform::gl::CreationFailedError; +use libloading::Library; +use std::ffi::{c_void, CStr}; +use std::rc::Rc; + +mod bound_api; +mod config; +mod context; +mod display; +mod error; +mod extensions; +mod surface; +mod sys; + +use sys::Functions; + +use crate::wrappers::egl::extensions::Extensions; +pub use bound_api::BoundApi; +pub use config::EglConfig; +pub use context::EglContext; +pub use display::{EglDisplay, EglVersion}; +pub use error::EglError; +pub use surface::EglSurface; +pub use sys::MissingSymbolError; + +struct EglInner { + _library: Library, + functions: Functions, +} + +#[derive(Clone)] +pub struct Egl { + inner: Rc, +} + +impl Egl { + pub fn open() -> Result { + let library = + unsafe { Library::new("libEGL.so.1").or_else(|_| Library::new("libEGL.so")) }?; + + let functions = unsafe { Functions::load_from(&library)? }; + + Ok(Self { inner: Rc::new(EglInner { _library: library, functions }) }) + } + + pub fn with_opengl(&self, handler: impl FnOnce(&BoundApi) -> T) -> Result { + let api = BoundApi::new(self)?; + Ok(handler(&api)) + } + + pub fn query_client_extensions(&self) -> Extensions { + Extensions::new(self) + } + + pub fn get_proc_address(&self, proc_name: &CStr) -> *const c_void { + unsafe { (self.inner.functions.eglGetProcAddress)(proc_name.as_ptr()) } + } +} diff --git a/src/wrappers/egl/bound_api.rs b/src/wrappers/egl/bound_api.rs new file mode 100644 index 00000000..e5611262 --- /dev/null +++ b/src/wrappers/egl/bound_api.rs @@ -0,0 +1,45 @@ +use super::*; + +pub struct BoundApi { + egl: Egl, + previous_api: Option, +} + +impl BoundApi { + pub(super) fn new(egl: &Egl) -> Result { + let previous_api = egl.query_api(); + egl.bind_api(sys::OPENGL_API)?; + + Ok(Self { previous_api, egl: egl.clone() }) + } +} + +impl Drop for BoundApi { + fn drop(&mut self) { + let Some(previous_api) = self.previous_api else { return }; + + if let Err(e) = self.egl.bind_api(previous_api) { + crate::warn!("Failed to restore EGL api: {}", e); + } + } +} + +impl Egl { + fn query_api(&self) -> Option { + let result = unsafe { (self.inner.functions.eglQueryAPI)() }; + if result == sys::ENUM_NONE { + None + } else { + Some(result) + } + } + + fn bind_api(&self, api: sys::Enum) -> Result<(), EglError> { + let result = unsafe { (self.inner.functions.eglBindAPI)(api) }; + if result == sys::FALSE { + Err(EglError::from_last_error(self)) + } else { + Ok(()) + } + } +} diff --git a/src/wrappers/egl/config.rs b/src/wrappers/egl/config.rs new file mode 100644 index 00000000..483092e8 --- /dev/null +++ b/src/wrappers/egl/config.rs @@ -0,0 +1,90 @@ +use super::*; +use crate::gl::GlConfig; +use crate::wrappers::egl::display::EglDisplay; +use crate::wrappers::egl::sys::*; +use std::ffi::c_void; +use std::ptr::NonNull; +use x11rb::protocol::xproto::Visualid; + +#[derive(Copy, Clone)] +pub struct EglConfig(pub(super) NonNull); + +impl EglConfig { + pub(super) fn choose_config( + gl_config: &GlConfig, display: &EglDisplay, + ) -> Result, EglError> { + let mut config = core::ptr::null_mut(); + let fb_attribs = get_fb_attribs(gl_config); + let mut num_configs = 0; + let result = unsafe { + (display.egl().inner.functions.eglChooseConfig)( + display.as_raw(), + fb_attribs.as_ptr(), + &mut config, + 1, + &mut num_configs, + ) + }; + + if result == FALSE { + return Err(EglError::from_last_error(display.egl())); + } + + if num_configs == 0 { + return Ok(None); + } + + let Some(raw) = NonNull::new(config) else { return Ok(None) }; + + Ok(Some(EglConfig(raw))) + } + + fn get_attrib(&self, display: &EglDisplay, attrib: Int) -> Result { + let mut value = 0; + let result = unsafe { + (display.egl().inner.functions.eglGetConfigAttrib)( + display.as_raw(), + self.0.as_ptr(), + attrib, + &mut value, + ) + }; + + if result == FALSE { + return Err(EglError::from_last_error(display.egl())); + } + + Ok(value) + } + + pub fn get_visual_id(&self, display: &EglDisplay) -> Result { + let value = self.get_attrib(display, EGL_NATIVE_VISUAL_ID)?; + let value: Visualid = + value.try_into().map_err(|e| CreationFailedError::EglInvalidVisualId(value, e))?; + Ok(value) + } +} + +fn get_fb_attribs(config: &GlConfig) -> [Int; 17] { + let Some(color_size) = (config.red_bits as i32) + .checked_add(config.blue_bits as i32) + .and_then(|c| c.checked_add(config.green_bits as i32)) + else { + panic!("Overflow when computing color size") + }; + + #[rustfmt::skip] + let fb_attribs = [ + EGL_BUFFER_SIZE, color_size, + EGL_RED_SIZE, config.red_bits.into(), + EGL_GREEN_SIZE, config.green_bits.into(), + EGL_BLUE_SIZE, config.blue_bits.into(), + EGL_ALPHA_SIZE, config.alpha_bits.into(), + EGL_DEPTH_SIZE, config.depth_bits.into(), + EGL_STENCIL_SIZE, config.stencil_bits.into(), + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + ]; + + fb_attribs +} diff --git a/src/wrappers/egl/context.rs b/src/wrappers/egl/context.rs new file mode 100644 index 00000000..d8714d47 --- /dev/null +++ b/src/wrappers/egl/context.rs @@ -0,0 +1,121 @@ +use super::*; +use crate::gl::{GlConfig, Profile}; +use crate::wrappers::egl::sys::*; +use std::ptr::NonNull; + +pub struct EglContext { + display: EglDisplay, + raw: NonNull, +} + +impl EglContext { + pub(super) fn create( + display: &EglDisplay, config: EglConfig, gl_config: &GlConfig, + ) -> Result { + let raw = display.egl().create_context(display, config, gl_config)?; + + Ok(Self { display: display.clone(), raw }) + } + + pub fn make_current(&self, surface: &EglSurface) -> Result<(), EglError> { + self.display.egl().make_current(surface, self) + } + + pub fn make_not_current(&self, _bound_api: &BoundApi) -> Result<(), EglError> { + self.display.egl().make_not_current(self) + } +} + +impl Drop for EglContext { + fn drop(&mut self) { + if let Err(e) = unsafe { self.display.egl().destroy_context(self) } { + crate::warn!("Failed to destroy EGL context: {e}"); + } + } +} + +impl Egl { + fn get_context_attribs(gl_config: &GlConfig) -> [Int; 7] { + let profile_mask = match gl_config.profile { + Profile::Core => EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + Profile::Compatibility => EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, + }; + + #[rustfmt::skip] + let fb_attribs = [ + EGL_CONTEXT_MAJOR_VERSION, gl_config.version.0.into(), + EGL_CONTEXT_MINOR_VERSION, gl_config.version.1.into(), + EGL_CONTEXT_OPENGL_PROFILE_MASK, profile_mask, + EGL_NONE, + ]; + + fb_attribs + } + + fn create_context( + &self, display: &EglDisplay, config: EglConfig, gl_config: &GlConfig, + ) -> Result, EglError> { + let attribs = Self::get_context_attribs(gl_config); + let result = unsafe { + (self.inner.functions.eglCreateContext)( + display.as_raw(), + config.0.as_ptr(), + core::ptr::null_mut(), + attribs.as_ptr(), + ) + }; + + NonNull::new(result).ok_or_else(|| EglError::from_last_error(display.egl())) + } + + fn make_current(&self, surface: &EglSurface, context: &EglContext) -> Result<(), EglError> { + let result = unsafe { + (self.inner.functions.eglMakeCurrent)( + surface.display().as_raw(), + surface.as_raw(), + surface.as_raw(), + context.raw.as_ptr(), + ) + }; + + if result == FALSE { + Err(EglError::from_last_error(self)) + } else { + Ok(()) + } + } + + fn make_not_current(&self, context: &EglContext) -> Result<(), EglError> { + let current_context = unsafe { (self.inner.functions.eglGetCurrentContext)() }; + if current_context != context.raw.as_ptr() { + return Ok(()); + } + + let result = unsafe { + (self.inner.functions.eglMakeCurrent)( + context.display.as_raw(), + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + }; + + if result == FALSE { + Err(EglError::from_last_error(self)) + } else { + Ok(()) + } + } + + unsafe fn destroy_context(&self, context: &EglContext) -> Result<(), EglError> { + let result = unsafe { + (self.inner.functions.eglDestroyContext)(context.display.as_raw(), context.raw.as_ptr()) + }; + + if result == FALSE { + Err(EglError::from_last_error(self)) + } else { + Ok(()) + } + } +} diff --git a/src/wrappers/egl/display.rs b/src/wrappers/egl/display.rs new file mode 100644 index 00000000..4185711f --- /dev/null +++ b/src/wrappers/egl/display.rs @@ -0,0 +1,121 @@ +use crate::gl::GlConfig; +use crate::platform::gl::CreationFailedError; +use crate::platform::X11Connection; +use crate::wrappers::egl::bound_api::BoundApi; +use crate::wrappers::egl::config::EglConfig; +use crate::wrappers::egl::context::EglContext; +use crate::wrappers::egl::surface::EglSurface; +use crate::wrappers::egl::{sys, Egl, EglError}; +use crate::wrappers::xlib::XlibConnection; +use std::ffi::c_void; +use std::ptr::NonNull; +use std::rc::Rc; +use x11rb::protocol::xproto::Window; + +struct EglDisplayInner { + egl: Egl, + raw: NonNull, + // Kept to ensure the connection isn't dropped as long as the EGL display is alive + _connection: Rc, +} + +#[derive(Clone)] +pub struct EglDisplay { + inner: Rc, +} + +impl EglDisplay { + pub fn choose_config(&self, config: &GlConfig) -> Result, EglError> { + EglConfig::choose_config(config, self) + } + + pub fn egl(&self) -> &Egl { + &self.inner.egl + } + + pub fn as_raw(&self) -> sys::EGLDisplay { + self.inner.raw.as_ptr() + } + + pub fn create_surface( + &self, config: EglConfig, window: Window, gl_config: &GlConfig, + ) -> Result { + EglSurface::create(self, config, window, gl_config) + } + + pub fn create_context( + &self, config: EglConfig, _bound: &BoundApi, gl_config: &GlConfig, + ) -> Result { + EglContext::create(self, config, gl_config) + } +} + +impl Drop for EglDisplayInner { + fn drop(&mut self) { + if let Err(e) = unsafe { self.egl.terminate_display(self.raw) } { + crate::warn!("Failed to terminate EGL display connection: {}", e) + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct EglVersion { + pub major: sys::Int, + pub minor: sys::Int, +} + +impl Egl { + pub fn create_display( + &self, connection: &Rc, + ) -> Result { + let display = self + .create_display_basic(connection.conn.xlib_connection()) + .ok_or(CreationFailedError::EglNoDisplay)?; + + let egl = self.clone(); + + let version = unsafe { egl.initialize_display(display)? }; + + // Initialize DisplayInner here so its drop impl will run if code below fails + let inner = EglDisplayInner { egl, raw: display, _connection: Rc::clone(connection) }; + + if version.major != 1 || version.minor < 5 { + return Err(CreationFailedError::EglUnsupportedVersion(version)); + } + + Ok(EglDisplay { inner: Rc::new(inner) }) + } + + fn create_display_basic(&self, connection: &XlibConnection) -> Option> { + let result = unsafe { (self.inner.functions.eglGetDisplay)(connection.as_raw().cast()) }; + NonNull::new(result) + } + + unsafe fn initialize_display(&self, raw: NonNull) -> Result { + let mut version = EglVersion { major: 0, minor: 0 }; + + let result = unsafe { + (self.inner.functions.eglInitialize)( + raw.as_ptr(), + &mut version.major, + &mut version.minor, + ) + }; + + if result == sys::FALSE { + return Err(EglError::from_last_error(self)); + } + + Ok(version) + } + + unsafe fn terminate_display(&self, raw: NonNull) -> Result<(), EglError> { + let result = unsafe { (self.inner.functions.eglTerminate)(raw.as_ptr()) }; + + if result == sys::FALSE { + return Err(EglError::from_last_error(self)); + } + + Ok(()) + } +} diff --git a/src/wrappers/egl/error.rs b/src/wrappers/egl/error.rs new file mode 100644 index 00000000..3d61d9d7 --- /dev/null +++ b/src/wrappers/egl/error.rs @@ -0,0 +1,29 @@ +use super::*; +use crate::wrappers::egl::sys::*; +use std::error::Error; +use std::ffi::c_int; +use std::fmt::Display; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct EglError { + code: c_int, +} + +impl EglError { + pub fn from_last_error(egl: &Egl) -> EglError { + let code = unsafe { (egl.inner.functions.eglGetError)() }; + Self { code } + } +} + +impl Display for EglError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.code == EGL_SUCCESS { + f.write_str("EGL call failed but error code is EGL_SUCCESS") + } else { + write!(f, "EGL error code: {:x}", self.code) + } + } +} + +impl Error for EglError {} diff --git a/src/wrappers/egl/extensions.rs b/src/wrappers/egl/extensions.rs new file mode 100644 index 00000000..e0d6475f --- /dev/null +++ b/src/wrappers/egl/extensions.rs @@ -0,0 +1,39 @@ +use crate::wrappers::egl::{sys, Egl}; +use std::ffi::CStr; +use std::fmt::Debug; + +pub struct Extensions { + string: Option<&'static CStr>, +} + +impl Debug for Extensions { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self.string { + Some(s) => f.write_str(&s.to_string_lossy()), + None => f.write_str(""), + } + } +} + +impl Extensions { + pub(super) fn new(egl: &Egl) -> Extensions { + Self { string: egl.query_client_extensions_inner() } + } + + pub fn supports(&self, extension_id: &[u8]) -> bool { + let Some(string) = self.string else { return false }; + string.to_bytes().split(|b| *b == b' ').any(|s| s == extension_id) + } +} + +impl Egl { + fn query_client_extensions_inner(&self) -> Option<&'static CStr> { + let result = + unsafe { (self.inner.functions.eglQueryString)(sys::NO_DISPLAY, sys::EXTENSIONS) }; + if result.is_null() { + None + } else { + unsafe { Some(CStr::from_ptr(result)) } + } + } +} diff --git a/src/wrappers/egl/surface.rs b/src/wrappers/egl/surface.rs new file mode 100644 index 00000000..8030929a --- /dev/null +++ b/src/wrappers/egl/surface.rs @@ -0,0 +1,94 @@ +use super::sys::*; +use super::*; +use crate::gl::GlConfig; +use std::ffi::c_void; +use std::ptr::NonNull; +use x11rb::protocol::xproto::Window; + +pub struct EglSurface { + display: EglDisplay, + raw: NonNull, +} + +impl EglSurface { + pub(super) fn create( + display: &EglDisplay, config: EglConfig, window: Window, gl_config: &GlConfig, + ) -> Result { + let raw = display.egl().create_surface(display, config, window, gl_config)?; + + Ok(Self { display: display.clone(), raw }) + } + + pub fn display(&self) -> &EglDisplay { + &self.display + } + + pub fn as_raw(&self) -> *mut c_void { + self.raw.as_ptr() + } + + pub fn swap_buffers(&self) -> Result<(), EglError> { + self.display().egl().swap_buffers(self) + } +} + +impl Drop for EglSurface { + fn drop(&mut self) { + if let Err(e) = unsafe { self.display.egl().destroy_surface(self) } { + crate::warn!("Failed to destroy EGL surface: {e}"); + } + } +} + +impl Egl { + fn get_surface_attribs(gl_config: &GlConfig) -> [Int; 3] { + #[rustfmt::skip] + let fb_attribs = [ + EGL_GL_COLORSPACE, if gl_config.srgb { EGL_GL_COLORSPACE_SRGB } else { EGL_GL_COLORSPACE_LINEAR }, + // EGL_RENDER_BUFFER: EGL_BACK_BUFFER is default + EGL_NONE, + ]; + + fb_attribs + } + + fn create_surface( + &self, display: &EglDisplay, config: EglConfig, window: Window, gl_config: &GlConfig, + ) -> Result, EglError> { + let attribs = Self::get_surface_attribs(gl_config); + let result = unsafe { + (self.inner.functions.eglCreateWindowSurface)( + display.as_raw(), + config.0.as_ptr(), + window as _, + attribs.as_ptr(), + ) + }; + + NonNull::new(result).ok_or_else(|| EglError::from_last_error(self)) + } + + fn swap_buffers(&self, surface: &EglSurface) -> Result<(), EglError> { + let result = unsafe { + (self.inner.functions.eglSwapBuffers)(surface.display().as_raw(), surface.as_raw()) + }; + + if result == FALSE { + Err(EglError::from_last_error(self)) + } else { + Ok(()) + } + } + + unsafe fn destroy_surface(&self, surface: &EglSurface) -> Result<(), EglError> { + let result = unsafe { + (self.inner.functions.eglDestroySurface)(surface.display().as_raw(), surface.as_raw()) + }; + + if result == FALSE { + Err(EglError::from_last_error(self)) + } else { + Ok(()) + } + } +} diff --git a/src/wrappers/egl/sys.rs b/src/wrappers/egl/sys.rs new file mode 100644 index 00000000..bd5435fd --- /dev/null +++ b/src/wrappers/egl/sys.rs @@ -0,0 +1,171 @@ +#![allow(non_snake_case, non_camel_case_types, reason = "To match EGL function naming")] + +use crate::platform::gl::CreationFailedError; +use libloading::Library; +use std::ffi::*; +use std::fmt::{Display, Formatter}; + +pub type Enum = c_uint; +pub type Boolean = c_uint; +pub type Int = c_int; +pub type EGLDisplay = *mut c_void; +pub type EGLSurface = *mut c_void; +pub type NativeDisplayType = *mut c_void; +pub type NativeWindowType = *mut c_void; +pub type EGLConfig = *mut c_void; +pub type EGLContext = *mut c_void; + +pub type eglGetError = unsafe extern "system" fn() -> c_int; +pub type eglBindAPI = unsafe extern "system" fn(Enum) -> Boolean; +pub type eglQueryAPI = unsafe extern "system" fn() -> Enum; +pub type eglQueryString = unsafe extern "system" fn(EGLDisplay, Int) -> *const c_char; +pub type eglGetDisplay = unsafe extern "system" fn(NativeDisplayType) -> EGLDisplay; +pub type eglInitialize = + unsafe extern "system" fn(display: EGLDisplay, major: *mut Int, minor: *mut Int) -> Boolean; +pub type eglTerminate = unsafe extern "system" fn(display: EGLDisplay) -> Boolean; +pub type eglChooseConfig = unsafe extern "system" fn( + display: EGLDisplay, + attrib_list: *const Int, + configs: *mut EGLConfig, + config_size: Int, + num_config: *mut Int, +) -> Boolean; + +pub type eglGetConfigAttrib = unsafe extern "system" fn( + display: EGLDisplay, + config: EGLConfig, + attribute: Int, + value: *mut Int, +) -> Boolean; +pub type eglCreateWindowSurface = unsafe extern "system" fn( + display: EGLDisplay, + config: EGLConfig, + win: NativeWindowType, + attrib_list: *const Int, +) -> EGLSurface; +pub type eglCreateContext = unsafe extern "system" fn( + display: EGLDisplay, + config: EGLConfig, + share_context: EGLContext, + attrib_list: *const Int, +) -> EGLContext; +pub type eglDestroySurface = + unsafe extern "system" fn(display: EGLDisplay, surface: EGLSurface) -> Boolean; +pub type eglDestroyContext = + unsafe extern "system" fn(display: EGLDisplay, context: EGLContext) -> Boolean; +pub type eglMakeCurrent = unsafe extern "system" fn( + display: EGLDisplay, + draw: EGLSurface, + read: EGLSurface, + ctx: EGLContext, +) -> Boolean; +pub type eglGetProcAddress = unsafe extern "system" fn(procname: *const c_char) -> *const c_void; +pub type eglGetCurrentContext = unsafe extern "system" fn() -> EGLContext; +pub type eglSwapBuffers = + unsafe extern "system" fn(display: EGLDisplay, surface: EGLSurface) -> Boolean; + +pub const ENUM_NONE: Enum = 0x3038; +pub const OPENGL_API: Enum = 0x30A2; +pub const FALSE: Boolean = 0; +pub const NO_DISPLAY: EGLDisplay = 0 as EGLDisplay; +pub const EXTENSIONS: Int = 0x3055; +pub const EGL_SUCCESS: Int = 0x3000; + +pub const EGL_SURFACE_TYPE: Int = 0x3033; +pub const EGL_WINDOW_BIT: Int = 0x0004; + +pub const EGL_NONE: Int = 0x3038; +pub const EGL_BUFFER_SIZE: Int = 0x3020; +pub const EGL_RED_SIZE: Int = 0x3024; +pub const EGL_GREEN_SIZE: Int = 0x3023; +pub const EGL_BLUE_SIZE: Int = 0x3022; +pub const EGL_ALPHA_SIZE: Int = 0x3021; +pub const EGL_DEPTH_SIZE: Int = 0x3025; +pub const EGL_STENCIL_SIZE: Int = 0x3026; + +pub const EGL_GL_COLORSPACE: Int = 0x309D; +pub const EGL_GL_COLORSPACE_LINEAR: Int = 0x308A; +pub const EGL_GL_COLORSPACE_SRGB: Int = 0x3089; + +pub const EGL_CONTEXT_MAJOR_VERSION: Int = 0x3098; +pub const EGL_CONTEXT_MINOR_VERSION: Int = 0x30FB; +pub const EGL_CONTEXT_OPENGL_PROFILE_MASK: Int = 0x30FD; +pub const EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT: Int = 0x00000001; +pub const EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT: Int = 0x00000002; + +pub const EGL_NATIVE_VISUAL_ID: Int = 0x302E; + +#[derive(Debug, Copy, Clone)] +pub struct MissingSymbolError { + name: &'static CStr, +} + +impl Display for MissingSymbolError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Missing EGL symbol: {}", self.name.to_string_lossy()) + } +} + +impl From for CreationFailedError { + fn from(value: MissingSymbolError) -> Self { + Self::EGLMissingSymbol(value) + } +} + +impl From for CreationFailedError { + fn from(value: libloading::Error) -> Self { + Self::EGLLoadError(value) + } +} + +pub struct Functions { + pub eglGetError: eglGetError, + pub eglBindAPI: eglBindAPI, + pub eglQueryAPI: eglQueryAPI, + pub eglQueryString: eglQueryString, + pub eglGetDisplay: eglGetDisplay, + pub eglInitialize: eglInitialize, + pub eglTerminate: eglTerminate, + pub eglChooseConfig: eglChooseConfig, + pub eglGetConfigAttrib: eglGetConfigAttrib, + pub eglCreateWindowSurface: eglCreateWindowSurface, + pub eglDestroySurface: eglDestroySurface, + pub eglCreateContext: eglCreateContext, + pub eglGetProcAddress: eglGetProcAddress, + pub eglMakeCurrent: eglMakeCurrent, + pub eglDestroyContext: eglDestroyContext, + pub eglGetCurrentContext: eglGetCurrentContext, + pub eglSwapBuffers: eglSwapBuffers, +} + +impl Functions { + pub unsafe fn load_from(library: &Library) -> Result { + Ok(Self { + eglGetError: Self::get(library, c"eglGetError")?, + eglBindAPI: Self::get(library, c"eglBindAPI")?, + eglQueryAPI: Self::get(library, c"eglQueryAPI")?, + eglQueryString: Self::get(library, c"eglQueryString")?, + eglGetDisplay: Self::get(library, c"eglGetDisplay")?, + eglInitialize: Self::get(library, c"eglInitialize")?, + eglTerminate: Self::get(library, c"eglTerminate")?, + eglChooseConfig: Self::get(library, c"eglChooseConfig")?, + eglGetConfigAttrib: Self::get(library, c"eglGetConfigAttrib")?, + eglCreateWindowSurface: Self::get(library, c"eglCreateWindowSurface")?, + eglDestroySurface: Self::get(library, c"eglDestroySurface")?, + eglCreateContext: Self::get(library, c"eglCreateContext")?, + eglMakeCurrent: Self::get(library, c"eglMakeCurrent")?, + eglGetProcAddress: Self::get(library, c"eglGetProcAddress")?, + eglDestroyContext: Self::get(library, c"eglDestroyContext")?, + eglGetCurrentContext: Self::get(library, c"eglGetCurrentContext")?, + eglSwapBuffers: Self::get(library, c"eglSwapBuffers")?, + }) + } + + unsafe fn get( + library: &Library, name: &'static CStr, + ) -> Result { + let symbol = library.get::>(name.to_bytes_with_nul())?; + let symbol = symbol.lift_option().ok_or(MissingSymbolError { name })?; + Ok(*symbol) + } +} diff --git a/src/wrappers/glx.rs b/src/wrappers/glx.rs index 07cbc709..60f3ed88 100644 --- a/src/wrappers/glx.rs +++ b/src/wrappers/glx.rs @@ -6,6 +6,7 @@ use crate::platform::*; use std::ffi::{c_ulong, c_void, CStr}; use std::os::raw::c_int; use std::ptr::NonNull; +use std::rc::Rc; use x11_dl::glx::{arb::*, *}; use x11_dl::xlib; use x11_dl::xlib::XVisualInfo; @@ -22,13 +23,14 @@ type GlXCreateContextAttribsARB = unsafe extern "C" fn( /// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt. const GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20B2; +#[derive(Clone)] pub struct Glx { - inner: x11_dl::glx::Glx, + inner: Rc, } impl Glx { pub fn open() -> Result { - Ok(Self { inner: x11_dl::glx::Glx::open()? }) + Ok(Self { inner: Rc::new(x11_dl::glx::Glx::open()?) }) } fn get_fb_attribs(config: &GlConfig) -> [c_int; 29] {