use std::ffi::{c_void, CString};
use std::os::raw::{c_int, c_ulong};
use raw_window_handle::{
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use x11::glx;
use x11::xlib;
use super::{GlConfig, GlError, Profile};
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;
extern "C" fn err_handler(
_dpy: *mut xlib::Display,
_err: *mut xlib::XErrorEvent,
) -> i32 {
0
}
fn get_proc_address(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 {
window: c_ulong,
display: *mut xlib::_XDisplay,
context: glx::GLXContext,
}
impl GlContext {
pub fn create(
display: &dyn HasRawDisplayHandle,
window: &dyn HasRawWindowHandle,
config: GlConfig,
shared_context: Option<&GlContext>,
) -> Result<GlContext, GlError> {
let display_handle =
if let RawDisplayHandle::Xlib(display_handle) = display.raw_display_handle() {
display_handle
} else {
return Err(GlError::InvalidWindowHandle);
};
let window_handle = if let RawWindowHandle::Xlib(window_handle) = window.raw_window_handle()
{
window_handle
} else {
return Err(GlError::InvalidWindowHandle);
};
if display_handle.display.is_null() {
return Err(GlError::InvalidWindowHandle);
}
let prev_callback = unsafe { xlib::XSetErrorHandler(Some(err_handler)) };
let display = display_handle.display as *mut xlib::_XDisplay;
let screen = unsafe { xlib::XDefaultScreen(display) };
#[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(display, screen, fb_attribs.as_ptr(), &mut n_configs) };
if n_configs <= 0 {
return Err(GlError::CreationFailed);
}
#[allow(non_snake_case)]
let glXCreateContextAttribsARB: GlXCreateContextAttribsARB = unsafe {
let addr = get_proc_address("glXCreateContextAttribsARB");
if addr.is_null() {
return Err(GlError::CreationFailed);
} else {
std::mem::transmute(addr)
}
};
#[allow(non_snake_case)]
let glXSwapIntervalEXT: GlXSwapIntervalEXT = unsafe {
let addr = get_proc_address("glXSwapIntervalEXT");
if addr.is_null() {
return Err(GlError::CreationFailed);
} else {
std::mem::transmute(addr)
}
};
let profile_mask = match config.profile {
Profile::Core => glx::arb::GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
Profile::Compatibility => glx::arb::GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
};
let mut flags = 0;
if config.use_debug_context {
flags |= glx::arb::GLX_CONTEXT_DEBUG_BIT_ARB;
}
#[rustfmt::skip]
let ctx_attribs = [
glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB, config.version.0 as i32,
glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB, config.version.1 as i32,
glx::arb::GLX_CONTEXT_PROFILE_MASK_ARB, profile_mask,
glx::arb::GLX_CONTEXT_FLAGS_ARB, flags,
0,
];
let shared_context = shared_context
.map(|x| x.context)
.unwrap_or(std::ptr::null_mut());
let context = unsafe {
glXCreateContextAttribsARB(display, *fb_config, shared_context, 1, ctx_attribs.as_ptr())
};
if context.is_null() {
return Err(GlError::CreationFailed);
}
unsafe {
glx::glXMakeCurrent(display, window_handle.window, context);
glXSwapIntervalEXT(display, window_handle.window, config.vsync as i32);
glx::glXMakeCurrent(display, 0, std::ptr::null_mut());
}
unsafe {
xlib::XSetErrorHandler(prev_callback);
}
Ok(GlContext {
window: window_handle.window,
display,
context,
})
}
pub fn make_current(&self) {
unsafe {
glx::glXMakeCurrent(self.display, self.window, self.context);
}
}
pub fn make_not_current(&self) {
unsafe {
glx::glXMakeCurrent(self.display, 0, std::ptr::null_mut());
}
}
pub fn get_proc_address(
&self,
symbol: &str,
) -> *const c_void {
get_proc_address(symbol)
}
pub fn swap_buffers(&self) {
unsafe {
glx::glXSwapBuffers(self.display, self.window);
}
}
}
impl Drop for GlContext {
fn drop(&mut self) {}
}