Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
2 changes: 1 addition & 1 deletion src/gl.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/platform/win/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions src/platform/x11/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down Expand Up @@ -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),
}
}
}
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -221,6 +227,13 @@ impl From<super::gl::CreationFailedError> for PlatformError {
}
}

#[cfg(feature = "opengl")]
impl From<crate::wrappers::egl::EglError> for PlatformError {
fn from(value: crate::wrappers::egl::EglError) -> Self {
Self::EGl(value)
}
}

pub trait CookieExt {
fn check_warn(self);
}
Expand Down
159 changes: 79 additions & 80 deletions src/platform/x11/gl.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 {
Expand All @@ -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<EglError> for CreationFailedError {
fn from(err: EglError) -> Self {
CreationFailedError::EglError(err)
}
}

pub type GlContext = Rc<GlContextInner>;

pub struct GlContextInner {
glx: Glx,
window: NonZeroU32,
connection: Rc<X11Connection>,
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
Expand All @@ -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<X11Connection>, config: FbConfig,
window: &XcbWindow, connection: &Rc<X11Connection>, fb_config: FbConfig,
) -> Result<Rc<GlContextInner>> {
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<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, 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(),
}
}
}
77 changes: 77 additions & 0 deletions src/platform/x11/gl/egl.rs
Original file line number Diff line number Diff line change
@@ -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<Self, PlatformError> {
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<X11Connection>, 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<u8> {
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(())
}
}
Loading