diff options
author | Emilio Cobos Álvarez <emilio@crisal.io> | 2017-08-16 16:42:13 +0200 |
---|---|---|
committer | Emilio Cobos Álvarez <emilio@crisal.io> | 2017-08-16 16:42:13 +0200 |
commit | cfe22e3979b7270833a4b450b25fb2157deb1da2 (patch) | |
tree | f77d68b734a6327898cc8c01505b0723bf45ed4a /components/canvas/webgl_paint_thread.rs | |
parent | ee94e2b7c0bd327abe8f9545b2a1f792f67a2bdd (diff) | |
download | servo-cfe22e3979b7270833a4b450b25fb2157deb1da2.tar.gz servo-cfe22e3979b7270833a4b450b25fb2157deb1da2.zip |
Revert "Auto merge of #17891 - MortimerGoro:webgl_move, r=glennw,emilio"
This reverts commit 90f55ea4580e2a15f7d70d0491444f18b972d450, reversing
changes made to 2e60b27a2186a8cba4b952960155dfcf3f47d7db.
Diffstat (limited to 'components/canvas/webgl_paint_thread.rs')
-rw-r--r-- | components/canvas/webgl_paint_thread.rs | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/components/canvas/webgl_paint_thread.rs b/components/canvas/webgl_paint_thread.rs new file mode 100644 index 00000000000..2b6819effba --- /dev/null +++ b/components/canvas/webgl_paint_thread.rs @@ -0,0 +1,379 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use canvas_traits::{CanvasCommonMsg, CanvasData, CanvasMsg, CanvasImageData}; +use canvas_traits::{FromLayoutMsg, FromScriptMsg, byte_swap}; +use euclid::Size2D; +use gleam::gl; +use ipc_channel::ipc::{self, IpcSender}; +use offscreen_gl_context::{ColorAttachmentType, GLContext, GLLimits}; +use offscreen_gl_context::{GLContextAttributes, NativeGLContext, OSMesaContext}; +use servo_config::opts; +use std::borrow::ToOwned; +use std::mem; +use std::sync::Arc; +use std::sync::mpsc::channel; +use std::thread; +use webrender_api; + +enum GLContextWrapper { + Native(GLContext<NativeGLContext>), + OSMesa(GLContext<OSMesaContext>), +} + +impl GLContextWrapper { + fn new(size: Size2D<i32>, + attributes: GLContextAttributes, + gl_type: gl::GlType) -> Result<GLContextWrapper, &'static str> { + if opts::get().should_use_osmesa() { + let ctx = GLContext::<OSMesaContext>::new(size, + attributes, + ColorAttachmentType::Texture, + gl_type, + None); + ctx.map(GLContextWrapper::OSMesa) + } else { + let ctx = GLContext::<NativeGLContext>::new(size, + attributes, + ColorAttachmentType::Texture, + gl_type, + None); + ctx.map(GLContextWrapper::Native) + } + } + + pub fn get_limits(&self) -> GLLimits { + match *self { + GLContextWrapper::Native(ref ctx) => { + ctx.borrow_limits().clone() + } + GLContextWrapper::OSMesa(ref ctx) => { + ctx.borrow_limits().clone() + } + } + } + + fn resize(&mut self, size: Size2D<i32>) -> Result<Size2D<i32>, &'static str> { + match *self { + GLContextWrapper::Native(ref mut ctx) => { + ctx.resize(size)?; + Ok(ctx.borrow_draw_buffer().unwrap().size()) + } + GLContextWrapper::OSMesa(ref mut ctx) => { + ctx.resize(size)?; + Ok(ctx.borrow_draw_buffer().unwrap().size()) + } + } + } + + fn gl(&self) -> &gl::Gl { + match *self { + GLContextWrapper::Native(ref ctx) => { + ctx.gl() + } + GLContextWrapper::OSMesa(ref ctx) => { + ctx.gl() + } + } + } + + pub fn make_current(&self) { + match *self { + GLContextWrapper::Native(ref ctx) => { + ctx.make_current().unwrap(); + } + GLContextWrapper::OSMesa(ref ctx) => { + ctx.make_current().unwrap(); + } + } + } + + pub fn apply_command(&self, cmd: webrender_api::WebGLCommand) { + match *self { + GLContextWrapper::Native(ref ctx) => { + cmd.apply(ctx); + } + GLContextWrapper::OSMesa(ref ctx) => { + cmd.apply(ctx); + } + } + } +} + +enum WebGLPaintTaskData { + WebRender(webrender_api::RenderApi, webrender_api::WebGLContextId), + Readback { + context: GLContextWrapper, + webrender_api: webrender_api::RenderApi, + image_key: Option<webrender_api::ImageKey>, + /// An old webrender image key that can be deleted when the next epoch ends. + old_image_key: Option<webrender_api::ImageKey>, + /// An old webrender image key that can be deleted when the current epoch ends. + very_old_image_key: Option<webrender_api::ImageKey>, + }, +} + +pub struct WebGLPaintThread { + size: Size2D<i32>, + data: WebGLPaintTaskData, +} + +fn create_readback_painter(size: Size2D<i32>, + attrs: GLContextAttributes, + webrender_api: webrender_api::RenderApi, + gl_type: gl::GlType) + -> Result<(WebGLPaintThread, GLLimits), String> { + let context = GLContextWrapper::new(size, attrs, gl_type)?; + let limits = context.get_limits(); + let painter = WebGLPaintThread { + size: size, + data: WebGLPaintTaskData::Readback { + context: context, + webrender_api: webrender_api, + image_key: None, + old_image_key: None, + very_old_image_key: None, + }, + }; + + Ok((painter, limits)) +} + +impl WebGLPaintThread { + fn new(size: Size2D<i32>, + attrs: GLContextAttributes, + webrender_api_sender: webrender_api::RenderApiSender, + gl_type: gl::GlType) + -> Result<(WebGLPaintThread, GLLimits), String> { + let wr_api = webrender_api_sender.create_api(); + let device_size = webrender_api::DeviceIntSize::from_untyped(&size); + match wr_api.request_webgl_context(&device_size, attrs) { + Ok((id, limits)) => { + let painter = WebGLPaintThread { + data: WebGLPaintTaskData::WebRender(wr_api, id), + size: size + }; + Ok((painter, limits)) + }, + Err(msg) => { + warn!("Initial context creation failed, falling back to readback: {}", msg); + create_readback_painter(size, attrs, wr_api, gl_type) + } + } + } + + fn handle_webgl_message(&self, message: webrender_api::WebGLCommand) { + debug!("WebGL message: {:?}", message); + match self.data { + WebGLPaintTaskData::WebRender(ref api, id) => { + api.send_webgl_command(id, message); + } + WebGLPaintTaskData::Readback { ref context, .. } => { + context.apply_command(message); + } + } + } + + fn handle_webvr_message(&self, message: webrender_api::VRCompositorCommand) { + match self.data { + WebGLPaintTaskData::WebRender(ref api, id) => { + api.send_vr_compositor_command(id, message); + } + WebGLPaintTaskData::Readback { .. } => { + error!("Webrender is required for WebVR implementation"); + } + } + } + + + /// Creates a new `WebGLPaintThread` and returns an `IpcSender` to + /// communicate with it. + pub fn start(size: Size2D<i32>, + attrs: GLContextAttributes, + webrender_api_sender: webrender_api::RenderApiSender) + -> Result<(IpcSender<CanvasMsg>, GLLimits), String> { + let (sender, receiver) = ipc::channel::<CanvasMsg>().unwrap(); + let (result_chan, result_port) = channel(); + thread::Builder::new().name("WebGLThread".to_owned()).spawn(move || { + let gl_type = gl::GlType::default(); + let mut painter = match WebGLPaintThread::new(size, attrs, webrender_api_sender, gl_type) { + Ok((thread, limits)) => { + result_chan.send(Ok(limits)).unwrap(); + thread + }, + Err(e) => { + result_chan.send(Err(e)).unwrap(); + return + } + }; + painter.init(); + loop { + match receiver.recv().unwrap() { + CanvasMsg::WebGL(message) => painter.handle_webgl_message(message), + CanvasMsg::Common(message) => { + match message { + CanvasCommonMsg::Close => break, + // TODO(emilio): handle error nicely + CanvasCommonMsg::Recreate(size) => painter.recreate(size).unwrap(), + } + }, + CanvasMsg::FromScript(message) => { + match message { + FromScriptMsg::SendPixels(chan) =>{ + // Read the comment on + // HTMLCanvasElement::fetch_all_data. + chan.send(None).unwrap(); + } + } + } + CanvasMsg::FromLayout(message) => { + match message { + FromLayoutMsg::SendData(chan) => + painter.send_data(chan), + } + } + CanvasMsg::Canvas2d(_) => panic!("Wrong message sent to WebGLThread"), + CanvasMsg::WebVR(message) => painter.handle_webvr_message(message) + } + } + }).expect("Thread spawning failed"); + + result_port.recv().unwrap().map(|limits| (sender, limits)) + } + + fn send_data(&mut self, chan: IpcSender<CanvasData>) { + match self.data { + WebGLPaintTaskData::Readback { + ref context, + ref webrender_api, + ref mut image_key, + ref mut old_image_key, + ref mut very_old_image_key, + } => { + let width = self.size.width as usize; + let height = self.size.height as usize; + + let mut pixels = context.gl().read_pixels(0, 0, + self.size.width as gl::GLsizei, + self.size.height as gl::GLsizei, + gl::RGBA, gl::UNSIGNED_BYTE); + // flip image vertically (texture is upside down) + let orig_pixels = pixels.clone(); + let stride = width * 4; + for y in 0..height { + let dst_start = y * stride; + let src_start = (height - y - 1) * stride; + let src_slice = &orig_pixels[src_start .. src_start + stride]; + (&mut pixels[dst_start .. dst_start + stride]).clone_from_slice(&src_slice[..stride]); + } + + // rgba -> bgra + byte_swap(&mut pixels); + + let descriptor = webrender_api::ImageDescriptor { + width: width as u32, + height: height as u32, + stride: None, + format: webrender_api::ImageFormat::BGRA8, + offset: 0, + is_opaque: false, + }; + let data = webrender_api::ImageData::Raw(Arc::new(pixels)); + + let mut updates = webrender_api::ResourceUpdates::new(); + + match *image_key { + Some(image_key) => { + updates.update_image(image_key, + descriptor, + data, + None); + } + None => { + *image_key = Some(webrender_api.generate_image_key()); + updates.add_image(image_key.unwrap(), + descriptor, + data, + None); + } + } + + if let Some(image_key) = mem::replace(very_old_image_key, old_image_key.take()) { + updates.delete_image(image_key); + } + + webrender_api.update_resources(updates); + + let image_data = CanvasImageData { + image_key: image_key.unwrap(), + }; + + chan.send(CanvasData::Image(image_data)).unwrap(); + } + WebGLPaintTaskData::WebRender(_, id) => { + chan.send(CanvasData::WebGL(id)).unwrap(); + } + } + } + + #[allow(unsafe_code)] + fn recreate(&mut self, size: Size2D<i32>) -> Result<(), &'static str> { + match self.data { + WebGLPaintTaskData::Readback { ref mut context, ref mut image_key, ref mut old_image_key, .. } => { + if size.width > self.size.width || + size.height > self.size.height { + self.size = context.resize(size)?; + } else { + self.size = size; + context.gl().scissor(0, 0, size.width, size.height); + } + // Webrender doesn't let images change size, so we clear the webrender image key. + if let Some(image_key) = image_key.take() { + // If this executes, then we are in a new epoch since we last recreated the canvas, + // so `old_image_key` must be `None`. + debug_assert!(old_image_key.is_none()); + *old_image_key = Some(image_key); + } + } + WebGLPaintTaskData::WebRender(ref api, id) => { + let device_size = webrender_api::DeviceIntSize::from_untyped(&size); + api.resize_webgl_context(id, &device_size); + } + } + + Ok(()) + } + + fn init(&mut self) { + if let WebGLPaintTaskData::Readback { ref context, .. } = self.data { + context.make_current(); + } + } +} + +impl Drop for WebGLPaintThread { + fn drop(&mut self) { + if let WebGLPaintTaskData::Readback { + ref mut webrender_api, + image_key, + old_image_key, + very_old_image_key, + .. + } = self.data { + let mut updates = webrender_api::ResourceUpdates::new(); + + if let Some(image_key) = image_key { + updates.delete_image(image_key); + } + if let Some(image_key) = old_image_key { + updates.delete_image(image_key); + } + if let Some(image_key) = very_old_image_key { + updates.delete_image(image_key); + } + + webrender_api.update_resources(updates); + } + } +} |