Skip to content
Merged
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
203 changes: 51 additions & 152 deletions src/gl/x11.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use super::{GlConfig, GlError, Profile};
use super::{GlConfig, GlError};
use crate::x11::XcbConnection;
use std::ffi::{c_void, CString};
use std::os::raw::{c_int, c_ulong};
use std::os::raw::c_ulong;
use std::rc::Rc;
use x11_dl::error::OpenError;
use x11_dl::glx;
use x11_dl::glx::Glx;
use x11_dl::xlib;
use x11_dl::glx::GLXContext;

mod errors;
mod glx;
use crate::gl::x11::glx::GlxFbConfig;
use glx::Glx;

#[derive(Debug)]
pub enum CreationFailedError {
InvalidFBConfig,
NoValidFBConfig,
NoVisual,
GetProcAddressFailed,
MakeCurrentFailed,
Expand All @@ -33,42 +34,18 @@ impl From<OpenError> for GlError {
}
}

// See https://www.khronos.org/registry/OpenGL/extensions/ARB/GLX_ARB_create_context.txt

type GlXCreateContextAttribsARB = unsafe extern "C" fn(
dpy: *mut xlib::Display,
fbc: glx::GLXFBConfig,
share_context: glx::GLXContext,
direct: xlib::Bool,
attribs: *const c_int,
) -> glx::GLXContext;

// See https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_swap_control.txt

type GlXSwapIntervalEXT =
unsafe extern "C" fn(dpy: *mut xlib::Display, drawable: glx::GLXDrawable, interval: i32);

// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt

const GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20B2;

fn get_proc_address(glx: &Glx, symbol: &str) -> *const c_void {
let symbol = CString::new(symbol).unwrap();
unsafe { (glx.glXGetProcAddress)(symbol.as_ptr() as *const u8).unwrap() as *const c_void }
}

pub struct GlContext {
glx: Glx,
window: c_ulong,
connection: Rc<XcbConnection>,
context: glx::GLXContext,
context: GLXContext,
}

/// The frame buffer configuration along with the general OpenGL configuration to somewhat minimize
/// misuse.
pub struct FbConfig {
gl_config: GlConfig,
fb_config: *mut glx::__GLXFBConfigRec,
fb_config: GlxFbConfig,
}

/// The configuration a window should be created with after calling
Expand All @@ -92,76 +69,38 @@ impl GlContext {
) -> Result<GlContext, GlError> {
let glx = Glx::open()?;

let context = errors::XErrorHandler::handle(&connection, |error_handler| {
#[allow(non_snake_case)]
let glXCreateContextAttribsARB: GlXCreateContextAttribsARB = {
let addr = get_proc_address(&glx, "glXCreateContextAttribsARB");
if addr.is_null() {
return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed));
} else {
#[allow(clippy::missing_transmute_annotations)]
std::mem::transmute(addr)
}
errors::XErrorHandler::handle(&connection, |error_handler| {
let Some(create_context) = glx.get_glx_create_context_attribs_arb() else {
return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed));
};

#[allow(non_snake_case)]
let glXSwapIntervalEXT: GlXSwapIntervalEXT = {
let addr = get_proc_address(&glx, "glXSwapIntervalEXT");
if addr.is_null() {
return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed));
} else {
#[allow(clippy::missing_transmute_annotations)]
std::mem::transmute(addr)
}
let Some(swap_interval) = glx.get_glx_swap_interval_ext() else {
return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed));
};

error_handler.check()?;

let profile_mask = match config.gl_config.profile {
Profile::Core => glx::arb::GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
Profile::Compatibility => glx::arb::GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
};

#[rustfmt::skip]
let ctx_attribs = [
glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB, config.gl_config.version.0 as i32,
glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB, config.gl_config.version.1 as i32,
glx::arb::GLX_CONTEXT_PROFILE_MASK_ARB, profile_mask,
0,
];

let context = glXCreateContextAttribsARB(
connection.dpy,
let context = create_context.call(
&connection,
&config.gl_config,
config.fb_config,
std::ptr::null_mut(),
1,
ctx_attribs.as_ptr(),
);

error_handler.check()?;

if context.is_null() {
return Err(GlError::CreationFailed(CreationFailedError::ContextCreationFailed));
}

let res = (glx.glXMakeCurrent)(connection.dpy, window, context);
error_handler.check()?;
if res == 0 {
return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed));
}

glXSwapIntervalEXT(connection.dpy, window, config.gl_config.vsync as i32);
error_handler.check()?;

if (glx.glXMakeCurrent)(connection.dpy, 0, std::ptr::null_mut()) == 0 {
error_handler.check()?;
return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed));
}
error_handler,
)?;

// Create context object here so that error or panic will properly free the context
let context = GlContext { glx, window, connection: Rc::clone(&connection), context };

context.glx.with_current_context(
&connection,
window,
context.context,
error_handler,
|| {
swap_interval(connection.dpy, window, config.gl_config.vsync as i32);
error_handler.check()
},
)??;

Ok(context)
})?;

Ok(GlContext { glx, window, connection, context })
})
}

/// Find a matching framebuffer config and window visual for the given OpenGL configuration.
Expand All @@ -173,45 +112,11 @@ impl GlContext {
let glx = Glx::open()?;

errors::XErrorHandler::handle(conn, |error_handler| {
let screen = conn.screen;

#[rustfmt::skip]
let fb_attribs = [
glx::GLX_X_RENDERABLE, 1,
glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR,
glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT,
glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT,
glx::GLX_RED_SIZE, config.red_bits as i32,
glx::GLX_GREEN_SIZE, config.green_bits as i32,
glx::GLX_BLUE_SIZE, config.blue_bits as i32,
glx::GLX_ALPHA_SIZE, config.alpha_bits as i32,
glx::GLX_DEPTH_SIZE, config.depth_bits as i32,
glx::GLX_STENCIL_SIZE, config.stencil_bits as i32,
glx::GLX_DOUBLEBUFFER, config.double_buffer as i32,
glx::GLX_SAMPLE_BUFFERS, config.samples.is_some() as i32,
glx::GLX_SAMPLES, config.samples.unwrap_or(0) as i32,
GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32,
0,
];

let mut n_configs = 0;
let fb_config = unsafe {
(glx.glXChooseFBConfig)(conn.dpy, screen, fb_attribs.as_ptr(), &mut n_configs)
};

error_handler.check()?;
if n_configs <= 0 || fb_config.is_null() {
return Err(GlError::CreationFailed(CreationFailedError::InvalidFBConfig));
}
let fb_config = glx.choose_best_fb_config(conn, &config, error_handler)?;

// Now that we have a matching framebuffer config, we need to know which visual matches
// thsi config so the window is compatible with the OpenGL context we're about to create
let fb_config = unsafe { fb_config.read() };
let visual = unsafe { (glx.glXGetVisualFromFBConfig)(conn.dpy, fb_config) };
if visual.is_null() {
return Err(GlError::CreationFailed(CreationFailedError::NoVisual));
}
let visual = unsafe { visual.read() };
// this config so the window is compatible with the OpenGL context we're about to create
let visual = glx.get_visual_from_fb_config(conn, fb_config, error_handler)?;

Ok((
FbConfig { fb_config, gl_config: config },
Expand All @@ -222,42 +127,36 @@ impl GlContext {

pub unsafe fn make_current(&self) {
errors::XErrorHandler::handle(&self.connection, |error_handler| {
let res = (self.glx.glXMakeCurrent)(self.connection.dpy, self.window, self.context);
error_handler.check().unwrap();
if res == 0 {
panic!("make_current failed")
}
self.glx
.make_current(&self.connection, self.window, self.context, error_handler)
.unwrap();
})
}

pub unsafe fn make_not_current(&self) {
errors::XErrorHandler::handle(&self.connection, |error_handler| {
let res = (self.glx.glXMakeCurrent)(self.connection.dpy, 0, std::ptr::null_mut());
error_handler.check().unwrap();
if res == 0 {
panic!("make_not_current failed")
}
self.glx.clear_current(&self.connection, error_handler).unwrap();
})
}

pub fn get_proc_address(&self, symbol: &str) -> *const c_void {
get_proc_address(&self.glx, symbol)
let symbol = CString::new(symbol).unwrap();

match self.glx.get_proc_address(&symbol) {
Some(ptr) => ptr.as_ptr(),
None => std::ptr::null(),
}
}

pub fn swap_buffers(&self) {
unsafe {
errors::XErrorHandler::handle(&self.connection, |error_handler| {
(self.glx.glXSwapBuffers)(self.connection.dpy, self.window);
error_handler.check().unwrap();
})
}
errors::XErrorHandler::handle(&self.connection, |error_handler| {
self.glx.swap_buffers(&self.connection, self.window, error_handler).unwrap()
})
}
}

impl Drop for GlContext {
fn drop(&mut self) {
unsafe {
(self.glx.glXDestroyContext)(self.connection.dpy, self.context);
}
unsafe { self.glx.destroy_context(&self.connection, self.context) }
}
}
28 changes: 13 additions & 15 deletions src/gl/x11/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@ use std::fmt::{Debug, Display, Formatter};
use x11_dl::xlib;

use crate::x11::XcbConnection;
use std::cell::RefCell;
use std::cell::Cell;
use std::error::Error;
use std::os::raw::{c_int, c_uchar, c_ulong};
use std::panic::AssertUnwindSafe;

thread_local! {
/// Used as part of [`XErrorHandler::handle()`]. When an X11 error occurs during this function,
/// the error gets copied to this RefCell after which the program is allowed to resume. The
/// the error gets copied to this Cell after which the program is allowed to resume. The
/// error can then be converted to a regular Rust Result value afterward.
static CURRENT_X11_ERROR: RefCell<Option<CaughtXLibError>> = const { RefCell::new(None) };
static CURRENT_X11_ERROR: Cell<Option<CaughtXLibError>> = const { Cell::new(None) };
}

/// A helper struct for safe X11 error handling
pub struct XErrorHandler<'a> {
conn: &'a XcbConnection,
error: &'a RefCell<Option<CaughtXLibError>>,
error: &'a Cell<Option<CaughtXLibError>>,
}

impl<'a> XErrorHandler<'a> {
/// Syncs and checks if any previous X11 calls from the given display returned an error
pub fn check(&mut self) -> Result<(), XLibError> {
pub fn check(&self) -> Result<(), XLibError> {
// Flush all possible previous errors
unsafe { (self.conn.xlib.XSync)(self.conn.dpy, 0) };

let error = self.error.borrow_mut().take();
let error = self.error.take();

match error {
None => Ok(()),
Expand All @@ -43,17 +43,16 @@ impl<'a> XErrorHandler<'a> {
unsafe extern "C" fn error_handler(
_dpy: *mut xlib::Display, err: *mut xlib::XErrorEvent,
) -> i32 {
// SAFETY: the error pointer should be safe to access
let err = &*err;
// SAFETY: the error pointer should always be valid for reads
let err = unsafe { err.read() };

CURRENT_X11_ERROR.with(|error| {
let mut error = error.borrow_mut();
match error.as_mut() {
match error.get() {
// If multiple errors occur, keep the first one since that's likely going to be the
// cause of the other errors
Some(_) => 1,
None => {
*error = Some(CaughtXLibError::from_event(err));
error.set(Some(CaughtXLibError::from_event(err)));
0
}
}
Expand All @@ -65,9 +64,7 @@ impl<'a> XErrorHandler<'a> {

CURRENT_X11_ERROR.with(|error| {
// Make sure to clear any errors from the last call to this function
{
*error.borrow_mut() = None;
}
error.set(None);

let old_handler = unsafe { (conn.xlib.XSetErrorHandler)(Some(error_handler)) };
let panic_result = std::panic::catch_unwind(AssertUnwindSafe(|| {
Expand All @@ -85,6 +82,7 @@ impl<'a> XErrorHandler<'a> {
}
}

#[derive(Copy, Clone)]
struct CaughtXLibError {
type_: c_int,
resourceid: xlib::XID,
Expand All @@ -95,7 +93,7 @@ struct CaughtXLibError {
}

impl CaughtXLibError {
fn from_event(error: &xlib::XErrorEvent) -> CaughtXLibError {
fn from_event(error: xlib::XErrorEvent) -> CaughtXLibError {
Self {
type_: error.type_,
resourceid: error.resourceid,
Expand Down
Loading
Loading