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

When using the WebGL external image API, use sync calls if we happen to be on the WebGL thread #23868

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
239 changes: 99 additions & 140 deletions components/canvas/webgl_mode/inprocess.rs
Expand Up @@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::gl_context::GLContextFactory;
use crate::webgl_thread::{TexturesMap, WebGLMainThread, WebGLThread, WebGLThreadInit};
use crate::webgl_thread::{WebGLMainThread, WebGLThread, WebGLThreadInit};
use canvas_traits::webgl::webgl_channel;
use canvas_traits::webgl::DOMToTextureCommand;
use canvas_traits::webgl::{WebGLChan, WebGLContextId, WebGLMsg, WebGLPipeline, WebGLReceiver};
Expand All @@ -13,8 +13,6 @@ use euclid::default::Size2D;
use fnv::FnvHashMap;
use gleam::gl;
use servo_config::pref;
use std::cell::RefCell;
use std::collections::HashMap;
use std::default::Default;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
Expand All @@ -25,20 +23,21 @@ pub struct WebGLThreads(WebGLSender<WebGLMsg>);

pub enum ThreadMode {
MainThread(Box<dyn EventLoopWaker>),
OffThread(Rc<dyn gl::Gl>),
OffThread,
}

impl WebGLThreads {
/// Creates a new WebGLThreads object
pub fn new(
gl_factory: GLContextFactory,
webrender_gl: Rc<dyn gl::Gl>,
webrender_api_sender: webrender_api::RenderApiSender,
webvr_compositor: Option<Box<dyn WebVRRenderHandler>>,
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
mode: ThreadMode,
) -> (
WebGLThreads,
Option<WebGLMainThread>,
Option<Rc<WebGLMainThread>>,
Box<dyn WebrenderExternalImageApi>,
Option<Box<dyn webrender::OutputImageHandler>>,
) {
Expand All @@ -54,33 +53,24 @@ impl WebGLThreads {
};

let output_handler = if pref!(dom.webgl.dom_to_texture.enabled) {
Some(Box::new(match mode {
ThreadMode::MainThread(..) => OutputHandler::new_main_thread(),
ThreadMode::OffThread(ref webrender_gl) => {
OutputHandler::new_off_thread(webrender_gl.clone(), sender.clone())
},
}))
Some(Box::new(OutputHandler::new(
webrender_gl.clone(),
sender.clone(),
)))
} else {
None
};

let (external, webgl_thread) = match mode {
let external = WebGLExternalImages::new(webrender_gl, sender.clone());

let webgl_thread = match mode {
ThreadMode::MainThread(event_loop_waker) => {
let textures = Rc::new(RefCell::new(HashMap::new()));
let thread_data =
WebGLThread::run_on_current_thread(init, event_loop_waker, textures.clone());
(
WebGLExternalImages::new_main_thread(textures),
Some(thread_data),
)
let thread = WebGLThread::run_on_current_thread(init, event_loop_waker);
Some(thread)
},

ThreadMode::OffThread(webrender_gl) => {
ThreadMode::OffThread => {
WebGLThread::run_on_own_thread(init);
(
WebGLExternalImages::new_off_thread(webrender_gl, sender.clone()),
None,
)
None
},
};

Expand All @@ -107,113 +97,90 @@ impl WebGLThreads {
}

/// Bridge between the webrender::ExternalImage callbacks and the WebGLThreads.
enum WebGLExternalImages {
OffThread {
webrender_gl: Rc<dyn gl::Gl>,
webgl_channel: WebGLSender<WebGLMsg>,
// Used to avoid creating a new channel on each received WebRender request.
lock_channel: (
WebGLSender<(u32, Size2D<i32>, usize)>,
WebGLReceiver<(u32, Size2D<i32>, usize)>,
),
},
MainThread {
textures: TexturesMap,
},
struct WebGLExternalImages {
webrender_gl: Rc<dyn gl::Gl>,
webgl_channel: WebGLSender<WebGLMsg>,
// Used to avoid creating a new channel on each received WebRender request.
lock_channel: (
WebGLSender<(u32, Size2D<i32>, usize)>,
WebGLReceiver<(u32, Size2D<i32>, usize)>,
),
}

impl WebGLExternalImages {
fn new_off_thread(webrender_gl: Rc<dyn gl::Gl>, channel: WebGLSender<WebGLMsg>) -> Self {
WebGLExternalImages::OffThread {
fn new(webrender_gl: Rc<dyn gl::Gl>, channel: WebGLSender<WebGLMsg>) -> Self {
WebGLExternalImages {
webrender_gl,
webgl_channel: channel,
lock_channel: webgl_channel().unwrap(),
}
}

fn new_main_thread(textures: TexturesMap) -> Self {
WebGLExternalImages::MainThread { textures }
}
}

impl WebrenderExternalImageApi for WebGLExternalImages {
fn lock(&mut self, id: u64) -> (u32, Size2D<i32>) {
match *self {
WebGLExternalImages::OffThread {
ref webgl_channel,
ref webrender_gl,
ref lock_channel,
} => {
// WebGL Thread has it's own GL command queue that we need to synchronize with the WR GL command queue.
// The WebGLMsg::Lock message inserts a fence in the WebGL command queue.
webgl_channel
.send(WebGLMsg::Lock(
WebGLContextId(id as usize),
lock_channel.0.clone(),
))
.unwrap();
let (image_id, size, gl_sync) = lock_channel.1.recv().unwrap();
// The next glWaitSync call is run on the WR thread and it's used to synchronize the two
// flows of OpenGL commands in order to avoid WR using a semi-ready WebGL texture.
// glWaitSync doesn't block WR thread, it affects only internal OpenGL subsystem.
webrender_gl.wait_sync(gl_sync as gl::GLsync, 0, gl::TIMEOUT_IGNORED);
(image_id, size)
},

WebGLExternalImages::MainThread { ref textures } => {
let textures = textures.borrow();
let entry = textures
.get(&WebGLContextId(id as usize))
.expect("no texture entry???");
(entry.0, entry.1)
},
if let Some(main_thread) = WebGLMainThread::on_current_thread() {
// If we're on the same thread as WebGL, we can get the data directly
main_thread
.thread_data
.borrow_mut()
.handle_lock_unsync(WebGLContextId(id as usize))
} else {
// WebGL Thread has it's own GL command queue that we need to synchronize with the WR GL command queue.
// The WebGLMsg::Lock message inserts a fence in the WebGL command queue.
self.webgl_channel
.send(WebGLMsg::Lock(
WebGLContextId(id as usize),
self.lock_channel.0.clone(),
))
.unwrap();
let (image_id, size, gl_sync) = self.lock_channel.1.recv().unwrap();
// The next glWaitSync call is run on the WR thread and it's used to synchronize the two
// flows of OpenGL commands in order to avoid WR using a semi-ready WebGL texture.
// glWaitSync doesn't block WR thread, it affects only internal OpenGL subsystem.
self.webrender_gl
.wait_sync(gl_sync as gl::GLsync, 0, gl::TIMEOUT_IGNORED);
(image_id, size)
}
}

fn unlock(&mut self, id: u64) {
match *self {
WebGLExternalImages::OffThread {
ref webgl_channel, ..
} => {
webgl_channel
.send(WebGLMsg::Unlock(WebGLContextId(id as usize)))
.unwrap();
},

WebGLExternalImages::MainThread { .. } => {},
if let Some(main_thread) = WebGLMainThread::on_current_thread() {
// If we're on the same thread as WebGL, we can unlock directly
main_thread
.thread_data
.borrow_mut()
.handle_unlock(WebGLContextId(id as usize))
} else {
self.webgl_channel
.send(WebGLMsg::Unlock(WebGLContextId(id as usize)))
.unwrap()
}
}
}

/// struct used to implement DOMToTexture feature and webrender::OutputImageHandler trait.
type OutputHandlerData = Option<(u32, Size2D<i32>)>;
enum OutputHandler {
OffThread {
webrender_gl: Rc<dyn gl::Gl>,
webgl_channel: WebGLSender<WebGLMsg>,
// Used to avoid creating a new channel on each received WebRender request.
lock_channel: (
WebGLSender<OutputHandlerData>,
WebGLReceiver<OutputHandlerData>,
),
sync_objects: FnvHashMap<webrender_api::PipelineId, gl::GLsync>,
},
MainThread,
struct OutputHandler {
webrender_gl: Rc<dyn gl::Gl>,
webgl_channel: WebGLSender<WebGLMsg>,
// Used to avoid creating a new channel on each received WebRender request.
lock_channel: (
WebGLSender<OutputHandlerData>,
WebGLReceiver<OutputHandlerData>,
),
sync_objects: FnvHashMap<webrender_api::PipelineId, gl::GLsync>,
}

impl OutputHandler {
fn new_off_thread(webrender_gl: Rc<dyn gl::Gl>, channel: WebGLSender<WebGLMsg>) -> Self {
OutputHandler::OffThread {
fn new(webrender_gl: Rc<dyn gl::Gl>, channel: WebGLSender<WebGLMsg>) -> Self {
OutputHandler {
webrender_gl,
webgl_channel: channel,
lock_channel: webgl_channel().unwrap(),
sync_objects: Default::default(),
}
}

fn new_main_thread() -> Self {
OutputHandler::MainThread
}
}

/// Bridge between the WR frame outputs and WebGL to implement DOMToTexture synchronization.
Expand All @@ -222,49 +189,41 @@ impl webrender::OutputImageHandler for OutputHandler {
&mut self,
id: webrender_api::PipelineId,
) -> Option<(u32, webrender_api::units::FramebufferIntSize)> {
match *self {
OutputHandler::OffThread {
ref webrender_gl,
ref lock_channel,
ref webgl_channel,
..
} => {
// Insert a fence in the WR command queue
let gl_sync = webrender_gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
// The lock command adds a WaitSync call on the WebGL command flow.
let command =
DOMToTextureCommand::Lock(id, gl_sync as usize, lock_channel.0.clone());
webgl_channel
.send(WebGLMsg::DOMToTextureCommand(command))
.unwrap();
lock_channel.1.recv().unwrap().map(|(tex_id, size)| {
(
tex_id,
webrender_api::units::FramebufferIntSize::new(size.width, size.height),
)
})
},
// Insert a fence in the WR command queue
let gl_sync = self
.webrender_gl
.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
self.sync_objects.insert(id, gl_sync);

let result = if let Some(main_thread) = WebGLMainThread::on_current_thread() {
main_thread
.thread_data
.borrow_mut()
.handle_dom_to_texture_lock(id, gl_sync as usize)
} else {
// The lock command adds a WaitSync call on the WebGL command flow.
let command =
DOMToTextureCommand::Lock(id, gl_sync as usize, self.lock_channel.0.clone());
self.webgl_channel
.send(WebGLMsg::DOMToTextureCommand(command))
.unwrap();
self.lock_channel.1.recv().unwrap()
};

OutputHandler::MainThread => unimplemented!(),
}
result.map(|(tex_id, size)| {
(
tex_id,
webrender_api::units::FramebufferIntSize::new(size.width, size.height),
)
})
}

fn unlock(&mut self, id: webrender_api::PipelineId) {
match *self {
OutputHandler::OffThread {
ref webrender_gl,
ref mut sync_objects,
..
} => {
if let Some(gl_sync) = sync_objects.remove(&id) {
// Flush the Sync object into the GPU's command queue to guarantee that it it's signaled.
webrender_gl.flush();
// Mark the sync object for deletion.
webrender_gl.delete_sync(gl_sync);
}
},

OutputHandler::MainThread => {},
if let Some(gl_sync) = self.sync_objects.remove(&id) {
// Flush the Sync object into the GPU's command queue to guarantee that it it's signaled.
self.webrender_gl.flush();
// Mark the sync object for deletion.
self.webrender_gl.delete_sync(gl_sync);
}
}
}