Use surfman for managing GL surfaces

Co-authored-by: Alan Jeffrey <ajeffrey@mozilla.com>
Co-authored-by: Zakor Gyula <gyula.zakor@h-lab.eu>
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Patrick Walton 2019-10-15 12:57:00 -05:00 committed by Alan Jeffrey
parent 48d918dcde
commit a358bca766
52 changed files with 1929 additions and 2195 deletions

View file

@ -1,224 +1,160 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::gl_context::GLContextFactory;
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};
use canvas_traits::webgl::{WebGLSender, WebVRRenderHandler};
use embedder_traits::EventLoopWaker;
use crate::webgl_thread::{WebGLThread, WebGLThreadInit};
use canvas_traits::webgl::{webgl_channel, WebVRRenderHandler};
use canvas_traits::webgl::{WebGLContextId, WebGLMsg, WebGLThreads};
use euclid::default::Size2D;
use fnv::FnvHashMap;
use gleam::gl;
use gleam;
use servo_config::pref;
use sparkle::gl;
use sparkle::gl::GlType;
use std::default::Default;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use surfman::platform::generic::universal::context::Context;
use surfman::platform::generic::universal::device::Device;
use surfman::platform::generic::universal::surface::SurfaceTexture;
use surfman_chains::SwapChains;
use webrender_traits::{WebrenderExternalImageApi, WebrenderExternalImageRegistry};
use webxr_api::WebGLExternalImageApi;
use webxr_api::SwapChainId as WebXRSwapChainId;
/// WebGL Threading API entry point that lives in the constellation.
pub struct WebGLThreads(WebGLSender<WebGLMsg>);
pub enum ThreadMode {
MainThread(Box<dyn EventLoopWaker>),
OffThread,
pub struct WebGLComm {
pub webgl_threads: WebGLThreads,
pub webxr_swap_chains: SwapChains<WebXRSwapChainId>,
pub image_handler: Box<dyn WebrenderExternalImageApi>,
pub output_handler: Option<Box<dyn webrender::OutputImageHandler>>,
}
impl WebGLThreads {
/// Creates a new WebGLThreads object
impl WebGLComm {
/// Creates a new `WebGLComm` object.
pub fn new(
gl_factory: GLContextFactory,
webrender_gl: Rc<dyn gl::Gl>,
device: Device,
context: Context,
webrender_gl: Rc<dyn gleam::gl::Gl>,
webrender_api_sender: webrender_api::RenderApiSender,
webvr_compositor: Option<Box<dyn WebVRRenderHandler>>,
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
mode: ThreadMode,
) -> (
WebGLThreads,
Option<Rc<WebGLMainThread>>,
Box<dyn webxr_api::WebGLExternalImageApi>,
Box<dyn WebrenderExternalImageApi>,
Option<Box<dyn webrender::OutputImageHandler>>,
) {
api_type: GlType,
) -> WebGLComm {
debug!("WebGLThreads::new()");
let (sender, receiver) = webgl_channel::<WebGLMsg>().unwrap();
let webrender_swap_chains = SwapChains::new();
let webxr_swap_chains = SwapChains::new();
// This implementation creates a single `WebGLThread` for all the pipelines.
let init = WebGLThreadInit {
gl_factory,
webrender_api_sender,
webvr_compositor,
external_images,
sender: sender.clone(),
receiver,
webrender_swap_chains: webrender_swap_chains.clone(),
webxr_swap_chains: webxr_swap_chains.clone(),
connection: device.connection(),
adapter: device.adapter(),
api_type,
};
let output_handler = if pref!(dom.webgl.dom_to_texture.enabled) {
Some(Box::new(OutputHandler::new(
webrender_gl.clone(),
sender.clone(),
)))
Some(Box::new(OutputHandler::new(webrender_gl.clone())))
} else {
None
};
let external = WebGLExternalImages::new(webrender_gl, sender.clone());
let external = WebGLExternalImages::new(device, context, webrender_swap_chains);
let webgl_thread = match mode {
ThreadMode::MainThread(event_loop_waker) => {
let thread = WebGLThread::run_on_current_thread(init, event_loop_waker);
Some(thread)
},
ThreadMode::OffThread => {
WebGLThread::run_on_own_thread(init);
None
},
};
WebGLThread::run_on_own_thread(init);
(
WebGLThreads(sender),
webgl_thread,
external.sendable.clone_box(),
Box::new(external),
output_handler.map(|b| b as Box<_>),
)
}
/// Gets the WebGLThread handle for each script pipeline.
pub fn pipeline(&self) -> WebGLPipeline {
// This mode creates a single thread, so the existing WebGLChan is just cloned.
WebGLPipeline(WebGLChan(self.0.clone()))
}
/// Sends a exit message to close the WebGLThreads and release all WebGLContexts.
pub fn exit(&self) -> Result<(), &'static str> {
self.0
.send(WebGLMsg::Exit)
.map_err(|_| "Failed to send Exit message")
}
}
/// Bridge between the webxr_api::ExternalImage callbacks and the WebGLThreads.
struct SendableWebGLExternalImages {
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 SendableWebGLExternalImages {
fn new(channel: WebGLSender<WebGLMsg>) -> Self {
Self {
webgl_channel: channel,
lock_channel: webgl_channel().unwrap(),
WebGLComm {
webgl_threads: WebGLThreads(sender),
webxr_swap_chains,
image_handler: Box::new(external),
output_handler: output_handler.map(|b| b as Box<_>),
}
}
}
impl SendableWebGLExternalImages {
fn lock_and_get_current_texture(&self, id: usize) -> (u32, Size2D<i32>, Option<gl::GLsync>) {
if let Some(main_thread) = WebGLMainThread::on_current_thread() {
// If we're on the same thread as WebGL, we can get the data directly
let (image_id, size) = main_thread
.thread_data
.borrow_mut()
.handle_lock_unsync(WebGLContextId(id as usize));
// We don't need a GLsync object if we're running on the main thread
// Might be better to return an option?
(image_id, size, None)
} 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();
(image_id, size, Some(gl_sync as gl::GLsync))
}
}
}
impl webxr_api::WebGLExternalImageApi for SendableWebGLExternalImages {
fn lock(&self, id: usize) -> Option<gl::GLsync> {
let (_, _, gl_sync) = self.lock_and_get_current_texture(id);
gl_sync
}
fn unlock(&self, id: usize) {
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()
}
}
fn clone_box(&self) -> Box<dyn webxr_api::WebGLExternalImageApi> {
Box::new(Self::new(self.webgl_channel.clone()))
}
}
/// Bridge between the webrender::ExternalImage callbacks and the WebGLThreads.
struct WebGLExternalImages {
webrender_gl: Rc<dyn gl::Gl>,
sendable: SendableWebGLExternalImages,
device: Device,
context: Context,
swap_chains: SwapChains<WebGLContextId>,
locked_front_buffers: FnvHashMap<WebGLContextId, SurfaceTexture>,
}
impl Drop for WebGLExternalImages {
fn drop(&mut self) {
let _ = self.device.destroy_context(&mut self.context);
}
}
impl WebGLExternalImages {
fn new(webrender_gl: Rc<dyn gl::Gl>, channel: WebGLSender<WebGLMsg>) -> Self {
fn new(device: Device, context: Context, swap_chains: SwapChains<WebGLContextId>) -> Self {
Self {
webrender_gl,
sendable: SendableWebGLExternalImages::new(channel),
device,
context,
swap_chains,
locked_front_buffers: FnvHashMap::default(),
}
}
fn lock_swap_chain(&mut self, id: WebGLContextId) -> Option<(u32, Size2D<i32>)> {
debug!("... locking chain {:?}", id);
let front_buffer = self.swap_chains.get(id)?.take_surface()?;
debug!("... getting texture for surface {:?}", front_buffer.id());
let size = front_buffer.size();
let front_buffer_texture = self
.device
.create_surface_texture(&mut self.context, front_buffer)
.unwrap();
let gl_texture = front_buffer_texture.gl_texture();
self.locked_front_buffers.insert(id, front_buffer_texture);
Some((gl_texture, size))
}
fn unlock_swap_chain(&mut self, id: WebGLContextId) -> Option<()> {
let locked_front_buffer = self.locked_front_buffers.remove(&id)?;
let locked_front_buffer = self
.device
.destroy_surface_texture(&mut self.context, locked_front_buffer)
.unwrap();
debug!("... unlocked chain {:?}", id);
self.swap_chains
.get(id)?
.recycle_surface(locked_front_buffer);
Some(())
}
}
impl WebrenderExternalImageApi for WebGLExternalImages {
fn lock(&mut self, id: u64) -> (u32, Size2D<i32>) {
let (image_id, size, gl_sync) = self.sendable.lock_and_get_current_texture(id as usize);
// 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.
if let Some(gl_sync) = gl_sync {
self.webrender_gl.wait_sync(gl_sync, 0, gl::TIMEOUT_IGNORED);
}
(image_id, size)
let id = WebGLContextId(id);
self.lock_swap_chain(id).unwrap_or_default()
}
fn unlock(&mut self, id: u64) {
self.sendable.unlock(id as usize);
let id = WebGLContextId(id);
self.unlock_swap_chain(id);
}
}
/// struct used to implement DOMToTexture feature and webrender::OutputImageHandler trait.
type OutputHandlerData = Option<(u32, Size2D<i32>)>;
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>,
webrender_gl: Rc<dyn gleam::gl::Gl>,
sync_objects: FnvHashMap<webrender_api::PipelineId, gleam::gl::GLsync>,
}
impl OutputHandler {
fn new(webrender_gl: Rc<dyn gl::Gl>, channel: WebGLSender<WebGLMsg>) -> Self {
fn new(webrender_gl: Rc<dyn gleam::gl::Gl>) -> Self {
OutputHandler {
webrender_gl,
webgl_channel: channel,
lock_channel: webgl_channel().unwrap(),
sync_objects: Default::default(),
}
}
@ -235,28 +171,8 @@ impl webrender::OutputImageHandler for OutputHandler {
.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()
};
result.map(|(tex_id, size)| {
(
tex_id,
webrender_api::units::FramebufferIntSize::new(size.width, size.height),
)
})
// https://github.com/servo/servo/issues/24615
None
}
fn unlock(&mut self, id: webrender_api::PipelineId) {