summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/scene_builder_thread.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/scene_builder_thread.rs')
-rw-r--r--gfx/wr/webrender/src/scene_builder_thread.rs840
1 files changed, 840 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/scene_builder_thread.rs b/gfx/wr/webrender/src/scene_builder_thread.rs
new file mode 100644
index 0000000000..5550a48690
--- /dev/null
+++ b/gfx/wr/webrender/src/scene_builder_thread.rs
@@ -0,0 +1,840 @@
+/* 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 api::{AsyncBlobImageRasterizer, BlobImageResult};
+use api::{DocumentId, PipelineId, ExternalEvent, BlobImageRequest};
+use api::{NotificationRequest, Checkpoint, IdNamespace, QualitySettings};
+use api::{PrimitiveKeyKind, SharedFontInstanceMap};
+use api::{GlyphDimensionRequest, GlyphIndexRequest};
+use api::channel::{unbounded_channel, single_msg_channel, Receiver, Sender};
+use api::units::*;
+use crate::render_api::{ApiMsg, FrameMsg, SceneMsg, ResourceUpdate, TransactionMsg, MemoryReport};
+#[cfg(feature = "capture")]
+use crate::capture::CaptureConfig;
+use crate::frame_builder::FrameBuilderConfig;
+use crate::scene_building::SceneBuilder;
+use crate::clip::ClipIntern;
+use crate::filterdata::FilterDataIntern;
+use crate::intern::{Internable, Interner, UpdateList};
+use crate::internal_types::{FastHashMap, FastHashSet};
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use crate::prim_store::backdrop::Backdrop;
+use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
+use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
+use crate::prim_store::image::{Image, YuvImage};
+use crate::prim_store::line_dec::LineDecoration;
+use crate::prim_store::picture::Picture;
+use crate::prim_store::text_run::TextRun;
+use crate::profiler::{self, TransactionProfile};
+use crate::render_backend::SceneView;
+use crate::renderer::{PipelineInfo, SceneBuilderHooks};
+use crate::scene::{Scene, BuiltScene, SceneStats};
+use std::iter;
+use std::mem::replace;
+use time::precise_time_ns;
+use crate::util::drain_filter;
+use std::thread;
+use std::time::Duration;
+
+#[cfg(feature = "debugger")]
+use crate::debug_server;
+#[cfg(feature = "debugger")]
+use api::{BuiltDisplayListIter, DisplayItem};
+
+fn rasterize_blobs(txn: &mut TransactionMsg, is_low_priority: bool) {
+ profile_scope!("rasterize_blobs");
+
+ if let Some(ref mut rasterizer) = txn.blob_rasterizer {
+ let mut rasterized_blobs = rasterizer.rasterize(&txn.blob_requests, is_low_priority);
+ // try using the existing allocation if our current list is empty
+ if txn.rasterized_blobs.is_empty() {
+ txn.rasterized_blobs = rasterized_blobs;
+ } else {
+ txn.rasterized_blobs.append(&mut rasterized_blobs);
+ }
+ }
+}
+
+/// Represent the remaining work associated to a transaction after the scene building
+/// phase as well as the result of scene building itself if applicable.
+pub struct BuiltTransaction {
+ pub document_id: DocumentId,
+ pub built_scene: Option<BuiltScene>,
+ pub view: SceneView,
+ pub resource_updates: Vec<ResourceUpdate>,
+ pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
+ pub blob_rasterizer: Option<Box<dyn AsyncBlobImageRasterizer>>,
+ pub frame_ops: Vec<FrameMsg>,
+ pub removed_pipelines: Vec<(PipelineId, DocumentId)>,
+ pub notifications: Vec<NotificationRequest>,
+ pub interner_updates: Option<InternerUpdates>,
+ pub render_frame: bool,
+ pub invalidate_rendered_frame: bool,
+ pub discard_frame_state_for_pipelines: Vec<PipelineId>,
+ pub profile: TransactionProfile,
+}
+
+#[cfg(feature = "replay")]
+pub struct LoadScene {
+ pub document_id: DocumentId,
+ pub scene: Scene,
+ pub font_instances: SharedFontInstanceMap,
+ pub view: SceneView,
+ pub config: FrameBuilderConfig,
+ pub build_frame: bool,
+ pub interners: Interners,
+}
+
+/// Message to the scene builder thread.
+pub enum SceneBuilderRequest {
+ Transactions(Vec<Box<TransactionMsg>>),
+ AddDocument(DocumentId, DeviceIntSize),
+ DeleteDocument(DocumentId),
+ GetGlyphDimensions(GlyphDimensionRequest),
+ GetGlyphIndices(GlyphIndexRequest),
+ ClearNamespace(IdNamespace),
+ SimulateLongSceneBuild(u32),
+ SimulateLongLowPrioritySceneBuild(u32),
+ ExternalEvent(ExternalEvent),
+ WakeUp,
+ ShutDown(Option<Sender<()>>),
+ Flush(Sender<()>),
+ SetFrameBuilderConfig(FrameBuilderConfig),
+ ReportMemory(Box<MemoryReport>, Sender<Box<MemoryReport>>),
+ #[cfg(feature = "capture")]
+ SaveScene(CaptureConfig),
+ #[cfg(feature = "replay")]
+ LoadScenes(Vec<LoadScene>),
+ #[cfg(feature = "capture")]
+ StartCaptureSequence(CaptureConfig),
+ #[cfg(feature = "capture")]
+ StopCaptureSequence,
+ DocumentsForDebugger
+}
+
+// Message from scene builder to render backend.
+pub enum SceneBuilderResult {
+ Transactions(Vec<Box<BuiltTransaction>>, Option<Sender<SceneSwapResult>>),
+ #[cfg(feature = "capture")]
+ CapturedTransactions(Vec<Box<BuiltTransaction>>, CaptureConfig, Option<Sender<SceneSwapResult>>),
+ ExternalEvent(ExternalEvent),
+ FlushComplete(Sender<()>),
+ DeleteDocument(DocumentId),
+ ClearNamespace(IdNamespace),
+ GetGlyphDimensions(GlyphDimensionRequest),
+ GetGlyphIndices(GlyphIndexRequest),
+ ShutDown(Option<Sender<()>>),
+ DocumentsForDebugger(String)
+}
+
+// Message from render backend to scene builder to indicate the
+// scene swap was completed. We need a separate channel for this
+// so that they don't get mixed with SceneBuilderRequest messages.
+pub enum SceneSwapResult {
+ Complete(Sender<()>),
+ Aborted,
+}
+
+macro_rules! declare_interners {
+ ( $( $name:ident : $ty:ident, )+ ) => {
+ /// This struct contains all items that can be shared between
+ /// display lists. We want to intern and share the same clips,
+ /// primitives and other things between display lists so that:
+ /// - GPU cache handles remain valid, reducing GPU cache updates.
+ /// - Comparison of primitives and pictures between two
+ /// display lists is (a) fast (b) done during scene building.
+ #[cfg_attr(feature = "capture", derive(Serialize))]
+ #[cfg_attr(feature = "replay", derive(Deserialize))]
+ #[derive(Default)]
+ pub struct Interners {
+ $(
+ pub $name: Interner<$ty>,
+ )+
+ }
+
+ $(
+ impl AsMut<Interner<$ty>> for Interners {
+ fn as_mut(&mut self) -> &mut Interner<$ty> {
+ &mut self.$name
+ }
+ }
+ )+
+
+ pub struct InternerUpdates {
+ $(
+ pub $name: UpdateList<<$ty as Internable>::Key>,
+ )+
+ }
+
+ impl Interners {
+ /// Reports CPU heap memory used by the interners.
+ fn report_memory(
+ &self,
+ ops: &mut MallocSizeOfOps,
+ r: &mut MemoryReport,
+ ) {
+ $(
+ r.interning.interners.$name += self.$name.size_of(ops);
+ )+
+ }
+
+ fn end_frame_and_get_pending_updates(&mut self) -> InternerUpdates {
+ InternerUpdates {
+ $(
+ $name: self.$name.end_frame_and_get_pending_updates(),
+ )+
+ }
+ }
+ }
+ }
+}
+
+crate::enumerate_interners!(declare_interners);
+
+// A document in the scene builder contains the current scene,
+// as well as a persistent clip interner. This allows clips
+// to be de-duplicated, and persisted in the GPU cache between
+// display lists.
+struct Document {
+ scene: Scene,
+ interners: Interners,
+ stats: SceneStats,
+ view: SceneView,
+}
+
+impl Document {
+ fn new(device_rect: DeviceIntRect, device_pixel_ratio: f32) -> Self {
+ Document {
+ scene: Scene::new(),
+ interners: Interners::default(),
+ stats: SceneStats::empty(),
+ view: SceneView {
+ device_rect,
+ device_pixel_ratio,
+ page_zoom_factor: 1.0,
+ quality_settings: QualitySettings::default(),
+ },
+ }
+ }
+}
+
+pub struct SceneBuilderThread {
+ documents: FastHashMap<DocumentId, Document>,
+ rx: Receiver<SceneBuilderRequest>,
+ tx: Sender<ApiMsg>,
+ config: FrameBuilderConfig,
+ default_device_pixel_ratio: f32,
+ font_instances: SharedFontInstanceMap,
+ size_of_ops: Option<MallocSizeOfOps>,
+ hooks: Option<Box<dyn SceneBuilderHooks + Send>>,
+ simulate_slow_ms: u32,
+ removed_pipelines: FastHashSet<PipelineId>,
+ #[cfg(feature = "capture")]
+ capture_config: Option<CaptureConfig>,
+}
+
+pub struct SceneBuilderThreadChannels {
+ rx: Receiver<SceneBuilderRequest>,
+ tx: Sender<ApiMsg>,
+}
+
+impl SceneBuilderThreadChannels {
+ pub fn new(
+ tx: Sender<ApiMsg>
+ ) -> (Self, Sender<SceneBuilderRequest>) {
+ let (in_tx, in_rx) = unbounded_channel();
+ (
+ Self {
+ rx: in_rx,
+ tx,
+ },
+ in_tx,
+ )
+ }
+}
+
+impl SceneBuilderThread {
+ pub fn new(
+ config: FrameBuilderConfig,
+ default_device_pixel_ratio: f32,
+ font_instances: SharedFontInstanceMap,
+ size_of_ops: Option<MallocSizeOfOps>,
+ hooks: Option<Box<dyn SceneBuilderHooks + Send>>,
+ channels: SceneBuilderThreadChannels,
+ ) -> Self {
+ let SceneBuilderThreadChannels { rx, tx } = channels;
+
+ Self {
+ documents: Default::default(),
+ rx,
+ tx,
+ config,
+ default_device_pixel_ratio,
+ font_instances,
+ size_of_ops,
+ hooks,
+ simulate_slow_ms: 0,
+ removed_pipelines: FastHashSet::default(),
+ #[cfg(feature = "capture")]
+ capture_config: None,
+ }
+ }
+
+ /// Send a message to the render backend thread.
+ ///
+ /// We first put something in the result queue and then send a wake-up
+ /// message to the api queue that the render backend is blocking on.
+ pub fn send(&self, msg: SceneBuilderResult) {
+ self.tx.send(ApiMsg::SceneBuilderResult(msg)).unwrap();
+ }
+
+ /// The scene builder thread's event loop.
+ pub fn run(&mut self) {
+ if let Some(ref hooks) = self.hooks {
+ hooks.register();
+ }
+
+ loop {
+ tracy_begin_frame!("scene_builder_thread");
+
+ match self.rx.recv() {
+ Ok(SceneBuilderRequest::WakeUp) => {}
+ Ok(SceneBuilderRequest::Flush(tx)) => {
+ self.send(SceneBuilderResult::FlushComplete(tx));
+ }
+ Ok(SceneBuilderRequest::Transactions(mut txns)) => {
+ let built_txns : Vec<Box<BuiltTransaction>> = txns.iter_mut()
+ .map(|txn| self.process_transaction(txn))
+ .collect();
+ #[cfg(feature = "capture")]
+ match built_txns.iter().any(|txn| txn.built_scene.is_some()) {
+ true => self.save_capture_sequence(),
+ _ => {},
+ }
+ self.forward_built_transactions(built_txns);
+ }
+ Ok(SceneBuilderRequest::AddDocument(document_id, initial_size)) => {
+ let old = self.documents.insert(document_id, Document::new(
+ initial_size.into(),
+ self.default_device_pixel_ratio,
+ ));
+ debug_assert!(old.is_none());
+ }
+ Ok(SceneBuilderRequest::DeleteDocument(document_id)) => {
+ self.documents.remove(&document_id);
+ self.send(SceneBuilderResult::DeleteDocument(document_id));
+ }
+ Ok(SceneBuilderRequest::ClearNamespace(id)) => {
+ self.documents.retain(|doc_id, _doc| doc_id.namespace_id != id);
+ self.send(SceneBuilderResult::ClearNamespace(id));
+ }
+ Ok(SceneBuilderRequest::ExternalEvent(evt)) => {
+ self.send(SceneBuilderResult::ExternalEvent(evt));
+ }
+ Ok(SceneBuilderRequest::GetGlyphDimensions(request)) => {
+ self.send(SceneBuilderResult::GetGlyphDimensions(request));
+ }
+ Ok(SceneBuilderRequest::GetGlyphIndices(request)) => {
+ self.send(SceneBuilderResult::GetGlyphIndices(request));
+ }
+ Ok(SceneBuilderRequest::ShutDown(sync)) => {
+ self.send(SceneBuilderResult::ShutDown(sync));
+ break;
+ }
+ Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => {
+ self.simulate_slow_ms = time_ms
+ }
+ Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(_)) => {}
+ Ok(SceneBuilderRequest::ReportMemory(mut report, tx)) => {
+ (*report) += self.report_memory();
+ tx.send(report).unwrap();
+ }
+ Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => {
+ self.config = cfg;
+ }
+ #[cfg(feature = "replay")]
+ Ok(SceneBuilderRequest::LoadScenes(msg)) => {
+ self.load_scenes(msg);
+ }
+ #[cfg(feature = "capture")]
+ Ok(SceneBuilderRequest::SaveScene(config)) => {
+ self.save_scene(config);
+ }
+ #[cfg(feature = "capture")]
+ Ok(SceneBuilderRequest::StartCaptureSequence(config)) => {
+ self.start_capture_sequence(config);
+ }
+ #[cfg(feature = "capture")]
+ Ok(SceneBuilderRequest::StopCaptureSequence) => {
+ // FIXME(aosmond): clear config for frames and resource cache without scene
+ // rebuild?
+ self.capture_config = None;
+ }
+ Ok(SceneBuilderRequest::DocumentsForDebugger) => {
+ let json = self.get_docs_for_debugger();
+ self.send(SceneBuilderResult::DocumentsForDebugger(json));
+ }
+ Err(_) => {
+ break;
+ }
+ }
+
+ if let Some(ref hooks) = self.hooks {
+ hooks.poke();
+ }
+
+ tracy_end_frame!("scene_builder_thread");
+ }
+
+ if let Some(ref hooks) = self.hooks {
+ hooks.deregister();
+ }
+ }
+
+ #[cfg(feature = "capture")]
+ fn save_scene(&mut self, config: CaptureConfig) {
+ for (id, doc) in &self.documents {
+ let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id);
+ config.serialize_for_scene(&doc.interners, interners_name);
+
+ use crate::render_api::CaptureBits;
+ if config.bits.contains(CaptureBits::SCENE) {
+ let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
+ config.serialize_for_scene(&doc.scene, file_name);
+ }
+ }
+ }
+
+ #[cfg(feature = "replay")]
+ fn load_scenes(&mut self, scenes: Vec<LoadScene>) {
+ for mut item in scenes {
+ self.config = item.config;
+
+ let mut built_scene = None;
+ let mut interner_updates = None;
+
+ if item.scene.has_root_pipeline() {
+ built_scene = Some(SceneBuilder::build(
+ &item.scene,
+ item.font_instances,
+ &item.view,
+ &self.config,
+ &mut item.interners,
+ &SceneStats::empty(),
+ ));
+
+ interner_updates = Some(
+ item.interners.end_frame_and_get_pending_updates()
+ );
+ }
+
+ self.documents.insert(
+ item.document_id,
+ Document {
+ scene: item.scene,
+ interners: item.interners,
+ stats: SceneStats::empty(),
+ view: item.view.clone(),
+ },
+ );
+
+ let txns = vec![Box::new(BuiltTransaction {
+ document_id: item.document_id,
+ render_frame: item.build_frame,
+ invalidate_rendered_frame: false,
+ built_scene,
+ view: item.view,
+ resource_updates: Vec::new(),
+ rasterized_blobs: Vec::new(),
+ blob_rasterizer: None,
+ frame_ops: Vec::new(),
+ removed_pipelines: Vec::new(),
+ discard_frame_state_for_pipelines: Vec::new(),
+ notifications: Vec::new(),
+ interner_updates,
+ profile: TransactionProfile::new(),
+ })];
+
+ self.forward_built_transactions(txns);
+ }
+ }
+
+ #[cfg(feature = "capture")]
+ fn save_capture_sequence(
+ &mut self,
+ ) {
+ if let Some(ref mut config) = self.capture_config {
+ config.prepare_scene();
+ for (id, doc) in &self.documents {
+ let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id);
+ config.serialize_for_scene(&doc.interners, interners_name);
+
+ use crate::render_api::CaptureBits;
+ if config.bits.contains(CaptureBits::SCENE) {
+ let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
+ config.serialize_for_scene(&doc.scene, file_name);
+ }
+ }
+ }
+ }
+
+ #[cfg(feature = "capture")]
+ fn start_capture_sequence(
+ &mut self,
+ config: CaptureConfig,
+ ) {
+ self.capture_config = Some(config);
+ self.save_capture_sequence();
+ }
+
+ #[cfg(feature = "debugger")]
+ fn traverse_items<'a>(
+ &self,
+ traversal: &mut BuiltDisplayListIter<'a>,
+ node: &mut debug_server::TreeNode,
+ ) {
+ loop {
+ let subtraversal = {
+ let item = match traversal.next() {
+ Some(item) => item,
+ None => break,
+ };
+
+ match *item.item() {
+ display_item @ DisplayItem::PushStackingContext(..) => {
+ let mut subtraversal = item.sub_iter();
+ let mut child_node =
+ debug_server::TreeNode::new(&display_item.debug_name().to_string());
+ self.traverse_items(&mut subtraversal, &mut child_node);
+ node.add_child(child_node);
+ Some(subtraversal)
+ }
+ DisplayItem::PopStackingContext => {
+ return;
+ }
+ display_item => {
+ node.add_item(&display_item.debug_name().to_string());
+ None
+ }
+ }
+ };
+
+ // If flatten_item created a sub-traversal, we need `traversal` to have the
+ // same state as the completed subtraversal, so we reinitialize it here.
+ if let Some(subtraversal) = subtraversal {
+ *traversal = subtraversal;
+ }
+ }
+ }
+
+ #[cfg(not(feature = "debugger"))]
+ fn get_docs_for_debugger(&self) -> String {
+ String::new()
+ }
+
+ #[cfg(feature = "debugger")]
+ fn get_docs_for_debugger(&self) -> String {
+ let mut docs = debug_server::DocumentList::new();
+
+ for (_, doc) in &self.documents {
+ let mut debug_doc = debug_server::TreeNode::new("document");
+
+ for (_, pipeline) in &doc.scene.pipelines {
+ let mut debug_dl = debug_server::TreeNode::new("display-list");
+ self.traverse_items(&mut pipeline.display_list.iter(), &mut debug_dl);
+ debug_doc.add_child(debug_dl);
+ }
+
+ docs.add(debug_doc);
+ }
+
+ serde_json::to_string(&docs).unwrap()
+ }
+
+ /// Do the bulk of the work of the scene builder thread.
+ fn process_transaction(&mut self, txn: &mut TransactionMsg) -> Box<BuiltTransaction> {
+ profile_scope!("process_transaction");
+
+ if let Some(ref hooks) = self.hooks {
+ hooks.pre_scene_build();
+ }
+
+ let doc = self.documents.get_mut(&txn.document_id).unwrap();
+ let scene = &mut doc.scene;
+
+ let mut profile = txn.profile.take();
+
+ profile.start_time(profiler::SCENE_BUILD_TIME);
+
+ let mut discard_frame_state_for_pipelines = Vec::new();
+ let mut removed_pipelines = Vec::new();
+ let mut rebuild_scene = false;
+ for message in txn.scene_ops.drain(..) {
+ match message {
+ SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
+ scene.update_epoch(pipeline_id, epoch);
+ }
+ SceneMsg::SetPageZoom(factor) => {
+ doc.view.page_zoom_factor = factor.get();
+ }
+ SceneMsg::SetQualitySettings { settings } => {
+ doc.view.quality_settings = settings;
+ }
+ SceneMsg::SetDocumentView { device_rect, device_pixel_ratio } => {
+ doc.view.device_rect = device_rect;
+ doc.view.device_pixel_ratio = device_pixel_ratio;
+ }
+ SceneMsg::SetDisplayList {
+ epoch,
+ pipeline_id,
+ background,
+ viewport_size,
+ display_list,
+ preserve_frame_state,
+ } => {
+ let (builder_start_time_ns, builder_end_time_ns, send_time_ns) =
+ display_list.times();
+
+ let content_send_time = profiler::ns_to_ms(precise_time_ns() - send_time_ns);
+ let dl_build_time = profiler::ns_to_ms(builder_end_time_ns - builder_start_time_ns);
+ profile.set(profiler::CONTENT_SEND_TIME, content_send_time);
+ profile.set(profiler::DISPLAY_LIST_BUILD_TIME, dl_build_time);
+ profile.set(profiler::DISPLAY_LIST_MEM, profiler::bytes_to_mb(display_list.data().len()));
+
+ if self.removed_pipelines.contains(&pipeline_id) {
+ continue;
+ }
+
+ // Note: We could further reduce the amount of unnecessary scene
+ // building by keeping track of which pipelines are used by the
+ // scene (bug 1490751).
+ rebuild_scene = true;
+
+ scene.set_display_list(
+ pipeline_id,
+ epoch,
+ display_list,
+ background,
+ viewport_size,
+ );
+
+ if !preserve_frame_state {
+ discard_frame_state_for_pipelines.push(pipeline_id);
+ }
+ }
+ SceneMsg::SetRootPipeline(pipeline_id) => {
+ if scene.root_pipeline_id != Some(pipeline_id) {
+ rebuild_scene = true;
+ scene.set_root_pipeline_id(pipeline_id);
+ }
+ }
+ SceneMsg::RemovePipeline(pipeline_id) => {
+ scene.remove_pipeline(pipeline_id);
+ self.removed_pipelines.insert(pipeline_id);
+ removed_pipelines.push((pipeline_id, txn.document_id));
+ }
+ }
+ }
+
+ self.removed_pipelines.clear();
+
+ let mut built_scene = None;
+ let mut interner_updates = None;
+ if scene.has_root_pipeline() && rebuild_scene {
+
+ let built = SceneBuilder::build(
+ &scene,
+ self.font_instances.clone(),
+ &doc.view,
+ &self.config,
+ &mut doc.interners,
+ &doc.stats,
+ );
+
+ // Update the allocation stats for next scene
+ doc.stats = built.get_stats();
+
+ // Retrieve the list of updates from the clip interner.
+ interner_updates = Some(
+ doc.interners.end_frame_and_get_pending_updates()
+ );
+
+ built_scene = Some(built);
+ }
+
+ profile.end_time(profiler::SCENE_BUILD_TIME);
+
+
+ if !txn.blob_requests.is_empty() {
+ profile.start_time(profiler::BLOB_RASTERIZATION_TIME);
+
+ let is_low_priority = false;
+ rasterize_blobs(txn, is_low_priority);
+
+ profile.end_time(profiler::BLOB_RASTERIZATION_TIME);
+ }
+
+ drain_filter(
+ &mut txn.notifications,
+ |n| { n.when() == Checkpoint::SceneBuilt },
+ |n| { n.notify(); },
+ );
+
+ if self.simulate_slow_ms > 0 {
+ thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
+ }
+
+ Box::new(BuiltTransaction {
+ document_id: txn.document_id,
+ render_frame: txn.generate_frame.as_bool(),
+ invalidate_rendered_frame: txn.invalidate_rendered_frame,
+ built_scene,
+ view: doc.view,
+ rasterized_blobs: replace(&mut txn.rasterized_blobs, Vec::new()),
+ resource_updates: replace(&mut txn.resource_updates, Vec::new()),
+ blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
+ frame_ops: replace(&mut txn.frame_ops, Vec::new()),
+ removed_pipelines,
+ discard_frame_state_for_pipelines,
+ notifications: replace(&mut txn.notifications, Vec::new()),
+ interner_updates,
+ profile,
+ })
+ }
+
+ /// Send the results of process_transaction back to the render backend.
+ fn forward_built_transactions(&mut self, txns: Vec<Box<BuiltTransaction>>) {
+ let (pipeline_info, result_tx, result_rx) = match self.hooks {
+ Some(ref hooks) => {
+ if txns.iter().any(|txn| txn.built_scene.is_some()) {
+ let info = PipelineInfo {
+ epochs: txns.iter()
+ .filter(|txn| txn.built_scene.is_some())
+ .map(|txn| {
+ txn.built_scene.as_ref().unwrap()
+ .pipeline_epochs.iter()
+ .zip(iter::repeat(txn.document_id))
+ .map(|((&pipeline_id, &epoch), document_id)| ((pipeline_id, document_id), epoch))
+ }).flatten().collect(),
+ removed_pipelines: txns.iter()
+ .map(|txn| txn.removed_pipelines.clone())
+ .flatten().collect(),
+ };
+
+ let (tx, rx) = single_msg_channel();
+ let txn = txns.iter().find(|txn| txn.built_scene.is_some()).unwrap();
+ hooks.pre_scene_swap((txn.profile.get(profiler::SCENE_BUILD_TIME).unwrap() * 1000000.0) as u64);
+
+ (Some(info), Some(tx), Some(rx))
+ } else {
+ (None, None, None)
+ }
+ }
+ _ => (None, None, None)
+ };
+
+ let scene_swap_start_time = precise_time_ns();
+ let document_ids = txns.iter().map(|txn| txn.document_id).collect();
+ let have_resources_updates : Vec<DocumentId> = if pipeline_info.is_none() {
+ txns.iter()
+ .filter(|txn| !txn.resource_updates.is_empty() || txn.invalidate_rendered_frame)
+ .map(|txn| txn.document_id)
+ .collect()
+ } else {
+ Vec::new()
+ };
+
+ #[cfg(feature = "capture")]
+ match self.capture_config {
+ Some(ref config) => self.send(SceneBuilderResult::CapturedTransactions(txns, config.clone(), result_tx)),
+ None => self.send(SceneBuilderResult::Transactions(txns, result_tx)),
+ };
+
+ #[cfg(not(feature = "capture"))]
+ self.send(SceneBuilderResult::Transactions(txns, result_tx));
+
+ let _ = self.tx.send(ApiMsg::WakeUp);
+
+ if let Some(pipeline_info) = pipeline_info {
+ // Block until the swap is done, then invoke the hook.
+ let swap_result = result_rx.unwrap().recv();
+ let scene_swap_time = precise_time_ns() - scene_swap_start_time;
+ self.hooks.as_ref().unwrap().post_scene_swap(&document_ids,
+ pipeline_info, scene_swap_time);
+ // Once the hook is done, allow the RB thread to resume
+ if let Ok(SceneSwapResult::Complete(resume_tx)) = swap_result {
+ resume_tx.send(()).ok();
+ }
+ } else if !have_resources_updates.is_empty() {
+ if let Some(ref hooks) = self.hooks {
+ hooks.post_resource_update(&have_resources_updates);
+ }
+ } else if let Some(ref hooks) = self.hooks {
+ hooks.post_empty_scene_build();
+ }
+ }
+
+ /// Reports CPU heap memory used by the SceneBuilder.
+ fn report_memory(&mut self) -> MemoryReport {
+ let ops = self.size_of_ops.as_mut().unwrap();
+ let mut report = MemoryReport::default();
+ for doc in self.documents.values() {
+ doc.interners.report_memory(ops, &mut report);
+ doc.scene.report_memory(ops, &mut report);
+ }
+
+ report
+ }
+}
+
+/// 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 LowPrioritySceneBuilderThread {
+ pub rx: Receiver<SceneBuilderRequest>,
+ pub tx: Sender<SceneBuilderRequest>,
+ pub simulate_slow_ms: u32,
+}
+
+impl LowPrioritySceneBuilderThread {
+ pub fn run(&mut self) {
+ loop {
+ match self.rx.recv() {
+ Ok(SceneBuilderRequest::Transactions(mut txns)) => {
+ let txns : Vec<Box<TransactionMsg>> = txns.drain(..)
+ .map(|txn| self.process_transaction(txn))
+ .collect();
+ self.tx.send(SceneBuilderRequest::Transactions(txns)).unwrap();
+ }
+ Ok(SceneBuilderRequest::ShutDown(sync)) => {
+ self.tx.send(SceneBuilderRequest::ShutDown(sync)).unwrap();
+ break;
+ }
+ Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(time_ms)) => {
+ self.simulate_slow_ms = time_ms;
+ }
+ Ok(other) => {
+ self.tx.send(other).unwrap();
+ }
+ Err(_) => {
+ break;
+ }
+ }
+ }
+ }
+
+ fn process_transaction(&mut self, mut txn: Box<TransactionMsg>) -> Box<TransactionMsg> {
+ let is_low_priority = true;
+ rasterize_blobs(&mut txn, is_low_priority);
+ txn.blob_requests = Vec::new();
+
+ if self.simulate_slow_ms > 0 {
+ thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
+ }
+
+ txn
+ }
+}