opengl.rs 9.72 KB

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();
}}