aboutsummaryrefslogtreecommitdiffstats
path: root/components/canvas/webgl_paint_thread.rs
diff options
context:
space:
mode:
authorEmilio Cobos Álvarez <emilio@crisal.io>2017-08-16 16:42:13 +0200
committerEmilio Cobos Álvarez <emilio@crisal.io>2017-08-16 16:42:13 +0200
commitcfe22e3979b7270833a4b450b25fb2157deb1da2 (patch)
treef77d68b734a6327898cc8c01505b0723bf45ed4a /components/canvas/webgl_paint_thread.rs
parentee94e2b7c0bd327abe8f9545b2a1f792f67a2bdd (diff)
downloadservo-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.rs379
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);
+ }
+ }
+}