diff options
author | Josh Matthews <josh@joshmatthews.net> | 2020-06-08 13:53:33 -0400 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2020-06-09 13:05:16 -0400 |
commit | 75efaa95f53dd0f110d02d5d4009bb491290af4d (patch) | |
tree | face9912a579fe911f9a43b11dbc7f0c7f71a5b2 | |
parent | a6016b3a62804788ad23358e34fa5c1f50716f9a (diff) | |
download | servo-75efaa95f53dd0f110d02d5d4009bb491290af4d.tar.gz servo-75efaa95f53dd0f110d02d5d4009bb491290af4d.zip |
Proxy all WR interactions for layout/font/script/canvas threads to the compositor
thread. There is now a single RenderApi that is used, and all transactions are serialized
through the compositor.
-rw-r--r-- | Cargo.lock | 30 | ||||
-rw-r--r-- | components/canvas/canvas_data.rs | 35 | ||||
-rw-r--r-- | components/canvas/canvas_paint_thread.rs | 43 | ||||
-rw-r--r-- | components/canvas/webgl_thread.rs | 8 | ||||
-rw-r--r-- | components/canvas_traits/lib.rs | 2 | ||||
-rw-r--r-- | components/compositing/Cargo.toml | 1 | ||||
-rw-r--r-- | components/compositing/compositor.rs | 163 | ||||
-rw-r--r-- | components/compositing/compositor_thread.rs | 29 | ||||
-rw-r--r-- | components/constellation/constellation.rs | 148 | ||||
-rw-r--r-- | components/gfx/font_cache_thread.rs | 42 | ||||
-rw-r--r-- | components/gfx_traits/Cargo.toml | 2 | ||||
-rw-r--r-- | components/gfx_traits/lib.rs | 14 | ||||
-rw-r--r-- | components/layout/display_list/webrender_helpers.rs | 10 | ||||
-rw-r--r-- | components/layout_2020/display_list/stacking_context.rs | 9 | ||||
-rw-r--r-- | components/servo/Cargo.toml | 2 | ||||
-rw-r--r-- | components/servo/lib.rs | 69 |
16 files changed, 345 insertions, 262 deletions
diff --git a/Cargo.lock b/Cargo.lock index a961d3ae81d..e13c7dff6a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,6 +755,7 @@ dependencies = [ name = "compositing" version = "0.0.1" dependencies = [ + "app_units", "canvas", "crossbeam-channel", "embedder_traits", @@ -1892,10 +1893,12 @@ dependencies = [ name = "gfx_traits" version = "0.0.1" dependencies = [ + "app_units", "malloc_size_of", "malloc_size_of_derive", "range", "serde", + "webrender_api", ] [[package]] @@ -3018,6 +3021,7 @@ dependencies = [ name = "libservo" version = "0.0.1" dependencies = [ + "app_units", "background_hang_monitor", "bluetooth", "bluetooth_traits", @@ -3034,6 +3038,7 @@ dependencies = [ "euclid", "gaol", "gfx", + "gfx_traits", "gleam 0.11.0", "gstreamer", "ipc-channel", @@ -3928,7 +3933,7 @@ dependencies = [ [[package]] name = "peek-poke" version = "0.2.0" -source = "git+https://github.com/servo/webrender#01082a9091ab98c392af8934d04271eb1dd546df" +source = "git+https://github.com/servo/webrender#de3999583ab20aad7c57ea35a3e0394ed45be627" dependencies = [ "euclid", "peek-poke-derive 0.2.1 (git+https://github.com/servo/webrender)", @@ -3946,7 +3951,7 @@ dependencies = [ [[package]] name = "peek-poke-derive" version = "0.2.1" -source = "git+https://github.com/servo/webrender#01082a9091ab98c392af8934d04271eb1dd546df" +source = "git+https://github.com/servo/webrender#de3999583ab20aad7c57ea35a3e0394ed45be627" dependencies = [ "proc-macro2 1.0.17", "quote 1.0.2", @@ -4413,15 +4418,6 @@ checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" [[package]] name = "ron" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da06feaa07f69125ab9ddc769b11de29090122170b402547f64b86fe16ebc399" -dependencies = [ - "serde", -] - -[[package]] -name = "ron" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" @@ -6401,7 +6397,7 @@ dependencies = [ [[package]] name = "webrender" version = "0.61.0" -source = "git+https://github.com/servo/webrender#01082a9091ab98c392af8934d04271eb1dd546df" +source = "git+https://github.com/servo/webrender#de3999583ab20aad7c57ea35a3e0394ed45be627" dependencies = [ "base64 0.10.1", "bincode", @@ -6427,7 +6423,7 @@ dependencies = [ "num-traits", "plane-split", "rayon", - "ron 0.1.7", + "ron", "serde", "serde_json", "smallvec 1.3.0", @@ -6443,7 +6439,7 @@ dependencies = [ [[package]] name = "webrender_api" version = "0.61.0" -source = "git+https://github.com/servo/webrender#01082a9091ab98c392af8934d04271eb1dd546df" +source = "git+https://github.com/servo/webrender#de3999583ab20aad7c57ea35a3e0394ed45be627" dependencies = [ "app_units", "bitflags", @@ -6464,7 +6460,7 @@ dependencies = [ [[package]] name = "webrender_build" version = "0.0.1" -source = "git+https://github.com/servo/webrender#01082a9091ab98c392af8934d04271eb1dd546df" +source = "git+https://github.com/servo/webrender#de3999583ab20aad7c57ea35a3e0394ed45be627" dependencies = [ "bitflags", "lazy_static", @@ -6544,7 +6540,7 @@ dependencies = [ "naga", "parking_lot 0.10.2", "peek-poke 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ron 0.5.1", + "ron", "serde", "smallvec 1.3.0", "spirv_headers", @@ -6644,7 +6640,7 @@ dependencies = [ [[package]] name = "wr_malloc_size_of" version = "0.0.1" -source = "git+https://github.com/servo/webrender#01082a9091ab98c392af8934d04271eb1dd546df" +source = "git+https://github.com/servo/webrender#de3999583ab20aad7c57ea35a3e0394ed45be627" dependencies = [ "app_units", "euclid", diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 9c3aae1d0be..1a4d1ccfc98 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -2,7 +2,7 @@ * 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::canvas_paint_thread::AntialiasMode; +use crate::canvas_paint_thread::{AntialiasMode, ImageUpdate, WebrenderApi}; use crate::raqote_backend::Repetition; use canvas_traits::canvas::*; use cssparser::RGBA; @@ -13,7 +13,6 @@ use num_traits::ToPrimitive; use std::marker::PhantomData; use std::mem; use std::sync::Arc; -use webrender::api::DirtyRect; use webrender_api::units::RectExt as RectExt_; /// The canvas data stores a state machine for the current status of @@ -367,8 +366,7 @@ pub struct CanvasData<'a> { path_state: Option<PathState>, state: CanvasPaintState<'a>, saved_states: Vec<CanvasPaintState<'a>>, - webrender_api: webrender_api::RenderApi, - webrender_doc: webrender_api::DocumentId, + webrender_api: Box<dyn WebrenderApi>, 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>, @@ -384,22 +382,19 @@ fn create_backend() -> Box<dyn Backend> { impl<'a> CanvasData<'a> { pub fn new( size: Size2D<u64>, - webrender_api_sender: webrender_api::RenderApiSender, - webrender_doc: webrender_api::DocumentId, + webrender_api: Box<dyn WebrenderApi>, antialias: AntialiasMode, canvas_id: CanvasId, ) -> CanvasData<'a> { let backend = create_backend(); let draw_target = backend.create_drawtarget(size); - let webrender_api = webrender_api_sender.create_api(); CanvasData { backend, drawtarget: draw_target, path_state: None, state: CanvasPaintState::new(antialias), saved_states: vec![], - webrender_api: webrender_api, - webrender_doc, + webrender_api, image_key: None, old_image_key: None, very_old_image_key: None, @@ -979,27 +974,28 @@ impl<'a> CanvasData<'a> { let data = self.drawtarget.snapshot_data_owned(); let data = webrender_api::ImageData::Raw(Arc::new(data)); - let mut txn = webrender_api::Transaction::new(); + let mut updates = vec![]; match self.image_key { Some(image_key) => { debug!("Updating image {:?}.", image_key); - txn.update_image(image_key, descriptor, data, &DirtyRect::All); + updates.push(ImageUpdate::Update(image_key, descriptor, data)); }, None => { - self.image_key = Some(self.webrender_api.generate_image_key()); + let key = self.webrender_api.generate_key(); + updates.push(ImageUpdate::Add(key, descriptor, data)); + self.image_key = Some(key); debug!("New image {:?}.", self.image_key); - txn.add_image(self.image_key.unwrap(), descriptor, data, None); }, } if let Some(image_key) = mem::replace(&mut self.very_old_image_key, self.old_image_key.take()) { - txn.delete_image(image_key); + updates.push(ImageUpdate::Delete(image_key)); } - self.webrender_api.send_transaction(self.webrender_doc, txn); + self.webrender_api.update_images(updates); let data = CanvasImageData { image_key: self.image_key.unwrap(), @@ -1110,16 +1106,15 @@ impl<'a> CanvasData<'a> { impl<'a> Drop for CanvasData<'a> { fn drop(&mut self) { - let mut txn = webrender_api::Transaction::new(); - + let mut updates = vec![]; if let Some(image_key) = self.old_image_key.take() { - txn.delete_image(image_key); + updates.push(ImageUpdate::Delete(image_key)); } if let Some(image_key) = self.very_old_image_key.take() { - txn.delete_image(image_key); + updates.push(ImageUpdate::Delete(image_key)); } - self.webrender_api.send_transaction(self.webrender_doc, txn); + self.webrender_api.update_images(updates); } } diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index f73d8f7b9a8..6832a5cddc8 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -12,35 +12,52 @@ use ipc_channel::router::ROUTER; use std::borrow::ToOwned; use std::collections::HashMap; use std::thread; +use webrender_api::{ImageData, ImageDescriptor, ImageKey}; pub enum AntialiasMode { Default, None, } +pub enum ImageUpdate { + Add(ImageKey, ImageDescriptor, ImageData), + Update(ImageKey, ImageDescriptor, ImageData), + Delete(ImageKey), +} + +pub trait WebrenderApi { + fn generate_key(&self) -> webrender_api::ImageKey; + fn update_images(&self, updates: Vec<ImageUpdate>); + fn clone(&self) -> Box<dyn WebrenderApi>; +} + pub struct CanvasPaintThread<'a> { canvases: HashMap<CanvasId, CanvasData<'a>>, next_canvas_id: CanvasId, + webrender_api: Box<dyn WebrenderApi>, } impl<'a> CanvasPaintThread<'a> { - fn new() -> CanvasPaintThread<'a> { + fn new(webrender_api: Box<dyn WebrenderApi>) -> CanvasPaintThread<'a> { CanvasPaintThread { canvases: HashMap::new(), next_canvas_id: CanvasId(0), + webrender_api, } } /// Creates a new `CanvasPaintThread` and returns an `IpcSender` to /// communicate with it. - pub fn start() -> (Sender<ConstellationCanvasMsg>, IpcSender<CanvasMsg>) { + pub fn start( + webrender_api: Box<dyn WebrenderApi + Send>, + ) -> (Sender<ConstellationCanvasMsg>, IpcSender<CanvasMsg>) { let (ipc_sender, ipc_receiver) = ipc::channel::<CanvasMsg>().unwrap(); let msg_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_receiver); let (create_sender, create_receiver) = unbounded(); thread::Builder::new() .name("CanvasThread".to_owned()) .spawn(move || { - let mut canvas_paint_thread = CanvasPaintThread::new(); + let mut canvas_paint_thread = CanvasPaintThread::new(webrender_api); loop { select! { recv(msg_receiver) -> msg => { @@ -74,16 +91,9 @@ impl<'a> CanvasPaintThread<'a> { Ok(ConstellationCanvasMsg::Create { id_sender: creator, size, - webrender_sender: webrenderer_api_sender, - webrender_doc, antialias }) => { - let canvas_id = canvas_paint_thread.create_canvas( - size, - webrenderer_api_sender, - webrender_doc, - antialias, - ); + let canvas_id = canvas_paint_thread.create_canvas(size, antialias); creator.send(canvas_id).unwrap(); }, Ok(ConstellationCanvasMsg::Exit) => break, @@ -101,13 +111,7 @@ impl<'a> CanvasPaintThread<'a> { (create_sender, ipc_sender) } - pub fn create_canvas( - &mut self, - size: Size2D<u64>, - webrender_api_sender: webrender_api::RenderApiSender, - webrender_doc: webrender_api::DocumentId, - antialias: bool, - ) -> CanvasId { + pub fn create_canvas(&mut self, size: Size2D<u64>, antialias: bool) -> CanvasId { let antialias = if antialias { AntialiasMode::Default } else { @@ -119,8 +123,7 @@ impl<'a> CanvasPaintThread<'a> { let canvas_data = CanvasData::new( size, - webrender_api_sender, - webrender_doc, + self.webrender_api.clone(), antialias, canvas_id.clone(), ); diff --git a/components/canvas/webgl_thread.rs b/components/canvas/webgl_thread.rs index 9b7b4ed4683..0d8bc69cb77 100644 --- a/components/canvas/webgl_thread.rs +++ b/components/canvas/webgl_thread.rs @@ -655,7 +655,7 @@ impl WebGLThread { ); let image_key = Self::create_wr_external_image( - &self.webrender_api, + &mut self.webrender_api, self.webrender_doc, size.to_i32(), has_alpha, @@ -718,7 +718,7 @@ impl WebGLThread { .contains(ContextAttributeFlags::ALPHA); let texture_target = current_wr_texture_target(&self.device); Self::update_wr_external_image( - &self.webrender_api, + &mut self.webrender_api, self.webrender_doc, size.to_i32(), has_alpha, @@ -1021,7 +1021,7 @@ impl WebGLThread { /// Creates a `webrender_api::ImageKey` that uses shared textures. fn create_wr_external_image( - webrender_api: &webrender_api::RenderApi, + webrender_api: &mut webrender_api::RenderApi, webrender_doc: webrender_api::DocumentId, size: Size2D<i32>, alpha: bool, @@ -1041,7 +1041,7 @@ impl WebGLThread { /// Updates a `webrender_api::ImageKey` that uses shared textures. fn update_wr_external_image( - webrender_api: &webrender_api::RenderApi, + webrender_api: &mut webrender_api::RenderApi, webrender_doc: webrender_api::DocumentId, size: Size2D<i32>, alpha: bool, diff --git a/components/canvas_traits/lib.rs b/components/canvas_traits/lib.rs index ec510e6281d..467eb432e0f 100644 --- a/components/canvas_traits/lib.rs +++ b/components/canvas_traits/lib.rs @@ -26,8 +26,6 @@ pub enum ConstellationCanvasMsg { Create { id_sender: Sender<CanvasId>, size: Size2D<u64>, - webrender_sender: webrender_api::RenderApiSender, - webrender_doc: webrender_api::DocumentId, antialias: bool, }, Exit, diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml index 027d16233bf..97419f37752 100644 --- a/components/compositing/Cargo.toml +++ b/components/compositing/Cargo.toml @@ -16,6 +16,7 @@ default = [] gl = ["gleam", "pixels"] [dependencies] +app_units = "0.7" canvas = { path = "../canvas" } crossbeam-channel = "0.4" embedder_traits = { path = "../embedder_traits" } diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index c3c545af4bb..1b0929dda74 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -3,7 +3,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::compositor_thread::CompositorReceiver; -use crate::compositor_thread::{InitialCompositorState, Msg}; +use crate::compositor_thread::{ + InitialCompositorState, Msg, WebrenderCanvasMsg, WebrenderFontMsg, WebrenderMsg, +}; #[cfg(feature = "gl")] use crate::gl; use crate::touch::{TouchAction, TouchHandler}; @@ -11,10 +13,11 @@ use crate::windowing::{ self, EmbedderCoordinates, MouseWindowEvent, WebRenderDebugOption, WindowMethods, }; use crate::{CompositionPipeline, ConstellationMsg, SendableFrameTree}; +use canvas::canvas_paint_thread::ImageUpdate; use crossbeam_channel::Sender; use embedder_traits::Cursor; use euclid::{Point2D, Rect, Scale, Vector2D}; -use gfx_traits::Epoch; +use gfx_traits::{Epoch, FontData}; #[cfg(feature = "gl")] use image::{DynamicImage, ImageFormat}; use ipc_channel::ipc; @@ -40,8 +43,6 @@ use std::fs::{create_dir_all, File}; use std::io::Write; use std::num::NonZeroU32; use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; use style_traits::viewport::ViewportConstraints; use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor}; use time::{now, precise_time_ns, precise_time_s}; @@ -218,7 +219,7 @@ pub struct IOCompositor<Window: WindowMethods + ?Sized> { /// True if a WR frame render has been requested. Screenshots /// taken before the render is complete will not reflect the /// most up to date rendering. - waiting_on_pending_frame: Arc<AtomicBool>, + waiting_on_pending_frame: bool, } #[derive(Clone, Copy)] @@ -334,7 +335,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { is_running_problem_test, exit_after_load, convert_mouse_to_touch, - waiting_on_pending_frame: state.pending_wr_frame, + waiting_on_pending_frame: false, } } @@ -443,7 +444,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { }, (Msg::Recomposite(reason), ShutdownState::NotShuttingDown) => { - self.waiting_on_pending_frame.store(false, Ordering::SeqCst); + self.waiting_on_pending_frame = false; self.composition_request = CompositionRequest::CompositeNow(reason) }, @@ -474,7 +475,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { self.ready_to_save_state, ReadyState::WaitingForConstellationReply ); - if is_ready && !self.waiting_on_pending_frame.load(Ordering::SeqCst) { + if is_ready && !self.waiting_on_pending_frame { self.ready_to_save_state = ReadyState::ReadyToSaveImage; if self.is_running_problem_test { println!("ready to save image!"); @@ -569,6 +570,10 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { } }, + (Msg::Webrender(msg), ShutdownState::NotShuttingDown) => { + self.handle_webrender_message(msg); + }, + // When we are shutting_down, we need to avoid performing operations // such as Paint that may crash because we have begun tearing down // the rest of our resources. @@ -578,6 +583,148 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { true } + /// Accept messages from content processes that need to be relayed to the WebRender + /// instance in the parent process. + fn handle_webrender_message(&mut self, msg: WebrenderMsg) { + match msg { + WebrenderMsg::Layout(script_traits::WebrenderMsg::SendInitialTransaction( + _doc, + pipeline, + )) => { + self.waiting_on_pending_frame = true; + let mut txn = webrender_api::Transaction::new(); + txn.set_display_list( + webrender_api::Epoch(0), + None, + Default::default(), + (pipeline, Default::default(), Default::default()), + false, + ); + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + + WebrenderMsg::Layout(script_traits::WebrenderMsg::SendScrollNode( + _doc, + point, + scroll_id, + clamping, + )) => { + let mut txn = webrender_api::Transaction::new(); + txn.scroll_node_with_id(point, scroll_id, clamping); + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + + WebrenderMsg::Layout(script_traits::WebrenderMsg::SendDisplayList( + _doc, + epoch, + size, + pipeline, + size2, + data, + descriptor, + )) => { + self.waiting_on_pending_frame = true; + let mut txn = webrender_api::Transaction::new(); + txn.set_display_list( + epoch, + None, + size, + ( + pipeline, + size2, + webrender_api::BuiltDisplayList::from_data(data, descriptor), + ), + true, + ); + txn.generate_frame(); + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + + WebrenderMsg::Layout(script_traits::WebrenderMsg::HitTest( + _doc, + pipeline, + point, + flags, + sender, + )) => { + let result = + self.webrender_api + .hit_test(self.webrender_document, pipeline, point, flags); + let _ = sender.send(result); + }, + + WebrenderMsg::Layout(script_traits::WebrenderMsg::GenerateImageKey(sender)) | + WebrenderMsg::Net(net_traits::WebrenderImageMsg::GenerateImageKey(sender)) => { + let _ = sender.send(self.webrender_api.generate_image_key()); + }, + + WebrenderMsg::Layout(script_traits::WebrenderMsg::UpdateImages(updates)) => { + let mut txn = webrender_api::Transaction::new(); + for update in updates { + match update { + script_traits::ImageUpdate::AddImage(key, desc, data) => { + txn.add_image(key, desc, data, None) + }, + script_traits::ImageUpdate::DeleteImage(key) => txn.delete_image(key), + script_traits::ImageUpdate::UpdateImage(key, desc, data) => { + txn.update_image(key, desc, data, &webrender_api::DirtyRect::All) + }, + } + } + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + + WebrenderMsg::Net(net_traits::WebrenderImageMsg::AddImage(key, desc, data)) => { + let mut txn = webrender_api::Transaction::new(); + txn.add_image(key, desc, data, None); + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + WebrenderMsg::Font(WebrenderFontMsg::AddFontInstance(font_key, size, sender)) => { + let key = self.webrender_api.generate_font_instance_key(); + let mut txn = webrender_api::Transaction::new(); + txn.add_font_instance(key, font_key, size, None, None, Vec::new()); + self.webrender_api + .send_transaction(self.webrender_document, txn); + let _ = sender.send(key); + }, + WebrenderMsg::Font(WebrenderFontMsg::AddFont(data, sender)) => { + let font_key = self.webrender_api.generate_font_key(); + let mut txn = webrender_api::Transaction::new(); + match data { + FontData::Raw(bytes) => txn.add_raw_font(font_key, bytes, 0), + FontData::Native(native_font) => txn.add_native_font(font_key, native_font), + } + self.webrender_api + .send_transaction(self.webrender_document, txn); + let _ = sender.send(font_key); + }, + WebrenderMsg::Canvas(WebrenderCanvasMsg::GenerateKey(sender)) => { + let _ = sender.send(self.webrender_api.generate_image_key()); + }, + WebrenderMsg::Canvas(WebrenderCanvasMsg::UpdateImages(updates)) => { + let mut txn = webrender_api::Transaction::new(); + for update in updates { + match update { + ImageUpdate::Add(key, descriptor, data) => { + txn.add_image(key, descriptor, data, None) + }, + ImageUpdate::Update(key, descriptor, data) => { + txn.update_image(key, descriptor, data, &webrender_api::DirtyRect::All) + }, + ImageUpdate::Delete(key) => txn.delete_image(key), + } + } + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + } + } + /// Sets or unsets the animations-running flag for the given pipeline, and schedules a /// recomposite if necessary. fn change_running_animations_state( diff --git a/components/compositing/compositor_thread.rs b/components/compositing/compositor_thread.rs index cbd715af0dd..01fb92a544e 100644 --- a/components/compositing/compositor_thread.rs +++ b/components/compositing/compositor_thread.rs @@ -6,6 +6,7 @@ use crate::compositor::CompositingReason; use crate::{ConstellationMsg, SendableFrameTree}; +use canvas::canvas_paint_thread::ImageUpdate; use crossbeam_channel::{Receiver, Sender}; use embedder_traits::EventLoopWaker; use euclid::Rect; @@ -18,8 +19,6 @@ use profile_traits::time; use script_traits::{AnimationState, EventResult, MouseButton, MouseEventType}; use std::fmt::{Debug, Error, Formatter}; use std::rc::Rc; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; use style_traits::viewport::ViewportConstraints; use style_traits::CSSPixel; use webrender_api; @@ -122,6 +121,30 @@ pub enum Msg { GetScreenSize(IpcSender<DeviceIntSize>), /// Get screen available size. GetScreenAvailSize(IpcSender<DeviceIntSize>), + + /// Webrender operations requested from non-compositor threads. + Webrender(WebrenderMsg), +} + +pub enum WebrenderFontMsg { + AddFontInstance( + webrender_api::FontKey, + app_units::Au, + Sender<webrender_api::FontInstanceKey>, + ), + AddFont(gfx_traits::FontData, Sender<webrender_api::FontKey>), +} + +pub enum WebrenderCanvasMsg { + GenerateKey(Sender<webrender_api::ImageKey>), + UpdateImages(Vec<ImageUpdate>), +} + +pub enum WebrenderMsg { + Layout(script_traits::WebrenderMsg), + Net(net_traits::WebrenderImageMsg), + Font(WebrenderFontMsg), + Canvas(WebrenderCanvasMsg), } impl Debug for Msg { @@ -148,6 +171,7 @@ impl Debug for Msg { Msg::GetClientWindow(..) => write!(f, "GetClientWindow"), Msg::GetScreenSize(..) => write!(f, "GetScreenSize"), Msg::GetScreenAvailSize(..) => write!(f, "GetScreenAvailSize"), + Msg::Webrender(..) => write!(f, "Webrender"), } } } @@ -171,5 +195,4 @@ pub struct InitialCompositorState { pub webrender_surfman: WebrenderSurfman, pub webrender_gl: Rc<dyn gleam::gl::Gl>, pub webxr_main_thread: webxr::MainThreadRegistry, - pub pending_wr_frame: Arc<AtomicBool>, } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index d7408ac549e..1ea79bc3209 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -109,6 +109,7 @@ use canvas_traits::webgl::WebGLThreads; use canvas_traits::ConstellationCanvasMsg; use compositing::compositor_thread::CompositorProxy; use compositing::compositor_thread::Msg as ToCompositorMsg; +use compositing::compositor_thread::WebrenderMsg; use compositing::{ConstellationMsg as FromCompositorMsg, SendableFrameTree}; use crossbeam_channel::{after, never, unbounded, Receiver, Sender}; use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg}; @@ -171,7 +172,6 @@ use std::marker::PhantomData; use std::mem::replace; use std::process; use std::rc::{Rc, Weak}; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; use style_traits::viewport::ViewportConstraints; @@ -384,10 +384,6 @@ pub struct Constellation<Message, LTF, STF, SWF> { /// A single WebRender document the constellation operates on. webrender_document: webrender_api::DocumentId, - /// A channel for the constellation to send messages to the - /// WebRender thread. - webrender_api_sender: webrender_api::RenderApiSender, - /// A channel for content processes to send messages that will /// be relayed to the WebRender thread. webrender_api_ipc_sender: script_traits::WebrenderIpcSender, @@ -537,9 +533,6 @@ pub struct InitialConstellationState { /// Webrender document ID. pub webrender_document: webrender_api::DocumentId, - /// Webrender API. - pub webrender_api_sender: webrender_api::RenderApiSender, - /// Entry point to create and get channels to a WebGLThread. pub webgl_threads: Option<WebGLThreads>, @@ -554,9 +547,6 @@ pub struct InitialConstellationState { /// Mechanism to force the compositor to process events. pub event_loop_waker: Option<Box<dyn EventLoopWaker>>, - /// A flag share with the compositor to indicate that a WR frame is in progress. - pub pending_wr_frame: Arc<AtomicBool>, - /// User agent string to report in network requests. pub user_agent: Cow<'static, str>, } @@ -726,113 +716,6 @@ where mpsc_receiver } -enum WebrenderMsg { - Layout(script_traits::WebrenderMsg), - Net(net_traits::WebrenderImageMsg), -} - -/// Accept messages from content processes that need to be relayed to the WebRender -/// instance in the parent process. -fn handle_webrender_message( - pending_wr_frame: &AtomicBool, - webrender_api: &webrender_api::RenderApi, - webrender_doc: webrender_api::DocumentId, - msg: WebrenderMsg, -) { - match msg { - WebrenderMsg::Layout(script_traits::WebrenderMsg::SendInitialTransaction( - doc, - pipeline, - )) => { - pending_wr_frame.store(true, Ordering::SeqCst); - let mut txn = webrender_api::Transaction::new(); - txn.set_display_list( - webrender_api::Epoch(0), - None, - Default::default(), - (pipeline, Default::default(), Default::default()), - false, - ); - webrender_api.send_transaction(doc, txn); - }, - - WebrenderMsg::Layout(script_traits::WebrenderMsg::SendScrollNode( - doc, - point, - scroll_id, - clamping, - )) => { - let mut txn = webrender_api::Transaction::new(); - txn.scroll_node_with_id(point, scroll_id, clamping); - webrender_api.send_transaction(doc, txn); - }, - - WebrenderMsg::Layout(script_traits::WebrenderMsg::SendDisplayList( - doc, - epoch, - size, - pipeline, - size2, - data, - descriptor, - )) => { - pending_wr_frame.store(true, Ordering::SeqCst); - let mut txn = webrender_api::Transaction::new(); - txn.set_display_list( - epoch, - None, - size, - ( - pipeline, - size2, - webrender_api::BuiltDisplayList::from_data(data, descriptor), - ), - true, - ); - txn.generate_frame(); - webrender_api.send_transaction(doc, txn); - }, - - WebrenderMsg::Layout(script_traits::WebrenderMsg::HitTest( - doc, - pipeline, - point, - flags, - sender, - )) => { - let result = webrender_api.hit_test(doc, pipeline, point, flags); - let _ = sender.send(result); - }, - - WebrenderMsg::Layout(script_traits::WebrenderMsg::GenerateImageKey(sender)) | - WebrenderMsg::Net(net_traits::WebrenderImageMsg::GenerateImageKey(sender)) => { - let _ = sender.send(webrender_api.generate_image_key()); - }, - - WebrenderMsg::Layout(script_traits::WebrenderMsg::UpdateImages(updates)) => { - let mut txn = webrender_api::Transaction::new(); - for update in updates { - match update { - script_traits::ImageUpdate::AddImage(key, desc, data) => { - txn.add_image(key, desc, data, None) - }, - script_traits::ImageUpdate::DeleteImage(key) => txn.delete_image(key), - script_traits::ImageUpdate::UpdateImage(key, desc, data) => { - txn.update_image(key, desc, data, &webrender_api::DirtyRect::All) - }, - } - } - webrender_api.send_transaction(webrender_doc, txn); - }, - - WebrenderMsg::Net(net_traits::WebrenderImageMsg::AddImage(key, desc, data)) => { - let mut txn = webrender_api::Transaction::new(); - txn.add_image(key, desc, data, None); - webrender_api.send_transaction(webrender_doc, txn); - }, - } -} - impl<Message, LTF, STF, SWF> Constellation<Message, LTF, STF, SWF> where LTF: LayoutThreadFactory<Message = Message>, @@ -931,32 +814,23 @@ where let (webrender_image_ipc_sender, webrender_image_ipc_receiver) = ipc::channel().expect("ipc channel failure"); - let webrender_api = state.webrender_api_sender.create_api(); - let webrender_doc = state.webrender_document; - let pending_wr_frame_clone = state.pending_wr_frame.clone(); + let compositor_proxy = state.compositor_proxy.clone(); ROUTER.add_route( webrender_ipc_receiver.to_opaque(), Box::new(move |message| { - handle_webrender_message( - &pending_wr_frame_clone, - &webrender_api, - webrender_doc, + let _ = compositor_proxy.send(ToCompositorMsg::Webrender( WebrenderMsg::Layout(message.to().expect("conversion failure")), - ) + )); }), ); - let webrender_api = state.webrender_api_sender.create_api(); - let pending_wr_frame_clone = state.pending_wr_frame.clone(); + let compositor_proxy = state.compositor_proxy.clone(); ROUTER.add_route( webrender_image_ipc_receiver.to_opaque(), Box::new(move |message| { - handle_webrender_message( - &pending_wr_frame_clone, - &webrender_api, - webrender_doc, + let _ = compositor_proxy.send(ToCompositorMsg::Webrender( WebrenderMsg::Net(message.to().expect("conversion failure")), - ) + )); }), ); @@ -1009,7 +883,6 @@ where scheduler_receiver, document_states: HashMap::new(), webrender_document: state.webrender_document, - webrender_api_sender: state.webrender_api_sender, webrender_api_ipc_sender: script_traits::WebrenderIpcSender::new( webrender_ipc_sender, ), @@ -1474,10 +1347,10 @@ where )) } recv(self.swmanager_receiver) -> msg => { - msg.expect("Unexpected panic channel panic in constellation").map(Request::FromSWManager) + msg.expect("Unexpected SW channel panic in constellation").map(Request::FromSWManager) } recv(self.scheduler_receiver) -> msg => { - msg.expect("Unexpected panic channel panic in constellation").map(Request::Timer) + msg.expect("Unexpected schedule channel panic in constellation").map(Request::Timer) } recv(scheduler_timeout) -> _ => { // Note: by returning, we go back to the top, @@ -4386,14 +4259,11 @@ where size: UntypedSize2D<u64>, response_sender: IpcSender<(IpcSender<CanvasMsg>, CanvasId)>, ) { - let webrender_api = self.webrender_api_sender.clone(); let (canvas_id_sender, canvas_id_receiver) = unbounded(); if let Err(e) = self.canvas_chan.send(ConstellationCanvasMsg::Create { id_sender: canvas_id_sender, size, - webrender_sender: webrender_api, - webrender_doc: self.webrender_document, antialias: self.enable_canvas_antialiasing, }) { return warn!("Create canvas paint thread failed ({})", e); diff --git a/components/gfx/font_cache_thread.rs b/components/gfx/font_cache_thread.rs index 48d4c9e2889..45eead2357d 100644 --- a/components/gfx/font_cache_thread.rs +++ b/components/gfx/font_cache_thread.rs @@ -12,6 +12,7 @@ use crate::platform::font_list::system_default_family; use crate::platform::font_list::SANS_SERIF_FONT_FAMILY; use crate::platform::font_template::FontTemplateData; use app_units::Au; +use gfx_traits::{FontData, WebrenderApi}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use net_traits::request::{Destination, RequestBuilder}; use net_traits::{fetch_async, CoreResourceThread, FetchResponseMsg}; @@ -135,9 +136,8 @@ struct FontCache { web_families: HashMap<LowercaseString, FontTemplates>, font_context: FontContextHandle, core_resource_thread: CoreResourceThread, - webrender_api: webrender_api::RenderApi, + webrender_api: Box<dyn WebrenderApi>, webrender_fonts: HashMap<Atom, webrender_api::FontKey>, - webrender_doc: webrender_api::DocumentId, font_instances: HashMap<(webrender_api::FontKey, Au), webrender_api::FontInstanceKey>, } @@ -181,19 +181,11 @@ impl FontCache { }, Command::GetFontInstance(font_key, size, result) => { let webrender_api = &self.webrender_api; - let doc = self.webrender_doc; - - let instance_key = - *self - .font_instances - .entry((font_key, size)) - .or_insert_with(|| { - let key = webrender_api.generate_font_instance_key(); - let mut txn = webrender_api::Transaction::new(); - txn.add_font_instance(key, font_key, size, None, None, Vec::new()); - webrender_api.send_transaction(doc, txn); - key - }); + + let instance_key = *self + .font_instances + .entry((font_key, size)) + .or_insert_with(|| webrender_api.add_font_instance(font_key, size)); let _ = result.send(instance_key); }, @@ -391,21 +383,17 @@ impl FontCache { fn get_font_template_info(&mut self, template: Arc<FontTemplateData>) -> FontTemplateInfo { let webrender_api = &self.webrender_api; - let doc = self.webrender_doc; let webrender_fonts = &mut self.webrender_fonts; let font_key = *webrender_fonts .entry(template.identifier.clone()) .or_insert_with(|| { - let font_key = webrender_api.generate_font_key(); - let mut txn = webrender_api::Transaction::new(); - match (template.bytes_if_in_memory(), template.native_font()) { - (Some(bytes), _) => txn.add_raw_font(font_key, bytes, 0), - (None, Some(native_font)) => txn.add_native_font(font_key, native_font), - (None, None) => txn.add_raw_font(font_key, template.bytes(), 0), - } - webrender_api.send_transaction(doc, txn); - font_key + let font = match (template.bytes_if_in_memory(), template.native_font()) { + (Some(bytes), _) => FontData::Raw(bytes), + (None, Some(native_font)) => FontData::Native(native_font), + (None, None) => FontData::Raw(template.bytes()), + }; + webrender_api.add_font(font) }); FontTemplateInfo { @@ -444,8 +432,7 @@ pub struct FontCacheThread { impl FontCacheThread { pub fn new( core_resource_thread: CoreResourceThread, - webrender_api: webrender_api::RenderApi, - webrender_doc: webrender_api::DocumentId, + webrender_api: Box<dyn WebrenderApi + Send>, ) -> FontCacheThread { let (chan, port) = ipc::channel().unwrap(); @@ -465,7 +452,6 @@ impl FontCacheThread { font_context: FontContextHandle::new(), core_resource_thread, webrender_api, - webrender_doc, webrender_fonts: HashMap::new(), font_instances: HashMap::new(), }; diff --git a/components/gfx_traits/Cargo.toml b/components/gfx_traits/Cargo.toml index ff83cc79406..5d7acdd1b61 100644 --- a/components/gfx_traits/Cargo.toml +++ b/components/gfx_traits/Cargo.toml @@ -11,7 +11,9 @@ name = "gfx_traits" path = "lib.rs" [dependencies] +app_units = "0.7" malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = "0.1" range = { path = "../range" } serde = "1.0" +webrender_api = { git = "https://github.com/servo/webrender" } diff --git a/components/gfx_traits/lib.rs b/components/gfx_traits/lib.rs index 165059c7996..447be6e5dc2 100644 --- a/components/gfx_traits/lib.rs +++ b/components/gfx_traits/lib.rs @@ -103,3 +103,17 @@ pub fn node_id_from_scroll_id(id: usize) -> Option<usize> { } None } + +pub enum FontData { + Raw(Vec<u8>), + Native(webrender_api::NativeFontHandle), +} + +pub trait WebrenderApi { + fn add_font_instance( + &self, + font_key: webrender_api::FontKey, + size: app_units::Au, + ) -> webrender_api::FontInstanceKey; + fn add_font(&self, data: FontData) -> webrender_api::FontKey; +} diff --git a/components/layout/display_list/webrender_helpers.rs b/components/layout/display_list/webrender_helpers.rs index dd187cf3704..9495b2ab166 100644 --- a/components/layout/display_list/webrender_helpers.rs +++ b/components/layout/display_list/webrender_helpers.rs @@ -234,6 +234,12 @@ impl DisplayItem { builder.push_iter(&stacking_context.filters); } + // TODO(jdm): WebRender now requires us to create stacking context items + // with the IS_BLEND_CONTAINER flag enabled if any children + // of the stacking context have a blend mode applied. + // This will require additional tracking during layout + // before we start collecting stacking contexts so that + // information will be available when we reach this point. let wr_item = PushStackingContextDisplayItem { origin: bounds.origin, spatial_id, @@ -243,9 +249,7 @@ impl DisplayItem { mix_blend_mode: stacking_context.mix_blend_mode, clip_id: None, raster_space: RasterSpace::Screen, - // TODO(pcwalton): Enable picture caching? - cache_tiles: false, - is_backdrop_root: false, + flags: Default::default(), }, }; diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs index fce6fb6caec..23357da4984 100644 --- a/components/layout_2020/display_list/stacking_context.rs +++ b/components/layout_2020/display_list/stacking_context.rs @@ -234,6 +234,12 @@ impl StackingContext { )); } + // TODO(jdm): WebRender now requires us to create stacking context items + // with the IS_BLEND_CONTAINER flag enabled if any children + // of the stacking context have a blend mode applied. + // This will require additional tracking during layout + // before we start collecting stacking contexts so that + // information will be available when we reach this point. builder.wr.push_stacking_context( LayoutPoint::zero(), // origin self.spatial_id, @@ -245,8 +251,7 @@ impl StackingContext { &vec![], // filter_datas &vec![], // filter_primitives wr::RasterSpace::Screen, - false, // cache_tiles, - false, // false + wr::StackingContextFlags::empty(), ); true diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 1cb332e3da9..96b2ad36f68 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -38,6 +38,7 @@ webrender_debugger = ["webrender/debugger"] xr-profile = ["canvas/xr-profile", "canvas_traits/xr-profile", "script/xr-profile", "webxr/profile"] [dependencies] +app_units = "0.7" background_hang_monitor = { path = "../background_hang_monitor" } bluetooth = { path = "../bluetooth" } bluetooth_traits = { path = "../bluetooth_traits" } @@ -53,6 +54,7 @@ embedder_traits = { path = "../embedder_traits" } env_logger = "0.7" euclid = "0.20" gfx = { path = "../gfx" } +gfx_traits = { path = "../gfx_traits" } gleam = "0.11" gstreamer = { version = "0.15", optional = true } ipc-channel = "0.14" diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 9278fb5c0de..25fdd2261b4 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -64,10 +64,12 @@ fn webdriver(_port: u16, _constellation: Sender<ConstellationMsg>) {} use bluetooth::BluetoothThreadFactory; use bluetooth_traits::BluetoothRequest; +use canvas::canvas_paint_thread::{self, CanvasPaintThread}; use canvas::{SurfaceProviders, WebGLComm, WebGlExecutor}; use canvas_traits::webgl::WebGLThreads; use compositing::compositor_thread::{ - CompositorProxy, CompositorReceiver, InitialCompositorState, Msg, + CompositorProxy, CompositorReceiver, InitialCompositorState, Msg, WebrenderCanvasMsg, + WebrenderFontMsg, WebrenderMsg, }; use compositing::windowing::{EmbedderMethods, WindowEvent, WindowMethods}; use compositing::{CompositingReason, ConstellationMsg, IOCompositor, ShutdownState}; @@ -115,7 +117,6 @@ use std::borrow::Cow; use std::cmp::max; use std::path::PathBuf; use std::rc::Rc; -use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use surfman::GLApi; use webrender::ShaderPrecacheFlags; @@ -454,7 +455,7 @@ where webrender_surfman.clone(), webrender_gl.clone(), &mut webrender, - webrender_api_sender.clone(), + webrender_api.create_sender(), webrender_document, &mut webxr_main_thread, &mut external_image_handlers, @@ -500,8 +501,6 @@ where device_pixel_ratio: Scale::new(device_pixel_ratio), }; - let pending_wr_frame = Arc::new(AtomicBool::new(false)); - // Create the constellation, which maintains the engine // pipelines, including the script and layout threads, as well // as the navigation context. @@ -515,14 +514,12 @@ where debugger_chan, devtools_chan, webrender_document, - webrender_api_sender, webxr_main_thread.registry(), player_context, webgl_threads, glplayer_threads, event_loop_waker, window_size, - pending_wr_frame.clone(), ); if cfg!(feature = "webdriver") { @@ -547,7 +544,6 @@ where webrender_surfman, webrender_gl, webxr_main_thread, - pending_wr_frame, }, opts.output_file.clone(), opts.is_running_problem_test, @@ -855,14 +851,12 @@ fn create_constellation( debugger_chan: Option<debugger::Sender>, devtools_chan: Option<Sender<devtools_traits::DevtoolsControlMsg>>, webrender_document: webrender_api::DocumentId, - webrender_api_sender: webrender_api::RenderApiSender, webxr_registry: webxr_api::Registry, player_context: WindowGLContext, webgl_threads: Option<WebGLThreads>, glplayer_threads: Option<GLPlayerThreads>, event_loop_waker: Option<Box<dyn EventLoopWaker>>, initial_window_size: WindowSizeData, - pending_wr_frame: Arc<AtomicBool>, ) -> Sender<ConstellationMsg> { // Global configuration options, parsed from the command line. let opts = opts::get(); @@ -879,14 +873,14 @@ fn create_constellation( config_dir, opts.certificate_path.clone(), ); + let font_cache_thread = FontCacheThread::new( public_resource_threads.sender(), - webrender_api_sender.create_api(), - webrender_document, + Box::new(FontCacheWR(compositor_proxy.clone())), ); let initial_state = InitialConstellationState { - compositor_proxy, + compositor_proxy: compositor_proxy.clone(), embedder_proxy, debugger_chan, devtools_chan, @@ -897,17 +891,16 @@ fn create_constellation( time_profiler_chan, mem_profiler_chan, webrender_document, - webrender_api_sender, webxr_registry, webgl_threads, glplayer_threads, player_context, event_loop_waker, - pending_wr_frame, user_agent, }; - let (canvas_chan, ipc_canvas_chan) = canvas::canvas_paint_thread::CanvasPaintThread::start(); + let (canvas_chan, ipc_canvas_chan) = + CanvasPaintThread::start(Box::new(CanvasWebrenderApi(compositor_proxy))); let constellation_chan = Constellation::< script_layout_interface::message::Msg, @@ -929,6 +922,50 @@ fn create_constellation( constellation_chan } +struct FontCacheWR(CompositorProxy); + +impl gfx_traits::WebrenderApi for FontCacheWR { + fn add_font_instance( + &self, + font_key: webrender_api::FontKey, + size: app_units::Au, + ) -> webrender_api::FontInstanceKey { + let (sender, receiver) = unbounded(); + let _ = self.0.send(Msg::Webrender(WebrenderMsg::Font( + WebrenderFontMsg::AddFontInstance(font_key, size, sender), + ))); + receiver.recv().unwrap() + } + fn add_font(&self, data: gfx_traits::FontData) -> webrender_api::FontKey { + let (sender, receiver) = unbounded(); + let _ = self.0.send(Msg::Webrender(WebrenderMsg::Font( + WebrenderFontMsg::AddFont(data, sender), + ))); + receiver.recv().unwrap() + } +} + +#[derive(Clone)] +struct CanvasWebrenderApi(CompositorProxy); + +impl canvas_paint_thread::WebrenderApi for CanvasWebrenderApi { + fn generate_key(&self) -> webrender_api::ImageKey { + let (sender, receiver) = unbounded(); + let _ = self.0.send(Msg::Webrender(WebrenderMsg::Canvas( + WebrenderCanvasMsg::GenerateKey(sender), + ))); + receiver.recv().unwrap() + } + fn update_images(&self, updates: Vec<canvas_paint_thread::ImageUpdate>) { + let _ = self.0.send(Msg::Webrender(WebrenderMsg::Canvas( + WebrenderCanvasMsg::UpdateImages(updates), + ))); + } + fn clone(&self) -> Box<dyn canvas_paint_thread::WebrenderApi> { + Box::new(<Self as Clone>::clone(self)) + } +} + // A logger that logs to two downstream loggers. // This should probably be in the log crate. struct BothLogger<Log1, Log2>(Log1, Log2); |