diff options
author | Josh Matthews <josh@joshmatthews.net> | 2019-07-11 19:41:04 -0400 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2019-07-25 23:05:03 -0400 |
commit | 39d13d1fc85d3b38d04df689421fd0771c951b64 (patch) | |
tree | 2067c4e225e859a572a44ed733e0f9124c18c21c /components/canvas/webgl_thread.rs | |
parent | ad82d67b2e5ab56d046274ebb5436c3388ddf21a (diff) | |
download | servo-39d13d1fc85d3b38d04df689421fd0771c951b64.tar.gz servo-39d13d1fc85d3b38d04df689421fd0771c951b64.zip |
Support running WebGL in its own thread or on the main thread.
Diffstat (limited to 'components/canvas/webgl_thread.rs')
-rw-r--r-- | components/canvas/webgl_thread.rs | 195 |
1 files changed, 153 insertions, 42 deletions
diff --git a/components/canvas/webgl_thread.rs b/components/canvas/webgl_thread.rs index ce73b03cf09..715b6d8b8ca 100644 --- a/components/canvas/webgl_thread.rs +++ b/components/canvas/webgl_thread.rs @@ -5,6 +5,7 @@ use super::gl_context::{map_attrs_to_script_attrs, GLContextFactory, GLContextWrapper}; use byteorder::{ByteOrder, NativeEndian, WriteBytesExt}; use canvas_traits::webgl::*; +use embedder_traits::EventLoopWaker; use euclid::default::Size2D; use fnv::FnvHashMap; use gleam::gl; @@ -13,13 +14,17 @@ use ipc_channel::ipc::IpcSender; use offscreen_gl_context::{DrawBuffer, GLContext, NativeGLContextMethods}; use pixels::{self, PixelFormat}; use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashMap; +use std::mem; +use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::thread; use webrender_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType}; /// WebGL Threading API entry point that lives in the constellation. /// It allows to get a WebGLThread handle for each script pipeline. -pub use crate::webgl_mode::WebGLThreads; +pub use crate::webgl_mode::{ThreadMode, WebGLThreads}; struct GLContextData { ctx: GLContextWrapper, @@ -50,7 +55,7 @@ impl Default for GLState { /// A WebGLThread manages the life cycle and message multiplexing of /// a set of WebGLContexts living in the same thread. -pub struct WebGLThread<VR: WebVRRenderHandler + 'static> { +pub(crate) struct WebGLThread { /// Factory used to create a new GLContext shared with the WR/Main thread. gl_factory: GLContextFactory, /// Channel used to generate/update or delete `webrender_api::ImageKey`s. @@ -62,20 +67,74 @@ pub struct WebGLThread<VR: WebVRRenderHandler + 'static> { /// Current bound context. bound_context_id: Option<WebGLContextId>, /// Handler user to send WebVR commands. - webvr_compositor: Option<VR>, + webvr_compositor: Option<Box<dyn WebVRRenderHandler>>, /// Texture ids and sizes used in DOM to texture outputs. dom_outputs: FnvHashMap<webrender_api::PipelineId, DOMToTextureData>, /// List of registered webrender external images. /// We use it to get an unique ID for new WebGLContexts. external_images: Arc<Mutex<WebrenderExternalImageRegistry>>, + /// The receiver that will be used for processing WebGL messages. + receiver: WebGLReceiver<WebGLMsg>, + /// The receiver that should be used to send WebGL messages for processing. + sender: WebGLSender<WebGLMsg>, } -impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { - pub fn new( - gl_factory: GLContextFactory, - webrender_api_sender: webrender_api::RenderApiSender, - webvr_compositor: Option<VR>, - external_images: Arc<Mutex<WebrenderExternalImageRegistry>>, +/// A map of GL contexts to backing textures and their sizes. +/// Only used for accessing this information when the WebGL processing is run +/// on the main thread and the compositor needs access to this information +/// synchronously. +pub(crate) type TexturesMap = Rc<RefCell<HashMap<WebGLContextId, (u32, Size2D<i32>)>>>; + +#[derive(PartialEq)] +enum EventLoop { + Blocking, + Nonblocking, +} + +/// The data required to initialize an instance of the WebGLThread type. +pub(crate) struct WebGLThreadInit { + pub gl_factory: GLContextFactory, + pub webrender_api_sender: webrender_api::RenderApiSender, + pub webvr_compositor: Option<Box<dyn WebVRRenderHandler>>, + pub external_images: Arc<Mutex<WebrenderExternalImageRegistry>>, + pub sender: WebGLSender<WebGLMsg>, + pub receiver: WebGLReceiver<WebGLMsg>, +} + +/// The extra data required to run an instance of WebGLThread when it is +/// not running in its own thread. +pub struct WebGLMainThread { + thread_data: WebGLThread, + shut_down: bool, + textures: TexturesMap, +} + +impl WebGLMainThread { + /// Synchronously process all outstanding WebGL messages. + pub fn process(&mut self) { + if self.shut_down { + return; + } + + // Any context could be current when we start. + self.thread_data.bound_context_id = None; + self.shut_down = !self + .thread_data + .process(EventLoop::Nonblocking, Some(self.textures.clone())) + } +} + +impl WebGLThread { + /// Create a new instance of WebGLThread. + pub(crate) fn new( + WebGLThreadInit { + gl_factory, + webrender_api_sender, + webvr_compositor, + external_images, + sender, + receiver, + }: WebGLThreadInit, ) -> Self { WebGLThread { gl_factory, @@ -86,49 +145,83 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { webvr_compositor, dom_outputs: Default::default(), external_images, + sender, + receiver, } } - /// Creates a new `WebGLThread` and returns a Sender to - /// communicate with it. - pub fn start( - gl_factory: GLContextFactory, - webrender_api_sender: webrender_api::RenderApiSender, - webvr_compositor: Option<VR>, - external_images: Arc<Mutex<WebrenderExternalImageRegistry>>, - ) -> WebGLSender<WebGLMsg> { - let (sender, receiver) = webgl_channel::<WebGLMsg>().unwrap(); - let result = sender.clone(); + /// Perform all initialization required to run an instance of WebGLThread + /// concurrently on the current thread. Returns a `WebGLMainThread` instance + /// that can be used to process any outstanding WebGL messages at any given + /// point in time. + pub(crate) fn run_on_current_thread( + mut init: WebGLThreadInit, + event_loop_waker: Box<dyn EventLoopWaker>, + textures: TexturesMap, + ) -> WebGLMainThread { + // Interpose a new channel in between the existing WebGL channel endpoints. + // This will bounce all WebGL messages through a second thread adding a small + // delay, but this will also ensure that the main thread will wake up and + // process the WebGL message when it arrives. + let (from_router_sender, from_router_receiver) = webgl_channel::<WebGLMsg>().unwrap(); + let receiver = mem::replace(&mut init.receiver, from_router_receiver); + + let thread_data = WebGLThread::new(init); + thread::Builder::new() - .name("WebGLThread".to_owned()) + .name("WebGL main thread pump".to_owned()) .spawn(move || { - let mut renderer = WebGLThread::new( - gl_factory, - webrender_api_sender, - webvr_compositor, - external_images, - ); - let webgl_chan = WebGLChan(sender); - loop { - let msg = receiver.recv().unwrap(); - let exit = renderer.handle_msg(msg, &webgl_chan); - if exit { - return; - } + while let Ok(msg) = receiver.recv() { + let _ = from_router_sender.send(msg); + event_loop_waker.wake(); } }) .expect("Thread spawning failed"); - result + WebGLMainThread { + thread_data, + textures, + shut_down: false, + } + } + + /// Perform all initialization required to run an instance of WebGLThread + /// in parallel on its own dedicated thread. + pub(crate) fn run_on_own_thread(init: WebGLThreadInit) { + thread::Builder::new() + .name("WebGL thread".to_owned()) + .spawn(move || { + let mut data = WebGLThread::new(init); + data.process(EventLoop::Blocking, None); + }) + .expect("Thread spawning failed"); + } + + fn process(&mut self, loop_type: EventLoop, textures: Option<TexturesMap>) -> bool { + let webgl_chan = WebGLChan(self.sender.clone()); + while let Ok(msg) = match loop_type { + EventLoop::Blocking => self.receiver.recv(), + EventLoop::Nonblocking => self.receiver.try_recv(), + } { + let exit = self.handle_msg(msg, &webgl_chan, textures.as_ref()); + if exit { + return false; + } + } + true } /// Handles a generic WebGLMsg message - #[inline] - fn handle_msg(&mut self, msg: WebGLMsg, webgl_chan: &WebGLChan) -> bool { + fn handle_msg( + &mut self, + msg: WebGLMsg, + webgl_chan: &WebGLChan, + textures: Option<&TexturesMap>, + ) -> bool { trace!("processing {:?}", msg); match msg { WebGLMsg::CreateContext(version, size, attributes, result_sender) => { - let result = self.create_webgl_context(version, size, attributes); + let result = self.create_webgl_context(version, size, attributes, textures); result_sender .send(result.map(|(id, limits, share_mode)| { let data = Self::make_current_if_needed( @@ -173,10 +266,10 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { .unwrap(); }, WebGLMsg::ResizeContext(ctx_id, size, sender) => { - self.resize_webgl_context(ctx_id, size, sender); + self.resize_webgl_context(ctx_id, size, sender, textures); }, WebGLMsg::RemoveContext(ctx_id) => { - self.remove_webgl_context(ctx_id); + self.remove_webgl_context(ctx_id, textures); }, WebGLMsg::WebGLCommand(ctx_id, command, backtrace) => { self.handle_webgl_command(ctx_id, command, backtrace); @@ -296,6 +389,7 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { version: WebGLVersion, size: Size2D<u32>, attributes: GLContextAttributes, + textures: Option<&TexturesMap>, ) -> Result<(WebGLContextId, GLLimits, WebGLContextShareMode), String> { // Creating a new GLContext may make the current bound context_id dirty. // Clear it to ensure that make_current() is called in subsequent commands. @@ -332,6 +426,11 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { state: Default::default(), }, ); + + if let Some(ref textures) = textures { + textures.borrow_mut().insert(id, (texture_id, size)); + } + self.cached_context_info.insert( id, WebGLContextInfo { @@ -354,6 +453,7 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { context_id: WebGLContextId, size: Size2D<u32>, sender: WebGLSender<Result<(), String>>, + textures: Option<&TexturesMap>, ) { let data = Self::make_current_if_needed_mut( context_id, @@ -378,6 +478,13 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { // Update webgl texture size. Texture id may change too. info.texture_id = texture_id; info.size = real_size; + + if let Some(ref textures) = textures { + textures + .borrow_mut() + .insert(context_id, (texture_id, real_size)); + } + // Update WR image if needed. Resize image updates are only required for SharedTexture mode. // Readback mode already updates the image every frame to send the raw pixels. // See `handle_update_wr_image`. @@ -403,7 +510,7 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { } /// Removes a WebGLContext and releases attached resources. - fn remove_webgl_context(&mut self, context_id: WebGLContextId) { + fn remove_webgl_context(&mut self, context_id: WebGLContextId, textures: Option<&TexturesMap>) { // Release webrender image keys. if let Some(info) = self.cached_context_info.remove(&context_id) { let mut txn = webrender_api::Transaction::new(); @@ -422,6 +529,10 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { // Release GL context. self.contexts.remove(&context_id); + if let Some(ref textures) = textures { + textures.borrow_mut().remove(&context_id); + } + // Removing a GLContext may make the current bound context_id dirty. self.bound_context_id = None; } @@ -729,12 +840,12 @@ impl<VR: WebVRRenderHandler + 'static> WebGLThread<VR> { } } -impl<VR: WebVRRenderHandler + 'static> Drop for WebGLThread<VR> { +impl Drop for WebGLThread { fn drop(&mut self) { // Call remove_context functions in order to correctly delete WebRender image keys. let context_ids: Vec<WebGLContextId> = self.contexts.keys().map(|id| *id).collect(); for id in context_ids { - self.remove_webgl_context(id); + self.remove_webgl_context(id, None); } } } |