1
0
Fork 0

Merge pull request #10 from crsaracco/master

OpenGL surface for X11/XCB
This commit is contained in:
Mirko Covizzi 2020-05-31 21:48:40 +02:00 committed by GitHub
commit 9d4d82f9f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 296 additions and 51 deletions

View file

@ -11,12 +11,12 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: install XCB dependencies
- name: Install XCB and GL dependencies
run: |
sudo apt update
sudo apt install libx11-xcb-dev libxcb-dri2-0-dev
sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev
if: contains(matrix.os, 'ubuntu')
- name: install rust stable
- name: Install rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable

View file

@ -10,13 +10,14 @@ authors = [
edition = "2018"
[dependencies]
gl = "0.14.0"
[target.'cfg(target_os="linux")'.dependencies]
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
x11 = { version = "2.3", features = ["xlib", "glx"]}
[target.'cfg(target_os="windows")'.dependencies]
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi"] }
gl = "0.14.0"
[target.'cfg(target_os="macos")'.dependencies]
cocoa = "0.20.1"

View file

@ -8,9 +8,9 @@ A low-level windowing system geared towards making audio plugin UIs.
Below is a proposed list of milestones (roughly in-order) and their status. Subject to change at any time.
| Feature | Windows | Mac OS | Linux |
| -------------------------------------- | ------------------ | ------------------ | ----------------- |
| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
| Window uses an OpenGL surface | :heavy_check_mark: | | |
| Basic DPI scaling support | | | :question: |
| Feature | Windows | Mac OS | Linux |
| -------------------------------------- | ------------------ | ------------------ | ------------------ |
| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Window uses an OpenGL surface | :heavy_check_mark: | | :heavy_check_mark: |
| Basic DPI scaling support | | | :question: |

View file

@ -8,7 +8,7 @@ pub use win::*;
#[cfg(target_os = "linux")]
mod x11;
#[cfg(target_os = "linux")]
pub use x11::*;
pub use crate::x11::*;
#[cfg(target_os = "macos")]
mod macos;

View file

@ -1,2 +1,7 @@
mod window;
pub use window::*;
mod xcb_connection;
use xcb_connection::XcbConnection;
mod opengl_util;

66
src/x11/opengl_util.rs Normal file
View file

@ -0,0 +1,66 @@
use std::ffi::CString;
use std::os::raw::{c_int, c_void};
use ::x11::{glx, xlib};
use super::XcbConnection;
pub type GlXCreateContextAttribsARBProc = unsafe extern "C" fn(
dpy: *mut xlib::Display,
fbc: glx::GLXFBConfig,
share_context: glx::GLXContext,
direct: xlib::Bool,
attribs: *const c_int,
) -> glx::GLXContext;
// Check to make sure this system supports the correct version of GLX (>= 1.3 for now)
// For now it just panics if not, but TODO: do correct error handling
pub fn check_glx_version(xcb_connection: &XcbConnection) {
let raw_display = xcb_connection.conn.get_raw_dpy();
let mut maj: c_int = 0;
let mut min: c_int = 0;
unsafe {
if glx::glXQueryVersion(raw_display, &mut maj as *mut c_int, &mut min as *mut c_int) == 0 {
panic!("Cannot get GLX version");
}
if (maj < 1) || (maj == 1 && min < 3) {
panic!("GLX version >= 1.3 required! (have {}.{})", maj, min);
}
}
}
// Get GLX framebuffer config
// History: https://stackoverflow.com/questions/51558473/whats-the-difference-between-a-glx-visual-and-a-fbconfig
pub fn get_glxfbconfig(xcb_connection: &XcbConnection, visual_attribs: &[i32]) -> glx::GLXFBConfig {
let raw_display = xcb_connection.conn.get_raw_dpy();
let xlib_display = xcb_connection.xlib_display;
unsafe {
let mut fbcount: c_int = 0;
let fbcs = glx::glXChooseFBConfig(
raw_display,
xlib_display,
visual_attribs.as_ptr(),
&mut fbcount as *mut c_int,
);
if fbcount == 0 {
panic!("Could not find compatible GLX FB config.");
}
// If we get more than one, any of the different configs work. Just choose the first one.
let fbc = *fbcs;
xlib::XFree(fbcs as *mut c_void);
fbc
}
}
pub unsafe fn load_gl_func(name: &str) -> *mut c_void {
let cname = CString::new(name).unwrap();
let ptr: *mut c_void = std::mem::transmute(glx::glXGetProcAddress(cname.as_ptr() as *const u8));
if ptr.is_null() {
panic!("could not load {}", name);
}
ptr
}

View file

@ -1,15 +1,24 @@
// TODO: messy for now, will refactor when I have more of an idea of the API/architecture
// TODO: actually handle events
// TODO: set window title
// TODO: close window
// TODO: proper error handling (no bare `unwrap`s)
// TODO: refactor X connections (+setup, +screen) to a new struct
// TODO: proper error handling (no bare `unwrap`s, no panics)
// TODO: move more OpenGL-related stuff into opengl_util.rs
// TODO: consider researching all unsafe calls here and figuring out what invariants need to be upheld.
// (write safe wrappers?)
use std::ffi::CStr;
use std::os::raw::{c_int, c_void};
use std::ptr::null_mut;
use ::x11::{glx, xlib};
// use xcb::dri2; // needed later
use super::opengl_util;
use super::XcbConnection;
use crate::Parent;
use crate::WindowOpenOptions;
pub struct Window {
xcb_connection: xcb::Connection,
xcb_connection: XcbConnection,
}
impl Window {
@ -17,21 +26,81 @@ impl Window {
// Convert the parent to a X11 window ID if we're given one
let parent = match options.parent {
Parent::None => None,
Parent::AsIfParented => None, // ???
Parent::AsIfParented => None, // TODO: ???
Parent::WithParent(p) => Some(p as u32),
};
// Connect to the X server
let (conn, screen_num) = xcb::Connection::connect_with_xlib_display().unwrap();
let xcb_connection = XcbConnection::new();
// Figure out screen information
let setup = conn.get_setup();
let screen = setup.roots().nth(screen_num as usize).unwrap();
// Check GLX version (>= 1.3 needed)
opengl_util::check_glx_version(&xcb_connection);
// Get GLX framebuffer config (requires GLX >= 1.3)
#[rustfmt::skip]
let fb_config = opengl_util::get_glxfbconfig(
&xcb_connection,
&[
glx::GLX_X_RENDERABLE, 1,
glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT,
glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT,
glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR,
glx::GLX_RED_SIZE, 8,
glx::GLX_GREEN_SIZE, 8,
glx::GLX_BLUE_SIZE, 8,
glx::GLX_ALPHA_SIZE, 8,
glx::GLX_DEPTH_SIZE, 24,
glx::GLX_STENCIL_SIZE, 8,
glx::GLX_DOUBLEBUFFER, 1,
0
],
);
// The GLX framebuffer config holds an XVisualInfo, which we'll need for other X operations.
let x_visual_info: *const xlib::XVisualInfo =
unsafe { glx::glXGetVisualFromFBConfig(xcb_connection.conn.get_raw_dpy(), fb_config) };
// Load up DRI2 extensions.
// See also: https://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt
/*
// needed later when we handle events
let dri2_ev = {
xcb_connection.conn.prefetch_extension_data(dri2::id());
match xcb_connection.conn.get_extension_data(dri2::id()) {
None => panic!("could not load dri2 extension"),
Some(r) => r.first_event(),
}
};
*/
// Get screen information (?)
let setup = xcb_connection.conn.get_setup();
let screen = unsafe { setup.roots().nth((*x_visual_info).screen as usize).unwrap() };
// Convert parent into something that X understands
let parent_id = if let Some(p) = parent {
p
} else {
screen.root()
};
// Create a colormap
let colormap = xcb_connection.conn.generate_id();
unsafe {
xcb::create_colormap(
&xcb_connection.conn,
xcb::COLORMAP_ALLOC_NONE as u8,
colormap,
parent_id,
(*x_visual_info).visualid as u32,
);
}
// Create window, connecting to the parent if we have one
let win = conn.generate_id();
let event_mask = &[
(xcb::CW_BACK_PIXEL, screen.black_pixel()),
let window_id = xcb_connection.conn.generate_id();
let cw_values = [
(xcb::CW_BACK_PIXEL, screen.white_pixel()),
(xcb::CW_BORDER_PIXEL, screen.black_pixel()),
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE
@ -39,69 +108,154 @@ impl Window {
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_BUTTON_1_MOTION,
),
(xcb::CW_COLORMAP, colormap),
];
xcb::create_window(
// Connection
&conn,
&xcb_connection.conn,
// Depth
xcb::COPY_FROM_PARENT as u8,
unsafe { *x_visual_info }.depth as u8,
// Window ID
win,
window_id,
// Parent ID
if let Some(p) = parent {
p
} else {
screen.root()
},
parent_id,
// x
0,
// y
0,
// width
1024,
options.width as u16,
// height
1024,
options.height as u16,
// border width
0,
// class
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
// visual
screen.root_visual(),
// masks
event_mask,
unsafe { *x_visual_info }.visualid as u32,
// value list
&cw_values,
);
// Don't need the visual info anymore
unsafe {
xlib::XFree(x_visual_info as *mut c_void);
}
// Change window title
let title = options.title;
xcb::change_property(
&conn,
&xcb_connection.conn,
xcb::PROP_MODE_REPLACE as u8,
win,
window_id,
xcb::ATOM_WM_NAME,
xcb::ATOM_STRING,
8,
title.as_bytes(),
);
// Display the window
xcb::map_window(&conn, win);
conn.flush();
// Load GLX extensions
// We need at least `GLX_ARB_create_context`
let glx_extensions = unsafe {
CStr::from_ptr(glx::glXQueryExtensionsString(
xcb_connection.conn.get_raw_dpy(),
xcb_connection.xlib_display,
))
.to_str()
.unwrap()
};
glx_extensions
.find("GLX_ARB_create_context")
.expect("could not find GLX extension GLX_ARB_create_context");
let x11_window = Self {
xcb_connection: conn,
// With GLX, we don't need a context pre-created in order to load symbols.
// Otherwise, we would need to create a temporary legacy (dummy) GL context to load them.
// (something that has at least GlXCreateContextAttribsARB)
let glx_create_context_attribs: opengl_util::GlXCreateContextAttribsARBProc =
unsafe { std::mem::transmute(opengl_util::load_gl_func("glXCreateContextAttribsARB")) };
// Load all other symbols
unsafe {
gl::load_with(|n| opengl_util::load_gl_func(&n));
}
// Check GL3 support
if !gl::GenVertexArrays::is_loaded() {
panic!("no GL3 support available!");
}
// TODO: This requires a global, which is a no. Figure out if there's a better way to do it.
/*
// installing an event handler to check if error is generated
unsafe {
ctx_error_occurred = false;
}
let old_handler = unsafe {
xlib::XSetErrorHandler(Some(ctx_error_handler))
};
*/
// Create GLX context attributes. (?)
let context_attribs: [c_int; 5] = [
glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB as c_int,
3,
glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB as c_int,
0,
0,
];
let ctx = unsafe {
glx_create_context_attribs(
xcb_connection.conn.get_raw_dpy(),
fb_config,
null_mut(),
xlib::True,
&context_attribs[0] as *const c_int,
)
};
x11_window.handle_events();
if ctx.is_null()
/* || ctx_error_occurred */
{
panic!("Error when creating a GL 3.0 context");
}
if unsafe { glx::glXIsDirect(xcb_connection.conn.get_raw_dpy(), ctx) } == 0 {
panic!("Obtained indirect rendering context");
}
// Display the window
xcb::map_window(&xcb_connection.conn, window_id);
xcb_connection.conn.flush();
unsafe {
xlib::XSync(xcb_connection.conn.get_raw_dpy(), xlib::False);
}
let x11_window = Self { xcb_connection };
x11_window.handle_events(window_id, ctx);
return x11_window;
}
// Event loop
fn handle_events(&self) {
fn handle_events(&self, window_id: u32, ctx: *mut x11::glx::__GLXcontextRec) {
let raw_display = self.xcb_connection.conn.get_raw_dpy();
loop {
let ev = self.xcb_connection.wait_for_event();
let ev = self.xcb_connection.conn.wait_for_event();
if let Some(event) = ev {
println!("{:?}", event.response_type());
let event_type = event.response_type() & !0x80;
//println!("{:?}", event_type);
match event_type {
xcb::EXPOSE => unsafe {
glx::glXMakeCurrent(raw_display, window_id as xlib::XID, ctx);
gl::ClearColor(0.3, 0.8, 0.3, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::Flush();
glx::glXSwapBuffers(raw_display, window_id as xlib::XID);
glx::glXMakeCurrent(raw_display, 0, null_mut());
},
_ => {}
}
}
}
}
@ -111,11 +265,15 @@ impl Window {
// TODO: currently returning (96, 96) on my system, even though I have 4k screens. Problem with my setup perhaps?
#[allow(dead_code)]
pub fn get_scaling() -> (u32, u32) {
let (conn, screen_num) = xcb::Connection::connect_with_xlib_display().unwrap();
// Connect to the X server
let xcb_connection = XcbConnection::new();
// Figure out screen information
let setup = conn.get_setup();
let screen = setup.roots().nth(screen_num as usize).unwrap();
let setup = xcb_connection.conn.get_setup();
let screen = setup
.roots()
.nth(xcb_connection.xlib_display as usize)
.unwrap();
// Get the DPI from the screen struct
//

15
src/x11/xcb_connection.rs Normal file
View file

@ -0,0 +1,15 @@
/// A very light abstraction around the XCB connection.
///
/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect.
pub struct XcbConnection {
pub conn: xcb::Connection,
pub xlib_display: i32,
}
impl XcbConnection {
pub fn new() -> Self {
let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display().unwrap();
Self { conn, xlib_display }
}
}