use super::{GlConfig, GlError, Profile};
use crate::x11::XcbConnection;
use std::ffi::{c_void, CString};
use std::os::raw::{c_int, c_ulong};
use std::rc::Rc;
use x11_dl::error::OpenError;
use x11_dl::glx;
use x11_dl::glx::Glx;
use x11_dl::xlib;
mod errors;
#[derive(Debug)]
pub enum CreationFailedError {
InvalidFBConfig,
NoVisual,
GetProcAddressFailed,
MakeCurrentFailed,
ContextCreationFailed,
X11Error(errors::XLibError),
OpenError(OpenError),
}
impl From<errors::XLibError> for GlError {
fn from(e: errors::XLibError) -> Self {
GlError::CreationFailed(CreationFailedError::X11Error(e))
}
}
impl From<OpenError> for GlError {
fn from(e: OpenError) -> Self {
GlError::CreationFailed(CreationFailedError::OpenError(e))
}
}
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;
type GlXSwapIntervalEXT =
unsafe extern "C" fn(dpy: *mut xlib::Display, drawable: glx::GLXDrawable, interval: i32);
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,
}
pub struct FbConfig {
gl_config: GlConfig,
fb_config: *mut glx::__GLXFBConfigRec,
}
pub struct WindowConfig {
pub depth: u8,
pub visual: u32,
}
impl GlContext {
pub unsafe fn create(
window: c_ulong, connection: Rc<XcbConnection>, config: FbConfig,
) -> 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)
}
};
#[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)
}
};
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,
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));
}
Ok(context)
})?;
Ok(GlContext { glx, window, connection, context })
}
pub fn get_fb_config_and_visual(
conn: &XcbConnection, config: GlConfig,
) -> Result<(FbConfig, WindowConfig), GlError> {
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 = 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() };
Ok((
FbConfig { fb_config, gl_config: config },
WindowConfig { depth: visual.depth as u8, visual: visual.visualid as u32 },
))
})
}
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")
}
})
}
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")
}
})
}
pub fn get_proc_address(&self, symbol: &str) -> *const c_void {
get_proc_address(&self.glx, symbol)
}
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();
})
}
}
}
impl Drop for GlContext {
fn drop(&mut self) {
unsafe {
(self.glx.glXDestroyContext)(self.connection.dpy, self.context);
}
}
}