From a1df0aca3fb7f4888185da9f80ae50f359d97809 Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Mon, 27 Aug 2018 17:31:47 +0200 Subject: [PATCH 1/3] Add a low priority scene builder thread. The low priority scene builder is an additional thread that can be used to perform some work before forwarding the transaction to the "normal" scene builder thread. This lets us choose to rasterize very expensive content blobs without blocking the scene builder allowing updates of the UI disply list. Since all low priority scenes request get forwarded to the regular scene builder as well, any operation that affects both threads (such as flushes or shutting down) can simply be sent to the low priority thread. --- webrender/src/render_backend.rs | 15 ++++++---- webrender/src/renderer.rs | 25 +++++++++++++++- webrender/src/scene_builder.rs | 53 +++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 6363fdf75f..65147ab9da 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -241,7 +241,6 @@ impl Document { DocumentOps::nop() } - fn build_frame( &mut self, resource_cache: &mut ResourceCache, @@ -367,6 +366,7 @@ pub struct RenderBackend { payload_rx: Receiver, result_tx: Sender, scene_tx: Sender, + low_priority_scene_tx: Sender, scene_rx: Receiver, payload_buffer: Vec, @@ -393,6 +393,7 @@ impl RenderBackend { payload_rx: Receiver, result_tx: Sender, scene_tx: Sender, + low_priority_scene_tx: Sender, scene_rx: Receiver, default_device_pixel_ratio: f32, resource_cache: ResourceCache, @@ -410,6 +411,7 @@ impl RenderBackend { payload_rx, result_tx, scene_tx, + low_priority_scene_tx, scene_rx, payload_buffer: Vec::new(), default_device_pixel_ratio, @@ -628,7 +630,7 @@ impl RenderBackend { }; } - let _ = self.scene_tx.send(SceneBuilderRequest::Stop); + let _ = self.low_priority_scene_tx.send(SceneBuilderRequest::Stop); // Ensure we read everything the scene builder is sending us from // inflight messages, otherwise the scene builder might panic. while let Ok(msg) = self.scene_rx.recv() { @@ -665,7 +667,7 @@ impl RenderBackend { self.scene_tx.send(SceneBuilderRequest::WakeUp).unwrap(); } ApiMsg::FlushSceneBuilder(tx) => { - self.scene_tx.send(SceneBuilderRequest::Flush(tx)).unwrap(); + self.low_priority_scene_tx.send(SceneBuilderRequest::Flush(tx)).unwrap(); } ApiMsg::UpdateResources(mut updates) => { self.resource_cache.pre_scene_building_update( @@ -709,7 +711,7 @@ impl RenderBackend { } ApiMsg::DeleteDocument(document_id) => { self.documents.remove(&document_id); - self.scene_tx.send( + self.low_priority_scene_tx.send( SceneBuilderRequest::DeleteDocument(document_id) ).unwrap(); } @@ -754,7 +756,7 @@ impl RenderBackend { self.frame_config .dual_source_blending_is_enabled = enable; - self.scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig( + self.low_priority_scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig( self.frame_config.clone() )).unwrap(); @@ -835,6 +837,7 @@ impl RenderBackend { blob_requests: Vec::new(), resource_updates: transaction_msg.resource_updates, frame_ops: transaction_msg.frame_ops, + rasterized_blobs: Vec::new(), set_root_pipeline: None, build_frame: transaction_msg.generate_frame, render_frame: transaction_msg.generate_frame, @@ -1345,7 +1348,7 @@ impl RenderBackend { } if !scenes_to_build.is_empty() { - self.scene_tx.send( + self.low_priority_scene_tx.send( SceneBuilderRequest::LoadScenes(scenes_to_build) ).unwrap(); } diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index 77b1b27aa8..c295526db9 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -44,7 +44,7 @@ use device::query::GpuProfiler; use rayon::{ThreadPool, ThreadPoolBuilder}; use record::ApiRecordingReceiver; use render_backend::RenderBackend; -use scene_builder::SceneBuilder; +use scene_builder::{SceneBuilder, LowPrioritySceneBuilder}; use shade::Shaders; use render_task::{RenderTask, RenderTaskKind, RenderTaskTree}; use resource_cache::ResourceCache; @@ -1714,9 +1714,11 @@ impl Renderer { let blob_image_handler = options.blob_image_handler.take(); let thread_listener_for_render_backend = thread_listener.clone(); let thread_listener_for_scene_builder = thread_listener.clone(); + let thread_listener_for_lp_scene_builder = thread_listener.clone(); let scene_builder_hooks = options.scene_builder_hooks; let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0)); let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0)); + let lp_scene_thread_name = format!("WRSceneBuilderLP#{}", options.renderer_id.unwrap_or(0)); let glyph_rasterizer = GlyphRasterizer::new(workers)?; let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new( @@ -1737,6 +1739,26 @@ impl Renderer { } })?; + let (low_priority_scene_tx, low_priority_scene_rx) = channel(); + let lp_builder = LowPrioritySceneBuilder { + rx: low_priority_scene_rx, + tx: scene_tx.clone(), + }; + + thread::Builder::new().name(lp_scene_thread_name.clone()).spawn(move || { + register_thread_with_profiler(lp_scene_thread_name.clone()); + if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { + thread_listener.thread_started(&lp_scene_thread_name); + } + + let mut scene_builder = lp_builder; + scene_builder.run(); + + if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { + thread_listener.thread_stopped(&lp_scene_thread_name); + } + })?; + thread::Builder::new().name(rb_thread_name.clone()).spawn(move || { register_thread_with_profiler(rb_thread_name.clone()); if let Some(ref thread_listener) = *thread_listener_for_render_backend { @@ -1755,6 +1777,7 @@ impl Renderer { payload_rx_for_backend, result_tx, scene_tx, + low_priority_scene_tx, scene_rx, device_pixel_ratio, resource_cache, diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index 4ecec13963..830e9cfac8 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -27,6 +27,7 @@ pub struct Transaction { pub request_scene_build: Option, pub blob_requests: Vec, pub blob_rasterizer: Option>, + pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, pub resource_updates: Vec, pub frame_ops: Vec, pub set_root_pipeline: Option, @@ -315,18 +316,19 @@ impl SceneBuilder { } let blob_requests = replace(&mut txn.blob_requests, Vec::new()); - let rasterized_blobs = txn.blob_rasterizer.as_mut().map_or( + let mut rasterized_blobs = txn.blob_rasterizer.as_mut().map_or( Vec::new(), |rasterizer| rasterizer.rasterize(&blob_requests), ); + rasterized_blobs.append(&mut txn.rasterized_blobs); Box::new(BuiltTransaction { document_id: txn.document_id, build_frame: txn.build_frame || built_scene.is_some(), render_frame: txn.render_frame, built_scene, + rasterized_blobs, resource_updates: replace(&mut txn.resource_updates, Vec::new()), - rasterized_blobs: rasterized_blobs, blob_rasterizer: replace(&mut txn.blob_rasterizer, None), frame_ops: replace(&mut txn.frame_ops, Vec::new()), removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()), @@ -383,3 +385,50 @@ impl SceneBuilder { } } } + +/// A scene builder thread which executes expensive operations such as blob rasterization +/// with a lower priority than the normal scene builder thread. +/// +/// After rasterizing blobs, the secene building request is forwarded to the normal scene +/// builder where the FrameBuilder is generated. +pub struct LowPrioritySceneBuilder { + pub rx: Receiver, + pub tx: Sender, +} + +impl LowPrioritySceneBuilder { + pub fn run(&mut self) { + loop { + match self.rx.recv() { + Ok(SceneBuilderRequest::Transaction(txn)) => { + let txn = self.process_transaction(txn); + self.tx.send(SceneBuilderRequest::Transaction(txn)).unwrap(); + } + Ok(SceneBuilderRequest::DeleteDocument(document_id)) => { + self.tx.send(SceneBuilderRequest::DeleteDocument(document_id)).unwrap(); + } + Ok(SceneBuilderRequest::Stop) => { + self.tx.send(SceneBuilderRequest::Stop).unwrap(); + break; + } + Ok(other) => { + self.tx.send(other).unwrap(); + } + Err(_) => { + break; + } + } + } + } + + fn process_transaction(&mut self, mut txn: Box) -> Box { + let blob_requests = replace(&mut txn.blob_requests, Vec::new()); + let mut more_rasterized_blobs = txn.blob_rasterizer.as_mut().map_or( + Vec::new(), + |rasterizer| rasterizer.rasterize(&blob_requests), + ); + txn.rasterized_blobs.append(&mut more_rasterized_blobs); + + txn + } +} From fed564216e6181b4a7cff1ab02e07187551b39ad Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Tue, 28 Aug 2018 11:20:15 +0200 Subject: [PATCH 2/3] Expose low priority transactions in the API. --- webrender/src/render_backend.rs | 10 ++++++++-- webrender_api/src/api.rs | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 65147ab9da..7444adb4ba 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -599,7 +599,7 @@ impl RenderBackend { if txn.build_frame || !txn.resource_updates.is_empty() || !txn.frame_ops.is_empty() { self.update_document( - txn.document_id, + txn.document_id, replace(&mut txn.resource_updates, Vec::new()), replace(&mut txn.frame_ops, Vec::new()), txn.build_frame, @@ -895,7 +895,13 @@ impl RenderBackend { }); } - self.scene_tx.send(SceneBuilderRequest::Transaction(txn)).unwrap(); + let tx = if transaction_msg.low_priority { + &self.low_priority_scene_tx + } else { + &self.scene_tx + }; + + tx.send(SceneBuilderRequest::Transaction(txn)).unwrap(); } fn update_document( diff --git a/webrender_api/src/api.rs b/webrender_api/src/api.rs index acac37f62f..8cd4b160e2 100644 --- a/webrender_api/src/api.rs +++ b/webrender_api/src/api.rs @@ -56,6 +56,8 @@ pub struct Transaction { use_scene_builder_thread: bool, generate_frame: bool, + + low_priority: bool, } impl Transaction { @@ -67,6 +69,7 @@ impl Transaction { payloads: Vec::new(), use_scene_builder_thread: true, generate_frame: false, + low_priority: false, } } @@ -256,6 +259,7 @@ impl Transaction { resource_updates: self.resource_updates, use_scene_builder_thread: self.use_scene_builder_thread, generate_frame: self.generate_frame, + low_priority: self.low_priority, }, self.payloads, ) @@ -337,6 +341,18 @@ impl Transaction { self.resource_updates.push(ResourceUpdate::DeleteFontInstance(key)); } + // A hint that this transaction can be processed at a lower priority. High- + // priority transactions can jump ahead of regular-priority transactions, + // but both high- and regular-priority transactions are processed in order + // relative to other transactions of the same priority. + pub fn set_low_priority(&mut self, low_priority: bool) { + self.low_priority = low_priority; + } + + pub fn is_low_priority(&self) -> bool { + self.low_priority + } + pub fn merge(&mut self, mut other: Vec) { self.resource_updates.append(&mut other); } @@ -354,6 +370,7 @@ pub struct TransactionMsg { pub resource_updates: Vec, pub generate_frame: bool, pub use_scene_builder_thread: bool, + pub low_priority: bool, } impl TransactionMsg { @@ -372,6 +389,7 @@ impl TransactionMsg { resource_updates: Vec::new(), generate_frame: false, use_scene_builder_thread: false, + low_priority: false, } } @@ -382,6 +400,7 @@ impl TransactionMsg { resource_updates: Vec::new(), generate_frame: false, use_scene_builder_thread: false, + low_priority: false, } } } From 1f036039cfdde7f856990254859eaabdda4a02a4 Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Tue, 28 Aug 2018 11:47:42 +0200 Subject: [PATCH 3/3] Make low priority transaction support optional. This avoids the cost of an extra thread per renderer when we don't need it. If support is disabled for a given renderer, low and high preirity transactions go to the same queue. --- webrender/src/renderer.rs | 40 +++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index c295526db9..77e67f98ce 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -1739,25 +1739,31 @@ impl Renderer { } })?; - let (low_priority_scene_tx, low_priority_scene_rx) = channel(); - let lp_builder = LowPrioritySceneBuilder { - rx: low_priority_scene_rx, - tx: scene_tx.clone(), - }; + let low_priority_scene_tx = if options.support_low_priority_transactions { + let (low_priority_scene_tx, low_priority_scene_rx) = channel(); + let lp_builder = LowPrioritySceneBuilder { + rx: low_priority_scene_rx, + tx: scene_tx.clone(), + }; - thread::Builder::new().name(lp_scene_thread_name.clone()).spawn(move || { - register_thread_with_profiler(lp_scene_thread_name.clone()); - if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { - thread_listener.thread_started(&lp_scene_thread_name); - } + thread::Builder::new().name(lp_scene_thread_name.clone()).spawn(move || { + register_thread_with_profiler(lp_scene_thread_name.clone()); + if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { + thread_listener.thread_started(&lp_scene_thread_name); + } - let mut scene_builder = lp_builder; - scene_builder.run(); + let mut scene_builder = lp_builder; + scene_builder.run(); - if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { - thread_listener.thread_stopped(&lp_scene_thread_name); - } - })?; + if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { + thread_listener.thread_stopped(&lp_scene_thread_name); + } + })?; + + low_priority_scene_tx + } else { + scene_tx.clone() + }; thread::Builder::new().name(rb_thread_name.clone()).spawn(move || { register_thread_with_profiler(rb_thread_name.clone()); @@ -4245,6 +4251,7 @@ pub struct RendererOptions { pub scene_builder_hooks: Option>, pub sampler: Option>, pub chase_primitive: ChasePrimitive, + pub support_low_priority_transactions: bool, } impl Default for RendererOptions { @@ -4279,6 +4286,7 @@ impl Default for RendererOptions { scene_builder_hooks: None, sampler: None, chase_primitive: ChasePrimitive::Nothing, + support_low_priority_transactions: false, } } }