How to implement shared EGLContext instances stored in thread local and C pointer in safe Rust

#1

I’m implementing the khronos EGL in Rust just like google/angle but facing some problems such as ownership. I need some help. FYI: I’m not trying to call EGL through FFI.

Here are the major EGL functions prototypes.

typedef void *EGLContext;
// https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreateContext.xhtml
EGLContext eglCreateContext(EGLDisplay display,
    EGLConfig config,
    EGLContext share_context,
    EGLint const * attrib_list);
// https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglMakeCurrent.xhtml
EGLBoolean eglMakeCurrent(EGLDisplay display,
    EGLSurface draw,
    EGLSurface read,
    EGLContext context);
// https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglDestroyContext.xhtml
EGLBoolean eglDestroyContext(EGLDisplay display,
    EGLContext context);

Generally, eglCreateContext(...) creates an instance of EGLContext and then return address of that instance. Once eglMakeCurrent(...) stored the instance in a thread local variable, all the following OpenGL ES funcation calls like glGenTextures(...) will access and change the states held by that TLS variable.

For eglCreateContext() I think returning a Box::into_raw(Box::new(EGLContext) doesn’t fit into the case because it will be set to a TLS variable through eglMakeCurrent(). It should be two owers after the eglMakeCurrent() call. But I don’t know the side effect of returing a Rc<RefCell<EGLContext>>. Here’s the code snippets.

// real EGLContext struct to hold all the states and resources
struct Context {...}

#[no_mangle]
pub extern "C" fn eglCreateContext(...) -> EGLContext {
    let instance = Context::new();
    let value = Rc::new(RefCell::new(instance));
    Rc::into_raw(value) as _
}

// or write it like this
#[no_mangle]
pub extern "C" fn eglCreateContext(...) -> EGLContext {
    let instance = Context::new();
    let value = Rc::new(RefCell::new(instance));
    Box::into_raw(Box::new(Rc::into_raw(value)) as _
}

Now it comes to the biggest problem. **How to declare a proper thread_local! variable to hold an EGLContext value? **

I have to declare something like the following because the init value of EGLContext of a thread is None. It will has a valid value after eglMakeCurrent(...) passing an alive EGLContext context and it will back to null if user calling eglMakeCurrent(...) with nullptr again. However the CONTEXT prototype is very very complex.

thread_local! {
    pub static CONTEXT: Cell<Option<Rc<RefCell<Context>>>> = Cell::new(None);
}

With the Rc<RefCell<Context>> plan I don’t know how to implement the eglMakeCurrent() to get a new Rc value from the pointer of RefCell::new(instance) because it’s not a Rc anymore and eglMakeCurrent() can’t take the owership from C holder with Rc::from_raw() because that will make the previous EGLContext pointer to an wild pointer

With the Box<Rc<RefCell<Context>>> plan I may write it as this:

#[no_mangle]
pub extern "C" fn eglMakeCurrent(...) -> EGLBoolean {
    let value = unsafe { &*context } as Rc<RefCell<Context>>;
    let value = value.clone();
    CONTEXT.with(|c| c.set(Some(value)));
    EGLTrue // just return a fake true value
}

This plan has its own problem when I implement functions of OpenGL ES. Here’s a sample.

#[no_mangle]
pub extern "C" fn glGenTextures(n: GLint, names: *mut GLuint) {
    CONTEXT.with(|c| {
        let context = c.get().is_none();
        if context {
            return;
        } else {
            // It's tooooo long.
            context.expect().borrow_mut().gen_textures(n, names);
        }
    );
}
0 Likes

#2

use std::ffi::c_void;
use std::cell::Cell;

type EGLContext = *mut c_void;
type EGLDisplay = *mut c_void;
type EGLBoolean = bool;
type GLint = i32;
type GLuint = u32;

static EGLTrue: bool = true;

struct Context;

impl Context {
    pub fn new() -> Context {
        Context
    }
    
    pub fn gen_textures(&mut self, n: GLint, names: *mut GLuint) {}
}

#[no_mangle]
pub extern "C" fn eglCreateContext() -> EGLContext {
    let instance = Context::new();
    Box::into_raw(Box::new(instance)) as _
}

#[no_mangle]
pub extern "C" fn eglDestroyContext(display: EGLDisplay, context: EGLContext) -> EGLBoolean {
     let _ = unsafe { Box::from_raw(context) };
     EGLTrue
}

#[no_mangle]
pub extern "C" fn eglMakeCurrent(context: EGLContext) -> EGLBoolean {
    // TODO: Check if context is already associated
    
    CONTEXT.with(|c| {
        c.set(context);
    });
    
    EGLTrue
}

thread_local! {
    pub static CONTEXT: Cell<EGLContext> = Cell::new(::std::ptr::null_mut());
}

#[no_mangle]
pub extern "C" fn glGenTextures(n: GLint, names: *mut GLuint) {
    CONTEXT.with(|c| {
        let context_ptr = c.get();
        if context_ptr.is_null() {
            return;
        }
        
        let context: &mut Context = unsafe { &mut *(context_ptr as *mut _) };
        context.gen_textures(n, names);
    });
}
1 Like