diff --git a/src/gl/lib.rs b/src/gl/lib.rs new file mode 100644 index 0000000..9c43878 --- /dev/null +++ b/src/gl/lib.rs @@ -0,0 +1,100 @@ +use raw_window_handle::HasRawWindowHandle; + +use std::ffi::c_void; +use std::marker::PhantomData; + +#[cfg(target_os = "windows")] +mod win; +#[cfg(target_os = "windows")] +use win as platform; + +#[cfg(target_os = "linux")] +mod x11; +#[cfg(target_os = "linux")] +use crate::x11 as platform; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "macos")] +use macos as platform; + +#[derive(Clone, Debug)] +pub struct GlConfig { + pub version: (u8, u8), + pub profile: Profile, + pub red_bits: u8, + pub blue_bits: u8, + pub green_bits: u8, + pub alpha_bits: u8, + pub depth_bits: u8, + pub stencil_bits: u8, + pub samples: Option, + pub srgb: bool, + pub double_buffer: bool, + pub vsync: bool, +} + +impl Default for GlConfig { + fn default() -> Self { + GlConfig { + version: (3, 2), + profile: Profile::Core, + red_bits: 8, + blue_bits: 8, + green_bits: 8, + alpha_bits: 8, + depth_bits: 24, + stencil_bits: 8, + samples: None, + srgb: true, + double_buffer: true, + vsync: false, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Profile { + Compatibility, + Core, +} + +#[derive(Debug)] +pub enum GlError { + InvalidWindowHandle, + VersionNotSupported, + CreationFailed(platform::CreationFailedError), +} + +pub struct GlContext { + context: platform::GlContext, + phantom: PhantomData<*mut ()>, +} + +impl GlContext { + pub unsafe fn create( + parent: &impl HasRawWindowHandle, + config: GlConfig, + ) -> Result { + platform::GlContext::create(parent, config).map(|context| GlContext { + context, + phantom: PhantomData, + }) + } + + pub unsafe fn make_current(&self) { + self.context.make_current(); + } + + pub unsafe fn make_not_current(&self) { + self.context.make_not_current(); + } + + pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.context.get_proc_address(symbol) + } + + pub fn swap_buffers(&self) { + self.context.swap_buffers(); + } +} diff --git a/src/gl/macos.rs b/src/gl/macos.rs new file mode 100644 index 0000000..28f56c1 --- /dev/null +++ b/src/gl/macos.rs @@ -0,0 +1,147 @@ +use std::ffi::c_void; +use std::str::FromStr; + +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use cocoa::appkit::{ + NSOpenGLContext, NSOpenGLContextParameter, NSOpenGLPFAAccelerated, NSOpenGLPFAAlphaSize, + NSOpenGLPFAColorSize, NSOpenGLPFADepthSize, NSOpenGLPFADoubleBuffer, NSOpenGLPFAMultisample, + NSOpenGLPFAOpenGLProfile, NSOpenGLPFASampleBuffers, NSOpenGLPFASamples, NSOpenGLPFAStencilSize, + NSOpenGLPixelFormat, NSOpenGLProfileVersion3_2Core, NSOpenGLProfileVersion4_1Core, + NSOpenGLProfileVersionLegacy, NSOpenGLView, NSView, +}; +use cocoa::base::{id, nil, YES}; + +use core_foundation::base::TCFType; +use core_foundation::bundle::{CFBundleGetBundleWithIdentifier, CFBundleGetFunctionPointerForName}; +use core_foundation::string::CFString; + +use objc::{msg_send, sel, sel_impl}; + +use crate::{GlConfig, GlError, Profile}; + +pub type CreationFailedError = (); +pub struct GlContext { + view: id, + context: id, +} + +impl GlContext { + pub unsafe fn create( + parent: &impl HasRawWindowHandle, + config: GlConfig, + ) -> Result { + let handle = if let RawWindowHandle::MacOS(handle) = parent.raw_window_handle() { + handle + } else { + return Err(GlError::InvalidWindowHandle); + }; + + if handle.ns_view.is_null() { + return Err(GlError::InvalidWindowHandle); + } + + let parent_view = handle.ns_view as id; + + let version = if config.version < (3, 2) && config.profile == Profile::Compatibility { + NSOpenGLProfileVersionLegacy + } else if config.version == (3, 2) && config.profile == Profile::Core { + NSOpenGLProfileVersion3_2Core + } else if config.version > (3, 2) && config.profile == Profile::Core { + NSOpenGLProfileVersion4_1Core + } else { + return Err(GlError::VersionNotSupported); + }; + + #[rustfmt::skip] + let mut attrs = vec![ + NSOpenGLPFAOpenGLProfile as u32, version as u32, + NSOpenGLPFAColorSize as u32, (config.red_bits + config.blue_bits + config.green_bits) as u32, + NSOpenGLPFAAlphaSize as u32, config.alpha_bits as u32, + NSOpenGLPFADepthSize as u32, config.depth_bits as u32, + NSOpenGLPFAStencilSize as u32, config.stencil_bits as u32, + NSOpenGLPFAAccelerated as u32, + ]; + + if config.samples.is_some() { + #[rustfmt::skip] + attrs.extend_from_slice(&[ + NSOpenGLPFAMultisample as u32, + NSOpenGLPFASampleBuffers as u32, 1, + NSOpenGLPFASamples as u32, config.samples.unwrap() as u32, + ]); + } + + if config.double_buffer { + attrs.push(NSOpenGLPFADoubleBuffer as u32); + } + + attrs.push(0); + + let pixel_format = NSOpenGLPixelFormat::alloc(nil).initWithAttributes_(&attrs); + + if pixel_format == nil { + return Err(GlError::CreationFailed(())); + } + + let view = + NSOpenGLView::alloc(nil).initWithFrame_pixelFormat_(parent_view.frame(), pixel_format); + + if view == nil { + return Err(GlError::CreationFailed(())); + } + + view.setWantsBestResolutionOpenGLSurface_(YES); + + let () = msg_send![view, retain]; + NSOpenGLView::display_(view); + parent_view.addSubview_(view); + + let context: id = msg_send![view, openGLContext]; + let () = msg_send![context, retain]; + + context.setValues_forParameter_( + &(config.vsync as i32), + NSOpenGLContextParameter::NSOpenGLCPSwapInterval, + ); + + let () = msg_send![pixel_format, release]; + + Ok(GlContext { view, context }) + } + + pub unsafe fn make_current(&self) { + self.context.makeCurrentContext(); + } + + pub unsafe fn make_not_current(&self) { + NSOpenGLContext::clearCurrentContext(self.context); + } + + pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + let symbol_name = CFString::from_str(symbol).unwrap(); + let framework_name = CFString::from_str("com.apple.opengl").unwrap(); + let framework = + unsafe { CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()) }; + let addr = unsafe { + CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef()) + }; + addr as *const c_void + } + + pub fn swap_buffers(&self) { + unsafe { + self.context.flushBuffer(); + let () = msg_send![self.view, setNeedsDisplay: YES]; + } + } +} + +impl Drop for GlContext { + fn drop(&mut self) { + unsafe { + let () = msg_send![self.context, release]; + let () = msg_send![self.view, release]; + } + } +} diff --git a/src/gl/win.rs b/src/gl/win.rs new file mode 100644 index 0000000..5594ae9 --- /dev/null +++ b/src/gl/win.rs @@ -0,0 +1,312 @@ +use std::ffi::{c_void, CString, OsStr}; +use std::os::windows::ffi::OsStrExt; + +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use winapi::shared::minwindef::{HINSTANCE, HMODULE}; +use winapi::shared::ntdef::WCHAR; +use winapi::shared::windef::{HDC, HGLRC, HWND}; +use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryA}; +use winapi::um::wingdi::{ + wglCreateContext, wglDeleteContext, wglGetProcAddress, wglMakeCurrent, ChoosePixelFormat, + DescribePixelFormat, SetPixelFormat, SwapBuffers, PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, + PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, +}; +use winapi::um::winnt::IMAGE_DOS_HEADER; +use winapi::um::winuser::{ + CreateWindowExW, DefWindowProcW, DestroyWindow, GetDC, RegisterClassW, ReleaseDC, + UnregisterClassW, CS_OWNDC, CW_USEDEFAULT, WNDCLASSW, +}; + +use crate::{GlConfig, GlError, Profile}; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt + +type WglCreateContextAttribsARB = extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; + +const WGL_CONTEXT_MAJOR_VERSION_ARB: i32 = 0x2091; +const WGL_CONTEXT_MINOR_VERSION_ARB: i32 = 0x2092; +const WGL_CONTEXT_PROFILE_MASK_ARB: i32 = 0x9126; + +const WGL_CONTEXT_CORE_PROFILE_BIT_ARB: i32 = 0x00000001; +const WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: i32 = 0x00000002; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt + +type WglChoosePixelFormatARB = + extern "system" fn(HDC, *const i32, *const f32, u32, *mut i32, *mut u32) -> i32; + +const WGL_DRAW_TO_WINDOW_ARB: i32 = 0x2001; +const WGL_ACCELERATION_ARB: i32 = 0x2003; +const WGL_SUPPORT_OPENGL_ARB: i32 = 0x2010; +const WGL_DOUBLE_BUFFER_ARB: i32 = 0x2011; +const WGL_PIXEL_TYPE_ARB: i32 = 0x2013; +const WGL_RED_BITS_ARB: i32 = 0x2015; +const WGL_GREEN_BITS_ARB: i32 = 0x2017; +const WGL_BLUE_BITS_ARB: i32 = 0x2019; +const WGL_ALPHA_BITS_ARB: i32 = 0x201B; +const WGL_DEPTH_BITS_ARB: i32 = 0x2022; +const WGL_STENCIL_BITS_ARB: i32 = 0x2023; + +const WGL_FULL_ACCELERATION_ARB: i32 = 0x2027; +const WGL_TYPE_RGBA_ARB: i32 = 0x202B; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_multisample.txt + +const WGL_SAMPLE_BUFFERS_ARB: i32 = 0x2041; +const WGL_SAMPLES_ARB: i32 = 0x2042; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt + +const WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20A9; + +// See https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt + +type WglSwapIntervalEXT = extern "system" fn(i32) -> i32; + +pub type CreationFailedError = (); +pub struct GlContext { + hwnd: HWND, + hdc: HDC, + hglrc: HGLRC, + gl_library: HMODULE, +} + +extern "C" { + static __ImageBase: IMAGE_DOS_HEADER; +} + +impl GlContext { + pub unsafe fn create( + parent: &impl HasRawWindowHandle, + config: GlConfig, + ) -> Result { + let handle = if let RawWindowHandle::Windows(handle) = parent.raw_window_handle() { + handle + } else { + return Err(GlError::InvalidWindowHandle); + }; + + if handle.hwnd.is_null() { + return Err(GlError::InvalidWindowHandle); + } + + // Create temporary window and context to load function pointers + + let class_name_str = format!("raw-gl-context-window-{}", uuid::Uuid::new_v4().to_simple()); + let mut class_name: Vec = OsStr::new(&class_name_str).encode_wide().collect(); + class_name.push(0); + + let hinstance = &__ImageBase as *const IMAGE_DOS_HEADER as HINSTANCE; + + let wnd_class = WNDCLASSW { + style: CS_OWNDC, + lpfnWndProc: Some(DefWindowProcW), + hInstance: hinstance, + lpszClassName: class_name.as_ptr(), + ..std::mem::zeroed() + }; + + let class = RegisterClassW(&wnd_class); + if class == 0 { + return Err(GlError::CreationFailed(())); + } + + let hwnd_tmp = CreateWindowExW( + 0, + class as *const WCHAR, + [0].as_ptr(), + 0, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + std::ptr::null_mut(), + std::ptr::null_mut(), + hinstance, + std::ptr::null_mut(), + ); + + if hwnd_tmp.is_null() { + return Err(GlError::CreationFailed(())); + } + + let hdc_tmp = GetDC(hwnd_tmp); + + let pfd_tmp = PIXELFORMATDESCRIPTOR { + nSize: std::mem::size_of::() as u16, + nVersion: 1, + dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + iPixelType: PFD_TYPE_RGBA, + cColorBits: 32, + cAlphaBits: 8, + cDepthBits: 24, + cStencilBits: 8, + iLayerType: PFD_MAIN_PLANE, + ..std::mem::zeroed() + }; + + SetPixelFormat(hdc_tmp, ChoosePixelFormat(hdc_tmp, &pfd_tmp), &pfd_tmp); + + let hglrc_tmp = wglCreateContext(hdc_tmp); + if hglrc_tmp.is_null() { + ReleaseDC(hwnd_tmp, hdc_tmp); + UnregisterClassW(class as *const WCHAR, hinstance); + DestroyWindow(hwnd_tmp); + return Err(GlError::CreationFailed(())); + } + + wglMakeCurrent(hdc_tmp, hglrc_tmp); + + #[allow(non_snake_case)] + let wglCreateContextAttribsARB: Option = { + let symbol = CString::new("wglCreateContextAttribsARB").unwrap(); + let addr = wglGetProcAddress(symbol.as_ptr()); + if !addr.is_null() { + Some(std::mem::transmute(addr)) + } else { + None + } + }; + + #[allow(non_snake_case)] + let wglChoosePixelFormatARB: Option = { + let symbol = CString::new("wglChoosePixelFormatARB").unwrap(); + let addr = wglGetProcAddress(symbol.as_ptr()); + if !addr.is_null() { + Some(std::mem::transmute(addr)) + } else { + None + } + }; + + #[allow(non_snake_case)] + let wglSwapIntervalEXT: Option = { + let symbol = CString::new("wglSwapIntervalEXT").unwrap(); + let addr = wglGetProcAddress(symbol.as_ptr()); + if !addr.is_null() { + Some(std::mem::transmute(addr)) + } else { + None + } + }; + + wglMakeCurrent(hdc_tmp, std::ptr::null_mut()); + ReleaseDC(hwnd_tmp, hdc_tmp); + UnregisterClassW(class as *const WCHAR, hinstance); + DestroyWindow(hwnd_tmp); + + // Create actual context + + let hwnd = handle.hwnd as HWND; + + let hdc = GetDC(hwnd); + + #[rustfmt::skip] + let pixel_format_attribs = [ + WGL_DRAW_TO_WINDOW_ARB, 1, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_DOUBLE_BUFFER_ARB, config.double_buffer as i32, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_RED_BITS_ARB, config.red_bits as i32, + WGL_GREEN_BITS_ARB, config.green_bits as i32, + WGL_BLUE_BITS_ARB, config.blue_bits as i32, + WGL_ALPHA_BITS_ARB, config.alpha_bits as i32, + WGL_DEPTH_BITS_ARB, config.depth_bits as i32, + WGL_STENCIL_BITS_ARB, config.stencil_bits as i32, + WGL_SAMPLE_BUFFERS_ARB, config.samples.is_some() as i32, + WGL_SAMPLES_ARB, config.samples.unwrap_or(0) as i32, + WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32, + 0, + ]; + + let mut pixel_format = 0; + let mut num_formats = 0; + wglChoosePixelFormatARB.unwrap()( + hdc, + pixel_format_attribs.as_ptr(), + std::ptr::null(), + 1, + &mut pixel_format, + &mut num_formats, + ); + + let mut pfd: PIXELFORMATDESCRIPTOR = std::mem::zeroed(); + DescribePixelFormat( + hdc, + pixel_format, + std::mem::size_of::() as u32, + &mut pfd, + ); + SetPixelFormat(hdc, pixel_format, &pfd); + + let profile_mask = match config.profile { + Profile::Core => WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + Profile::Compatibility => WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + }; + + #[rustfmt::skip] + let ctx_attribs = [ + WGL_CONTEXT_MAJOR_VERSION_ARB, config.version.0 as i32, + WGL_CONTEXT_MINOR_VERSION_ARB, config.version.1 as i32, + WGL_CONTEXT_PROFILE_MASK_ARB, profile_mask, + 0 + ]; + + let hglrc = + wglCreateContextAttribsARB.unwrap()(hdc, std::ptr::null_mut(), ctx_attribs.as_ptr()); + if hglrc == std::ptr::null_mut() { + return Err(GlError::CreationFailed(())); + } + + let gl_library_name = CString::new("opengl32.dll").unwrap(); + let gl_library = LoadLibraryA(gl_library_name.as_ptr()); + + wglMakeCurrent(hdc, hglrc); + wglSwapIntervalEXT.unwrap()(config.vsync as i32); + wglMakeCurrent(hdc, std::ptr::null_mut()); + + Ok(GlContext { + hwnd, + hdc, + hglrc, + gl_library, + }) + } + + pub unsafe fn make_current(&self) { + wglMakeCurrent(self.hdc, self.hglrc); + } + + pub unsafe fn make_not_current(&self) { + wglMakeCurrent(self.hdc, std::ptr::null_mut()); + } + + pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + let symbol = CString::new(symbol).unwrap(); + let addr = unsafe { wglGetProcAddress(symbol.as_ptr()) as *const c_void }; + if !addr.is_null() { + addr + } else { + unsafe { GetProcAddress(self.gl_library, symbol.as_ptr()) as *const c_void } + } + } + + pub fn swap_buffers(&self) { + unsafe { + SwapBuffers(self.hdc); + } + } +} + +impl Drop for GlContext { + fn drop(&mut self) { + unsafe { + wglMakeCurrent(std::ptr::null_mut(), std::ptr::null_mut()); + wglDeleteContext(self.hglrc); + ReleaseDC(self.hwnd, self.hdc); + FreeLibrary(self.gl_library); + } + } +} diff --git a/src/gl/x11.rs b/src/gl/x11.rs new file mode 100644 index 0000000..ced977d --- /dev/null +++ b/src/gl/x11.rs @@ -0,0 +1,231 @@ +use std::ffi::{c_void, CString}; +use std::os::raw::{c_int, c_ulong}; + +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use x11::glx; +use x11::xlib; + +use crate::{GlConfig, GlError, Profile}; + +mod errors; + +#[derive(Debug)] +pub enum CreationFailedError { + InvalidFBConfig, + 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, +} + +impl GlContext { + pub unsafe fn create( + parent: &impl HasRawWindowHandle, + config: GlConfig, + ) -> Result { + let handle = if let RawWindowHandle::Xlib(handle) = parent.raw_window_handle() { + handle + } else { + return Err(GlError::InvalidWindowHandle); + }; + + if handle.display.is_null() { + return Err(GlError::InvalidWindowHandle); + } + + let display = handle.display as *mut xlib::_XDisplay; + + errors::XErrorHandler::handle(display, |error_handler| { + 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) + }; + + error_handler.check()?; + + if n_configs <= 0 { + return Err(GlError::CreationFailed( + CreationFailedError::InvalidFBConfig, + )); + } + + #[allow(non_snake_case)] + let glXCreateContextAttribsARB: GlXCreateContextAttribsARB = unsafe { + let addr = get_proc_address("glXCreateContextAttribsARB"); + if addr.is_null() { + return Err(GlError::CreationFailed( + CreationFailedError::GetProcAddressFailed, + )); + } 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( + CreationFailedError::GetProcAddressFailed, + )); + } else { + std::mem::transmute(addr) + } + }; + + error_handler.check()?; + + 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, + }; + + #[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, + 0, + ]; + + let context = unsafe { + glXCreateContextAttribsARB( + display, + *fb_config, + std::ptr::null_mut(), + 1, + ctx_attribs.as_ptr(), + ) + }; + + error_handler.check()?; + + if context.is_null() { + return Err(GlError::CreationFailed( + CreationFailedError::ContextCreationFailed, + )); + } + + unsafe { + let res = glx::glXMakeCurrent(display, handle.window, context); + error_handler.check()?; + if res == 0 { + return Err(GlError::CreationFailed( + CreationFailedError::MakeCurrentFailed, + )); + } + + glXSwapIntervalEXT(display, handle.window, 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: handle.window, + display, + context, + }) + }) + } + + 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) { + errors::XErrorHandler::handle(self.display, |error_handler| { + unsafe { + glx::glXSwapBuffers(self.display, self.window); + } + error_handler.check().unwrap(); + }) + } +} + +impl Drop for GlContext { + fn drop(&mut self) {} +} diff --git a/src/gl/x11/errors.rs b/src/gl/x11/errors.rs new file mode 100644 index 0000000..ec1be08 --- /dev/null +++ b/src/gl/x11/errors.rs @@ -0,0 +1,131 @@ +use std::ffi::CStr; +use std::fmt::{Debug, Formatter}; +use x11::xlib; + +use std::panic::AssertUnwindSafe; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::sync::Mutex; + +static CURRENT_ERROR_PTR: AtomicPtr>> = + AtomicPtr::new(::std::ptr::null_mut()); + +/// A helper struct for safe X11 error handling +pub struct XErrorHandler<'a> { + display: *mut xlib::Display, + mutex: &'a Mutex>, +} + +impl<'a> XErrorHandler<'a> { + /// Syncs and checks if any previous X11 calls returned an error + pub fn check(&mut self) -> Result<(), XLibError> { + // Flush all possible previous errors + unsafe { + xlib::XSync(self.display, 0); + } + let error = self.mutex.lock().unwrap().take(); + + match error { + None => Ok(()), + Some(inner) => Err(XLibError { inner }), + } + } + + /// Sets up a temporary X11 error handler for the duration of the given closure, and allows + /// that closure to check on the latest X11 error at any time + pub fn handle T>( + display: *mut xlib::Display, + handler: F, + ) -> T { + unsafe extern "C" fn error_handler( + _dpy: *mut xlib::Display, + err: *mut xlib::XErrorEvent, + ) -> i32 { + // SAFETY: the error pointer should be safe to copy + let err = *err; + // SAFETY: the ptr can only point to a valid instance or be null + let mutex = CURRENT_ERROR_PTR.load(Ordering::SeqCst).as_ref(); + let mutex = if let Some(mutex) = mutex { + mutex + } else { + return 2; + }; + + match mutex.lock() { + Ok(mut current_error) => { + *current_error = Some(err); + 0 + } + Err(e) => { + eprintln!( + "[FATAL] raw-gl-context: Failed to lock for X11 Error Handler: {:?}", + e + ); + 1 + } + } + } + + // Flush all possible previous errors + unsafe { + xlib::XSync(display, 0); + } + + let mut mutex = Mutex::new(None); + CURRENT_ERROR_PTR.store(&mut mutex, Ordering::SeqCst); + + let old_handler = unsafe { xlib::XSetErrorHandler(Some(error_handler)) }; + let panic_result = std::panic::catch_unwind(AssertUnwindSafe(|| { + let mut h = XErrorHandler { + display, + mutex: &mutex, + }; + handler(&mut h) + })); + // Whatever happened, restore old error handler + unsafe { xlib::XSetErrorHandler(old_handler) }; + CURRENT_ERROR_PTR.store(::core::ptr::null_mut(), Ordering::SeqCst); + + match panic_result { + Ok(v) => v, + Err(e) => std::panic::resume_unwind(e), + } + } +} + +pub struct XLibError { + inner: xlib::XErrorEvent, +} + +impl XLibError { + pub fn get_display_name(&self, buf: &mut [u8]) -> &CStr { + unsafe { + xlib::XGetErrorText( + self.inner.display, + self.inner.error_code.into(), + buf.as_mut_ptr().cast(), + (buf.len() - 1) as i32, + ); + } + + *buf.last_mut().unwrap() = 0; + // SAFETY: whatever XGetErrorText did or not, we guaranteed there is a nul byte at the end of the buffer + unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) } + } +} + +impl Debug for XLibError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut buf = [0; 255]; + let display_name = self.get_display_name(&mut buf).to_string_lossy(); + + f.debug_struct("XLibError") + .field("error_code", &self.inner.error_code) + .field("error_message", &display_name) + .field("minor_code", &self.inner.minor_code) + .field("request_code", &self.inner.request_code) + .field("type", &self.inner.type_) + .field("resource_id", &self.inner.resourceid) + .field("serial", &self.inner.serial) + .finish() + } +}