Skip to content

Commit 3551d5e

Browse files
committed
Implement OpenGL contexts for X11
This should in theory work! When requesting an OpenGL context, the window visual is determined based on the matched framebuffer config.
1 parent b4a3d2b commit 3551d5e

3 files changed

Lines changed: 169 additions & 59 deletions

File tree

src/gl/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ mod win;
88
#[cfg(target_os = "windows")]
99
use win as platform;
1010

11+
// We need to use this directly within the X11 window creation to negotiate the correct visual
1112
#[cfg(target_os = "linux")]
12-
mod x11;
13+
pub(crate) mod x11;
1314
#[cfg(target_os = "linux")]
14-
use self::x11 as platform;
15+
pub(crate) use self::x11 as platform;
1516

1617
#[cfg(target_os = "macos")]
1718
mod macos;
@@ -72,13 +73,22 @@ pub struct GlContext {
7273
}
7374

7475
impl GlContext {
76+
#[cfg(not(target_os = "linux"))]
7577
pub(crate) unsafe fn create(
7678
parent: &impl HasRawWindowHandle, config: GlConfig,
7779
) -> Result<GlContext, GlError> {
7880
platform::GlContext::create(parent, config)
7981
.map(|context| GlContext { context, phantom: PhantomData })
8082
}
8183

84+
/// The X11 version needs to be set up in a different way compared to the Windows and macOS
85+
/// versions. So the platform-specific versions should be used to construct the context within
86+
/// baseview, and then this object can be passed to the user.
87+
#[cfg(target_os = "linux")]
88+
pub(crate) fn new(context: platform::GlContext) -> GlContext {
89+
GlContext { context, phantom: PhantomData }
90+
}
91+
8292
pub unsafe fn make_current(&self) {
8393
self.context.make_current();
8494
}

src/gl/x11.rs

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod errors;
1313
#[derive(Debug)]
1414
pub enum CreationFailedError {
1515
InvalidFBConfig,
16+
NoVisual,
1617
GetProcAddressFailed,
1718
MakeCurrentFailed,
1819
ContextCreationFailed,
@@ -55,9 +56,31 @@ pub struct GlContext {
5556
context: glx::GLXContext,
5657
}
5758

59+
/// The frame buffer configuration along with the general OpenGL configuration to somewhat minimize
60+
/// misuse.
61+
pub struct FbConfig {
62+
gl_config: GlConfig,
63+
fb_config: *mut glx::__GLXFBConfigRec,
64+
}
65+
66+
/// The configuration a window should be created with after calling
67+
/// [GlContext::get_fb_config_and_visual].
68+
pub struct WindowConfig {
69+
pub depth: u8,
70+
pub visual: u32,
71+
}
72+
5873
impl GlContext {
74+
/// Creating an OpenGL context under X11 works slightly different. Different OpenGL
75+
/// configurations require different framebuffer configurations, and to be able to use that
76+
/// context with a window the window needs to be created with a matching visual. This means that
77+
/// you need to decide on the framebuffer config before creating the window, ask the X11 server
78+
/// for a matching visual for that framebuffer config, crate the window with that visual, and
79+
/// only then create the OpenGL context.
80+
///
81+
/// Use [Self::get_fb_config_and_visual] to create both of these things.
5982
pub unsafe fn create(
60-
parent: &impl HasRawWindowHandle, config: GlConfig,
83+
parent: &impl HasRawWindowHandle, config: FbConfig,
6184
) -> Result<GlContext, GlError> {
6285
let handle = if let RawWindowHandle::Xlib(handle) = parent.raw_window_handle() {
6386
handle
@@ -72,38 +95,6 @@ impl GlContext {
7295
let display = handle.display as *mut xlib::_XDisplay;
7396

7497
errors::XErrorHandler::handle(display, |error_handler| {
75-
let screen = unsafe { xlib::XDefaultScreen(display) };
76-
77-
#[rustfmt::skip]
78-
let fb_attribs = [
79-
glx::GLX_X_RENDERABLE, 1,
80-
glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR,
81-
glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT,
82-
glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT,
83-
glx::GLX_RED_SIZE, config.red_bits as i32,
84-
glx::GLX_GREEN_SIZE, config.green_bits as i32,
85-
glx::GLX_BLUE_SIZE, config.blue_bits as i32,
86-
glx::GLX_ALPHA_SIZE, config.alpha_bits as i32,
87-
glx::GLX_DEPTH_SIZE, config.depth_bits as i32,
88-
glx::GLX_STENCIL_SIZE, config.stencil_bits as i32,
89-
glx::GLX_DOUBLEBUFFER, config.double_buffer as i32,
90-
glx::GLX_SAMPLE_BUFFERS, config.samples.is_some() as i32,
91-
glx::GLX_SAMPLES, config.samples.unwrap_or(0) as i32,
92-
GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32,
93-
0,
94-
];
95-
96-
let mut n_configs = 0;
97-
let fb_config = unsafe {
98-
glx::glXChooseFBConfig(display, screen, fb_attribs.as_ptr(), &mut n_configs)
99-
};
100-
101-
error_handler.check()?;
102-
103-
if n_configs <= 0 {
104-
return Err(GlError::CreationFailed(CreationFailedError::InvalidFBConfig));
105-
}
106-
10798
#[allow(non_snake_case)]
10899
let glXCreateContextAttribsARB: GlXCreateContextAttribsARB = unsafe {
109100
let addr = get_proc_address("glXCreateContextAttribsARB");
@@ -126,23 +117,23 @@ impl GlContext {
126117

127118
error_handler.check()?;
128119

129-
let profile_mask = match config.profile {
120+
let profile_mask = match config.gl_config.profile {
130121
Profile::Core => glx::arb::GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
131122
Profile::Compatibility => glx::arb::GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
132123
};
133124

134125
#[rustfmt::skip]
135126
let ctx_attribs = [
136-
glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB, config.version.0 as i32,
137-
glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB, config.version.1 as i32,
127+
glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB, config.gl_config.version.0 as i32,
128+
glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB, config.gl_config.version.1 as i32,
138129
glx::arb::GLX_CONTEXT_PROFILE_MASK_ARB, profile_mask,
139130
0,
140131
];
141132

142133
let context = unsafe {
143134
glXCreateContextAttribsARB(
144135
display,
145-
*fb_config,
136+
config.fb_config,
146137
std::ptr::null_mut(),
147138
1,
148139
ctx_attribs.as_ptr(),
@@ -162,7 +153,7 @@ impl GlContext {
162153
return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed));
163154
}
164155

165-
glXSwapIntervalEXT(display, handle.window, config.vsync as i32);
156+
glXSwapIntervalEXT(display, handle.window, config.gl_config.vsync as i32);
166157
error_handler.check()?;
167158

168159
if glx::glXMakeCurrent(display, 0, std::ptr::null_mut()) == 0 {
@@ -175,6 +166,59 @@ impl GlContext {
175166
})
176167
}
177168

169+
/// Find a matching framebuffer config and window visual for the given OpenGL configuration.
170+
/// This needs to be passed to [Self::create] along with a handle to a window that was created
171+
/// using the visual also returned from this function.
172+
pub unsafe fn get_fb_config_and_visual(
173+
display: *mut xlib::_XDisplay, config: GlConfig,
174+
) -> Result<(FbConfig, WindowConfig), GlError> {
175+
errors::XErrorHandler::handle(display, |error_handler| {
176+
let screen = unsafe { xlib::XDefaultScreen(display) };
177+
178+
#[rustfmt::skip]
179+
let fb_attribs = [
180+
glx::GLX_X_RENDERABLE, 1,
181+
glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR,
182+
glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT,
183+
glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT,
184+
glx::GLX_RED_SIZE, config.red_bits as i32,
185+
glx::GLX_GREEN_SIZE, config.green_bits as i32,
186+
glx::GLX_BLUE_SIZE, config.blue_bits as i32,
187+
glx::GLX_ALPHA_SIZE, config.alpha_bits as i32,
188+
glx::GLX_DEPTH_SIZE, config.depth_bits as i32,
189+
glx::GLX_STENCIL_SIZE, config.stencil_bits as i32,
190+
glx::GLX_DOUBLEBUFFER, config.double_buffer as i32,
191+
glx::GLX_SAMPLE_BUFFERS, config.samples.is_some() as i32,
192+
glx::GLX_SAMPLES, config.samples.unwrap_or(0) as i32,
193+
GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32,
194+
0,
195+
];
196+
197+
let mut n_configs = 0;
198+
let fb_config = unsafe {
199+
glx::glXChooseFBConfig(display, screen, fb_attribs.as_ptr(), &mut n_configs)
200+
};
201+
202+
error_handler.check()?;
203+
if n_configs <= 0 || fb_config.is_null() {
204+
return Err(GlError::CreationFailed(CreationFailedError::InvalidFBConfig));
205+
}
206+
207+
// Now that we have a matching framebuffer config, we need to know which visual matches
208+
// thsi config so the window is compatible with the OpenGL context we're about to create
209+
let fb_config = *fb_config;
210+
let visual = glx::glXGetVisualFromFBConfig(display, fb_config);
211+
if visual.is_null() {
212+
return Err(GlError::CreationFailed(CreationFailedError::NoVisual));
213+
}
214+
215+
Ok((
216+
FbConfig { fb_config, gl_config: config },
217+
WindowConfig { depth: (*visual).depth as u8, visual: (*visual).visualid as u32 },
218+
))
219+
})
220+
}
221+
178222
pub unsafe fn make_current(&self) {
179223
errors::XErrorHandler::handle(self.display, |error_handler| {
180224
let res = glx::glXMakeCurrent(self.display, self.window, self.context);

src/x11/window.rs

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use std::thread;
77
use std::time::*;
88

99
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, XlibHandle};
10+
use xcb::ffi::xcb_screen_t;
11+
use xcb::StructPtr;
1012

1113
use super::XcbConnection;
1214
use crate::{
@@ -17,7 +19,7 @@ use crate::{
1719
use super::keyboard::{convert_key_press_event, convert_key_release_event};
1820

1921
#[cfg(feature = "opengl")]
20-
use crate::gl::GlContext;
22+
use crate::gl::{platform, GlContext};
2123

2224
pub struct WindowHandle {
2325
raw_window_handle: Option<RawWindowHandle>,
@@ -107,6 +109,12 @@ pub struct Window {
107109
// Hack to allow sending a RawWindowHandle between threads. Do not make public
108110
struct SendableRwh(RawWindowHandle);
109111

112+
/// Quick wrapper to satisfy [HasRawWindowHandle], because of course a raw window handle wouldn't
113+
/// have a raw window handle, that would be silly.
114+
struct RawWindowHandleWrapper {
115+
handle: RawWindowHandle,
116+
}
117+
110118
unsafe impl Send for SendableRwh {}
111119

112120
type WindowOpenResult = Result<SendableRwh, ()>;
@@ -211,23 +219,33 @@ impl Window {
211219

212220
let window_info = WindowInfo::from_logical_size(options.size, scaling);
213221

214-
// The window need to have a depth of 32 so so we can create graphics contexts with alpha
215-
// buffers
216-
let mut depth = xcb::COPY_FROM_PARENT as u8;
217-
let mut visual = xcb::COPY_FROM_PARENT as u32;
218-
'match_visual: for candidate_depth in screen.allowed_depths() {
219-
if candidate_depth.depth() != 32 {
220-
continue;
221-
}
222-
223-
for candidate_visual in candidate_depth.visuals() {
224-
if candidate_visual.class() == xcb::VISUAL_CLASS_TRUE_COLOR as u8 {
225-
depth = candidate_depth.depth();
226-
visual = candidate_visual.visual_id();
227-
break 'match_visual;
228-
}
229-
}
230-
}
222+
// Now it starts becoming fun. If we're creating an OpenGL context, then we need to create
223+
// the window with a visual that matches the framebuffer used for the OpenGL context. So the
224+
// idea is that we first retrieve a framebuffer config that matches our wanted OpenGL
225+
// configuration, find the visual that matches that framebuffer config, create the window
226+
// with that visual, and then finally create an OpenGL context for the window. If we don't
227+
// use OpenGL, then we'll just take a random visual with a 32-bit depth.
228+
let create_default_config = || {
229+
Self::find_visual_for_depth(&screen, 32)
230+
.map(|visual| (32, visual))
231+
.unwrap_or((xcb::COPY_FROM_PARENT as u8, xcb::COPY_FROM_PARENT as u32))
232+
};
233+
#[cfg(feature = "opengl")]
234+
let (fb_config, (depth, visual)) = match options.gl_config {
235+
Some(gl_config) => unsafe {
236+
platform::GlContext::get_fb_config_and_visual(
237+
xcb_connection.conn.get_raw_dpy(),
238+
gl_config,
239+
)
240+
.map(|(fb_config, window_config)| {
241+
(Some(fb_config), (window_config.depth, window_config.visual))
242+
})
243+
.expect("Could not fetch framebuffer config")
244+
},
245+
None => (None, create_default_config()),
246+
};
247+
#[cfg(not(feature = "opengl"))]
248+
let (depth, visual) = create_default_config();
231249

232250
// For this 32-bith depth to work, you also need to define a color map and set a border
233251
// pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818
@@ -300,6 +318,22 @@ impl Window {
300318

301319
xcb_connection.conn.flush();
302320

321+
// TODO: These APIs could use a couple tweaks now that everything is internal and there is
322+
// no error handling anymore at this point. Everything is more or less unchanged
323+
// compared to when raw-gl-context was a separate crate.
324+
#[cfg(feature = "opengl")]
325+
let gl_context = fb_config.map(|fb_config| {
326+
let mut handle = XlibHandle::empty();
327+
handle.window = window_id as c_ulong;
328+
handle.display = xcb_connection.conn.get_raw_dpy() as *mut c_void;
329+
let handle = RawWindowHandleWrapper { handle: RawWindowHandle::Xlib(handle) };
330+
331+
// Because of the visual negotation we had to take some extra steps to create this context
332+
let context = unsafe { platform::GlContext::create(&handle, fb_config) }
333+
.expect("Could not create OpenGL context");
334+
GlContext::new(context)
335+
});
336+
303337
let mut window = Self {
304338
xcb_connection,
305339
window_id,
@@ -314,7 +348,7 @@ impl Window {
314348
parent_handle,
315349

316350
#[cfg(feature = "opengl")]
317-
gl_context: todo!("Create the X11 OpenGL context"),
351+
gl_context,
318352
};
319353

320354
let mut handler = build(&mut crate::Window::new(&mut window));
@@ -360,6 +394,22 @@ impl Window {
360394
self.gl_context.as_ref()
361395
}
362396

397+
fn find_visual_for_depth(screen: &StructPtr<xcb_screen_t>, depth: u8) -> Option<u32> {
398+
for candidate_depth in screen.allowed_depths() {
399+
if candidate_depth.depth() != depth {
400+
continue;
401+
}
402+
403+
for candidate_visual in candidate_depth.visuals() {
404+
if candidate_visual.class() == xcb::VISUAL_CLASS_TRUE_COLOR as u8 {
405+
return Some(candidate_visual.visual_id());
406+
}
407+
}
408+
}
409+
410+
None
411+
}
412+
363413
#[inline]
364414
fn drain_xcb_events(&mut self, handler: &mut dyn WindowHandler) {
365415
// the X server has a tendency to send spurious/extraneous configure notify events when a
@@ -617,6 +667,12 @@ unsafe impl HasRawWindowHandle for Window {
617667
}
618668
}
619669

670+
unsafe impl HasRawWindowHandle for RawWindowHandleWrapper {
671+
fn raw_window_handle(&self) -> RawWindowHandle {
672+
self.handle
673+
}
674+
}
675+
620676
fn mouse_id(id: u8) -> MouseButton {
621677
match id {
622678
1 => MouseButton::Left,

0 commit comments

Comments
 (0)