Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable off-main-thread rendering on macOS #931

Closed
wants to merge 1 commit into from

Conversation

trishume
Copy link

@trishume trishume commented Sep 1, 2017

In servo/webrender#1640 we discussed how Firefox
(and probably also other browsers) have a compositor thread, which is not the
main thread, that does all their OpenGL calls. In glutin this kind of
architecture doesn't seem to be possible right now since it's not possible to
split the GlWindow into the window and the context, and the Context
doesn't implement Send on macOS.

The following two changes allow such architectures:

  • Implement Send for Context on macOS. The Apple docs suggest that this is correct.
  • Add a split method to GlWindow that allows obtaining an owned Window and Context.

cc @mitchmindtree @mstange @glennw

In servo/webrender#1640 we discussed how Firefox
(and probably also other browsers) have a compositor thread, which is not the
main thread, that does all their OpenGL calls. In glutin this kind of
architecture doesn't seem to be possible right now since it's not possible to
split the `GlWindow` into the window and the context, and the `Context`
doesn't implement `Send` on macOS.

The following two changes allow such architectures:
- Implement `Send` for `Context` on macOS. The Apple docs suggest that this is correct.
- Add a `split` method to `GlWindow` that allows obtaining an owned `Window` and `Context`.
@tomaka
Copy link
Contributor

tomaka commented Sep 1, 2017

I don't like this change as it is, because destroying the Window before the Context (which you could do with this split method) is a really bad idea.

@trishume
Copy link
Author

trishume commented Sep 1, 2017

Interesting, that makes sense. I'll see if I can think of some way to use the borrow checker to fix that, if possible.

@trishume
Copy link
Author

trishume commented Sep 1, 2017

Okay here's a sketch of my new idea, I'm not sure when or if I'll get around to fully implementing it though:

trait RenderThread {
    type Message;
    type State;

    // these are both called only on the render thread

    /// sets up state that needs the context, and probably sets context as active for this thread
    fn init(ctx: &Context) -> State;
    /// called by render thread event loop on getting an mpsc message
    fn got_message(ctx: &Context, state: &mut State, msg: Message);
}

struct ConcurrentGlWindow<R: RenderThread> { ... }

enum RenderThreadMsg<T> {
    // handled by the thread's loop, drops the context and shuts down the thread
    Close,
    // calls got_message with the contained message
    Inner(T),
}

impl<R: RenderThread> ConcurrentGlWindow<R> {
    // this is the main loop of the render thread, called from a thread::spawn somewhere
    pub render_thread_loop(render_thread: R, ctx: Context, rcv: mpsc::Receiver<RenderThreadMsg<R::Message>>) {
        // 1. do context initialization that has to be done on the render thread, if any
        ctx.do_setup_stuff();

        let mut state = render_thread.init(&ctx);

        for msg in rcv.into_iter() {
            match msg {
                RenderThreadMsg::Close => break,
                RenderThreadMsg::Inner(m) => render_thread.got_message(&ctx, &mut state, m),
            }
        }
    }

    // enable the main thread to send messages to render, like new frame data
    pub fn sender(&self) -> &mpsc::Sender<RenderThreadMsg<R::Message>> { ... }
    // on platforms that don't support off-main-thread rendering, this manually ticks the render thread event loop on the main thread. When there is a separate thread, it does nothing.
    pub fn tick(&self) { ... }

    pub fn window(&self) -> &Window { ... }
}

impl Deref for ConcurrentGlWindow { type Target = Window; ... }

impl Drop for ConcurrentGlWindow { 
    fn drop(&mut self) {
      self.sender.send(RenderThreadMsg::Close);
      self.thread_join_handle.join();
    }
}

Thoughts?

@tomaka
Copy link
Contributor

tomaka commented Sep 1, 2017

Theoretically, the correct and easiest fix would be to implement Sync on Context and !Sync on Window, so that the reference grabbed by calling gl_window.context() can be sent to another thread.
However you are probably going to run into lifetime issues when trying to use this design in practice.

@trishume
Copy link
Author

trishume commented Sep 1, 2017

@tomaka implementing Sync on Context would be partially correct, but I don't think it would be fully correct.

On macOS, each thread has its own OpenGL context, and you are only supposed to do calls on that context from one thread at a time. You can do it from multiple threads, but only if you synchronize them. If Context was Sync you could activate it on multiple threads and do concurrent OpenGL calls with nothing stopping you. With ConcurrentGLWindow you can only ever activate the OpenGL context on the render thread, which stops you from doing unsynchronized OpenGL calls.

Also the ConcurrentGLWindow approach works on both platforms that do and don't support off-main-thread OpenGL with the same code, as long as you call .tick() in your event loop.

@tomaka
Copy link
Contributor

tomaka commented Sep 1, 2017

On macOS, each thread has its own OpenGL context, and you are only supposed to do calls on that context from one thread at a time. You can do it from multiple threads, but only if you synchronize them. If Context was Sync you could activate it on multiple threads and do concurrent OpenGL calls with nothing stopping you. With ConcurrentGLWindow you can only ever activate the OpenGL context on the render thread, which stops you from doing unsynchronized OpenGL calls.

That's the reason why the methods on the GlContext trait and the OpenGL functions themselves are both unsafe. Glutin will never protect you against this one-context-per-thread thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants