//! X11 OpenGL context implementation via GLX. use std::ffi::{c_void, CString}; use std::os::raw::{c_int, c_ulong}; use x11::glx; use x11::xlib; use super::{GlConfig, GlError, Profile}; mod errors; #[derive(Debug)] pub enum CreationFailedError { InvalidFBConfig, NoVisual, GetProcAddressFailed, MakeCurrentFailed, ContextCreationFailed, X11Error(errors::XLibError), } impl From for GlError { fn from(e: errors::XLibError) -> Self { GlError::CreationFailed(CreationFailedError::X11Error(e)) } } // 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(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, } /// 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, } /// The configuration a window should be created with after calling /// [GlContext::get_fb_config_and_visual]. pub struct WindowConfig { pub depth: u8, pub visual: u32, } impl GlContext { /// Creating an OpenGL context under X11 works slightly different. Different OpenGL /// configurations require different framebuffer configurations, and to be able to use that /// context with a window the window needs to be created with a matching visual. This means that /// you need to decide on the framebuffer config before creating the window, ask the X11 server /// for a matching visual for that framebuffer config, crate the window with that visual, and /// only then create the OpenGL context. /// /// Use [Self::get_fb_config_and_visual] to create both of these things. pub unsafe fn create( window: c_ulong, display: *mut xlib::_XDisplay, config: FbConfig, ) -> Result { if display.is_null() { return Err(GlError::InvalidWindowHandle); } errors::XErrorHandler::handle(display, |error_handler| { #[allow(non_snake_case)] let glXCreateContextAttribsARB: GlXCreateContextAttribsARB = { let addr = get_proc_address("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("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( display, 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(display, window, context); error_handler.check()?; if res == 0 { return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed)); } glXSwapIntervalEXT(display, window, config.gl_config.vsync as i32); error_handler.check()?; if glx::glXMakeCurrent(display, 0, std::ptr::null_mut()) == 0 { error_handler.check()?; return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed)); } Ok(GlContext { window, display, context }) }) } /// 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 unsafe fn get_fb_config_and_visual( display: *mut xlib::_XDisplay, config: GlConfig, ) -> Result<(FbConfig, WindowConfig), GlError> { errors::XErrorHandler::handle(display, |error_handler| { let screen = 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 = glx::glXChooseFBConfig(display, 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)); } // 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 = *fb_config; let visual = glx::glXGetVisualFromFBConfig(display, fb_config); if visual.is_null() { return Err(GlError::CreationFailed(CreationFailedError::NoVisual)); } 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.display, |error_handler| { let res = glx::glXMakeCurrent(self.display, 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.display, |error_handler| { let res = glx::glXMakeCurrent(self.display, 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(symbol) } pub fn swap_buffers(&self) { unsafe { errors::XErrorHandler::handle(self.display, |error_handler| { glx::glXSwapBuffers(self.display, self.window); error_handler.check().unwrap(); }) } } } impl Drop for GlContext { fn drop(&mut self) { unsafe { glx::glXDestroyContext(self.display, self.context); } } }