A 3D math playground visualizing on a canvas trait which the user needs to implement e.g. using XCB or a HTML5 Canvas for drawing as WebAssembly application. (Both exists in separate projects.)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
9.7 KiB

extern crate x11;
extern crate xcb;
extern crate gl;
extern crate libc;
use xcb::dri2;
use x11::xlib;
use x11::glx::*;
use std::ptr::null_mut;
use std::ffi::{CStr, CString};
use std::os::raw::{c_int, c_void};
const GLX_CONTEXT_MAJOR_VERSION_ARB: u32 = 0x2091;
const GLX_CONTEXT_MINOR_VERSION_ARB: u32 = 0x2092;
type GlXCreateContextAttribsARBProc =
unsafe extern "C" fn (dpy: *mut xlib::Display, fbc: GLXFBConfig,
share_context: GLXContext, direct: xlib::Bool,
attribs: *const c_int) -> GLXContext;
unsafe fn load_gl_func (name: &str) -> *mut c_void {
let cname = CString::new(name).unwrap();
let ptr: *mut c_void = std::mem::transmute(glXGetProcAddress(
cname.as_ptr() as *const u8
));
if ptr.is_null() {
panic!("could not load {}", name);
}
ptr
}
fn check_glx_extension(glx_exts: &str, ext_name: &str) -> bool {
for glx_ext in glx_exts.split(" ") {
if glx_ext == ext_name {
return true;
}
}
false
}
static mut ctx_error_occurred: bool = false;
unsafe extern "C" fn ctx_error_handler(
_dpy: *mut xlib::Display,
_ev: *mut xlib::XErrorEvent) -> i32 {
ctx_error_occurred = true;
0
}
unsafe fn check_gl_error() {
let err = gl::GetError();
if err != gl::NO_ERROR {
println!("got gl error {}", err);
}
}
// returns the glx version in a decimal form
// eg. 1.3 => 13
fn glx_dec_version(dpy: *mut xlib::Display) -> i32 {
let mut maj: c_int = 0;
let mut min: c_int = 0;
unsafe {
if glXQueryVersion(dpy,
&mut maj as *mut c_int,
&mut min as *mut c_int) == 0 {
panic!("cannot get glx version");
}
}
(maj*10 + min) as i32
}
fn get_glxfbconfig(dpy: *mut xlib::Display, screen_num: i32,
visual_attribs: &[i32]) -> GLXFBConfig {
unsafe {
let mut fbcount: c_int = 0;
let fbcs = glXChooseFBConfig(dpy, screen_num,
visual_attribs.as_ptr(),
&mut fbcount as *mut c_int);
if fbcount == 0 {
panic!("could not find compatible fb config");
}
// we pick the first from the list
let fbc = *fbcs;
xlib::XFree(fbcs as *mut c_void);
fbc
}
}
fn main() { unsafe {
let (conn, screen_num) = xcb::Connection::connect_with_xlib_display().unwrap();
conn.set_event_queue_owner(xcb::EventQueueOwner::Xcb);
if glx_dec_version(conn.get_raw_dpy()) < 13 {
panic!("glx-1.3 is not supported");
}
let fbc = get_glxfbconfig(conn.get_raw_dpy(), screen_num, &[
GLX_X_RENDERABLE , 1,
GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT,
GLX_RENDER_TYPE , GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR,
GLX_RED_SIZE , 8,
GLX_GREEN_SIZE , 8,
GLX_BLUE_SIZE , 8,
GLX_ALPHA_SIZE , 8,
GLX_DEPTH_SIZE , 24,
GLX_STENCIL_SIZE , 8,
GLX_DOUBLEBUFFER , 1,
0
]);
let vi: *const xlib::XVisualInfo =
glXGetVisualFromFBConfig(conn.get_raw_dpy(), fbc);
let dri2_ev = {
conn.prefetch_extension_data(dri2::id());
match conn.get_extension_data(dri2::id()) {
None => { panic!("could not load dri2 extension") },
Some(r) => { r.first_event() }
}
};
let (wm_protocols, wm_delete_window) = {
let pc = xcb::intern_atom(&conn, false, "WM_PROTOCOLS");
let dwc = xcb::intern_atom(&conn, false, "WM_DELETE_WINDOW");
let p = match pc.get_reply() {
Ok(p) => p.atom(),
Err(_) => panic!("could not load WM_PROTOCOLS atom")
};
let dw = match dwc.get_reply() {
Ok(dw) => dw.atom(),
Err(_) => panic!("could not load WM_DELETE_WINDOW atom")
};
(p, dw)
};
let setup = conn.get_setup();
let screen = setup.roots().nth((*vi).screen as usize).unwrap();
let cmap = conn.generate_id();
let win = conn.generate_id();
xcb::create_colormap(&conn, xcb::COLORMAP_ALLOC_NONE as u8,
cmap, screen.root(), (*vi).visualid as u32);
let cw_values = [
(xcb::CW_BACK_PIXEL, screen.white_pixel()),
(xcb::CW_BORDER_PIXEL, screen.black_pixel()),
(xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_EXPOSURE),
(xcb::CW_COLORMAP, cmap)
];
xcb::create_window(&conn, (*vi).depth as u8, win, screen.root(), 0, 0, 640, 480,
0, xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
(*vi).visualid as u32, &cw_values);
xlib::XFree(vi as *mut c_void);
let title = "XCB OpenGL";
xcb::change_property(&conn,
xcb::PROP_MODE_REPLACE as u8,
win,
xcb::ATOM_WM_NAME,
xcb::ATOM_STRING,
8, title.as_bytes());
let protocols = [wm_delete_window];
xcb::change_property(&conn, xcb::PROP_MODE_REPLACE as u8,
win, wm_protocols, xcb::ATOM_ATOM, 32, &protocols);
xcb::map_window(&conn, win);
conn.flush();
xlib::XSync(conn.get_raw_dpy(), xlib::False);
let glx_exts = CStr::from_ptr(
glXQueryExtensionsString(conn.get_raw_dpy(), screen_num))
.to_str().unwrap();
if !check_glx_extension(&glx_exts, "GLX_ARB_create_context") {
panic!("could not find GLX extension GLX_ARB_create_context");
}
// with glx, no need of a current context is needed to load symbols
// otherwise we would need to create a temporary legacy GL context
// for loading symbols (at least glXCreateContextAttribsARB)
let glx_create_context_attribs: GlXCreateContextAttribsARBProc =
std::mem::transmute(load_gl_func("glXCreateContextAttribsARB"));
// loading all other symbols
gl::load_with(|n| load_gl_func(&n));
if !gl::GenVertexArrays::is_loaded() {
panic!("no GL3 support available!");
}
// installing an event handler to check if error is generated
ctx_error_occurred = false;
let old_handler = xlib::XSetErrorHandler(Some(ctx_error_handler));
let context_attribs: [c_int; 5] = [
GLX_CONTEXT_MAJOR_VERSION_ARB as c_int, 3,
GLX_CONTEXT_MINOR_VERSION_ARB as c_int, 0,
0
];
let ctx = glx_create_context_attribs(conn.get_raw_dpy(), fbc, null_mut(),
xlib::True, &context_attribs[0] as *const c_int);
conn.flush();
xlib::XSync(conn.get_raw_dpy(), xlib::False);
xlib::XSetErrorHandler(std::mem::transmute(old_handler));
if ctx.is_null() || ctx_error_occurred {
panic!("error when creating gl-3.0 context");
}
if glXIsDirect(conn.get_raw_dpy(), ctx) == 0 {
panic!("obtained indirect rendering context")
}
loop {
if let Some(ev) = conn.wait_for_event() {
let ev_type = ev.response_type() & !0x80;
match ev_type {
xcb::EXPOSE => {
glXMakeCurrent(conn.get_raw_dpy(), win as xlib::XID, ctx);
gl::ClearColor(0.5f32, 0.5f32, 1.0f32, 1.0f32);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::Flush();
check_gl_error();
glXSwapBuffers(conn.get_raw_dpy(), win as xlib::XID);
glXMakeCurrent(conn.get_raw_dpy(), 0, null_mut());
},
xcb::KEY_PRESS => {
break;
},
xcb::CLIENT_MESSAGE => {
let cmev = unsafe {
xcb::cast_event::<xcb::ClientMessageEvent>(&ev)
};
if cmev.type_() == wm_protocols && cmev.format() == 32 {
let protocol = cmev.data().data32()[0];
if protocol == wm_delete_window {
break;
}
}
},
_ => {
// following stuff is not obvious at all, but is necessary
// to handle GL when XCB owns the event queue
if ev_type == dri2_ev || ev_type == dri2_ev+1 {
// these are libgl dri2 event that need special handling
// see https://bugs.freedesktop.org/show_bug.cgi?id=35945#c4
// and mailing thread starting here:
// http://lists.freedesktop.org/archives/xcb/2015-November/010556.html
if let Some(proc_) =
xlib::XESetWireToEvent(conn.get_raw_dpy(),
ev_type as i32, None) {
xlib::XESetWireToEvent(conn.get_raw_dpy(),
ev_type as i32, Some(proc_));
let raw_ev = ev.ptr;
(*raw_ev).sequence =
xlib::XLastKnownRequestProcessed(
conn.get_raw_dpy()) as u16;
let mut dummy: xlib::XEvent = std::mem::zeroed();
proc_(conn.get_raw_dpy(),
&mut dummy as *mut xlib::XEvent,
raw_ev as *mut xlib::xEvent);
}
}
}
}
conn.flush();
}
else {
break;
}
}
// only to make sure that rs_client generate correct names for DRI2
// (used to be "*_DRI_2_*")
// should be in a "compile tests" section instead of example
let _ = xcb::ffi::dri2::XCB_DRI2_ATTACHMENT_BUFFER_ACCUM;
glXDestroyContext(conn.get_raw_dpy(), ctx);
xcb::unmap_window(&conn, win);
xcb::destroy_window(&conn, win);
xcb::free_colormap(&conn, cmap);
conn.flush();
}}