diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index 33a15a4dfb..e623844c3f 100644 --- a/webrender/src/display_list_flattener.rs +++ b/webrender/src/display_list_flattener.rs @@ -31,7 +31,6 @@ use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive}; use render_backend::{DocumentView}; use resource_cache::{FontInstanceMap, ImageRequest}; use scene::{Scene, ScenePipeline, StackingContextHelpers}; -use scene_builder::{BuiltScene, SceneRequest}; use spatial_node::{SpatialNodeType, StickyFrameInfo}; use std::{f32, iter, mem}; use tiling::{CompositeOps, ScrollbarPrimitive}; @@ -2000,31 +1999,6 @@ impl<'a> DisplayListFlattener<'a> { } } -pub fn build_scene(config: &FrameBuilderConfig, request: SceneRequest) -> BuiltScene { - - let mut clip_scroll_tree = ClipScrollTree::new(); - let mut new_scene = Scene::new(); - - let frame_builder = DisplayListFlattener::create_frame_builder( - FrameBuilder::empty(), // WIP, we're not really recycling anything here, clean this up. - &request.scene, - &mut clip_scroll_tree, - request.font_instances, - &request.view, - &request.output_pipelines, - config, - &mut new_scene, - request.scene_id, - ); - - BuiltScene { - scene: new_scene, - frame_builder, - clip_scroll_tree, - removed_pipelines: request.removed_pipelines, - } -} - /// Properties of a stacking context that are maintained /// during creation of the scene. These structures are /// not persisted after the initial scene build. diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 2d6e1ce615..6363fdf75f 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -17,8 +17,6 @@ use api::CapturedDocument; use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree}; #[cfg(feature = "debugger")] use debug_server; -#[cfg(feature = "replay")] -use display_list_flattener::build_scene; use frame_builder::{FrameBuilder, FrameBuilderConfig}; use gpu_cache::GpuCache; use hit_test::{HitTest, HitTester}; @@ -70,11 +68,6 @@ impl DocumentView { } } -struct SceneData { - scene: Scene, - removed_pipelines: Vec, -} - #[derive(Copy, Clone, Hash, PartialEq, PartialOrd, Debug, Eq, Ord)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] @@ -83,10 +76,11 @@ pub struct FrameId(pub u32); struct Document { // The latest built scene, usable to build frames. // received from the scene builder thread. - current: SceneData, - // The scene with the latest transactions applied, not necessarily built yet. - // what we will send to the scene builder. - pending: SceneData, + scene: Scene, + + // Temporary list of removed pipelines received from the scene builder + // thread and forwarded to the renderer. + removed_pipelines: Vec, view: DocumentView, @@ -98,9 +92,6 @@ struct Document { /// The id of the current frame. frame_id: FrameId, - /// A configuration object for the FrameBuilder that we produce. - frame_builder_config: FrameBuilderConfig, - // the `Option` here is only to deal with borrow checker frame_builder: Option, // A set of pipelines that the caller has requested be @@ -124,7 +115,6 @@ struct Document { impl Document { pub fn new( - frame_builder_config: FrameBuilderConfig, window_size: DeviceUintSize, layer: DocumentLayer, enable_render_on_scroll: bool, @@ -136,14 +126,8 @@ impl Document { None }; Document { - current: SceneData { - scene: Scene::new(), - removed_pipelines: Vec::new(), - }, - pending: SceneData { - scene: Scene::new(), - removed_pipelines: Vec::new(), - }, + scene: Scene::new(), + removed_pipelines: Vec::new(), view: DocumentView { window_size, inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), window_size), @@ -155,7 +139,6 @@ impl Document { }, clip_scroll_tree: ClipScrollTree::new(), frame_id: FrameId(0), - frame_builder_config, frame_builder: None, output_pipelines: FastHashSet::default(), render_on_scroll, @@ -164,7 +147,9 @@ impl Document { } } - fn can_render(&self) -> bool { self.frame_builder.is_some() } + fn can_render(&self) -> bool { + self.frame_builder.is_some() && self.scene.has_root_pipeline() + } fn has_pixels(&self) -> bool { !self.view.window_size.is_empty_or_negative() @@ -176,9 +161,7 @@ impl Document { ) -> DocumentOps { match message { FrameMsg::UpdateEpoch(pipeline_id, epoch) => { - self.current.scene.update_epoch(pipeline_id, epoch); - - DocumentOps::nop() + self.scene.update_epoch(pipeline_id, epoch); } FrameMsg::EnableFrameOutput(pipeline_id, enable) => { if enable { @@ -186,7 +169,6 @@ impl Document { } else { self.output_pipelines.remove(&pipeline_id); } - DocumentOps::nop() } FrameMsg::Scroll(delta, cursor) => { profile_scope!("Scroll"); @@ -209,12 +191,13 @@ impl Document { should_render && self.scroll_nearest_scrolling_ancestor(delta, node_index) && self.render_on_scroll == Some(true); - DocumentOps { + + return DocumentOps { scroll: true, - render: should_render, - composite: should_render, + build_frame: should_render, + render_frame: should_render, ..DocumentOps::nop() - } + }; } FrameMsg::HitTest(pipeline_id, point, flags, tx) => { @@ -226,11 +209,9 @@ impl Document { }; tx.send(result).unwrap(); - DocumentOps::nop() } FrameMsg::SetPan(pan) => { self.view.pan = pan; - DocumentOps::nop() } FrameMsg::ScrollNodeWithId(origin, id, clamp) => { profile_scope!("ScrollNodeWithScrollId"); @@ -238,75 +219,30 @@ impl Document { let should_render = self.scroll_node(origin, id, clamp) && self.render_on_scroll == Some(true); - DocumentOps { + return DocumentOps { scroll: true, - render: should_render, - composite: should_render, + build_frame: should_render, + render_frame: should_render, ..DocumentOps::nop() - } + }; } FrameMsg::GetScrollNodeState(tx) => { profile_scope!("GetScrollNodeState"); tx.send(self.get_scroll_node_state()).unwrap(); - DocumentOps::nop() } FrameMsg::UpdateDynamicProperties(property_bindings) => { self.dynamic_properties.set_properties(property_bindings); - DocumentOps::nop() } FrameMsg::AppendDynamicProperties(property_bindings) => { self.dynamic_properties.add_properties(property_bindings); - DocumentOps::nop() } } - } - - fn forward_transaction_to_scene_builder( - &mut self, - transaction_msg: TransactionMsg, - blobs_to_rasterize: &[ImageKey], - document_ops: &DocumentOps, - document_id: DocumentId, - scene_id: u64, - resource_cache: &mut ResourceCache, - scene_tx: &Sender, - ) { - // Do as much of the error handling as possible here before dispatching to - // the scene builder thread. - let build_scene: bool = document_ops.build - && self.pending.scene.root_pipeline_id.map( - |id| { self.pending.scene.pipelines.contains_key(&id) } - ).unwrap_or(false); - - let scene_request = if build_scene { - Some(SceneRequest { - scene: self.pending.scene.clone(), - removed_pipelines: replace(&mut self.pending.removed_pipelines, Vec::new()), - view: self.view.clone(), - font_instances: resource_cache.get_font_instances(), - output_pipelines: self.output_pipelines.clone(), - scene_id, - }) - } else { - None - }; - - let (blob_rasterizer, blob_requests) = resource_cache.create_blob_scene_builder_requests( - blobs_to_rasterize - ); - scene_tx.send(SceneBuilderRequest::Transaction { - scene: scene_request, - blob_requests, - blob_rasterizer, - resource_updates: transaction_msg.resource_updates, - frame_ops: transaction_msg.frame_ops, - render: transaction_msg.generate_frame, - document_id, - }).unwrap(); + DocumentOps::nop() } - fn render( + + fn build_frame( &mut self, resource_cache: &mut ResourceCache, gpu_cache: &mut GpuCache, @@ -323,7 +259,7 @@ impl Document { gpu_cache, self.frame_id, &mut self.clip_scroll_tree, - &self.current.scene.pipelines, + &self.scene.pipelines, accumulated_scale_factor, self.view.layer, pan, @@ -342,9 +278,9 @@ impl Document { } pub fn updated_pipeline_info(&mut self) -> PipelineInfo { - let removed_pipelines = replace(&mut self.current.removed_pipelines, Vec::new()); + let removed_pipelines = replace(&mut self.removed_pipelines, Vec::new()); PipelineInfo { - epochs: self.current.scene.pipeline_epochs.clone(), + epochs: self.scene.pipeline_epochs.clone(), removed_pipelines, } } @@ -377,11 +313,10 @@ impl Document { self.clip_scroll_tree.get_scroll_node_state() } - pub fn new_async_scene_ready(&mut self, mut built_scene: BuiltScene) { - self.current.scene = built_scene.scene; + pub fn new_async_scene_ready(&mut self, built_scene: BuiltScene) { + self.scene = built_scene.scene; self.frame_builder = Some(built_scene.frame_builder); - self.current.removed_pipelines.extend(built_scene.removed_pipelines.drain(..)); let old_scrolling_states = self.clip_scroll_tree.drain(); self.clip_scroll_tree = built_scene.clip_scroll_tree; @@ -394,41 +329,18 @@ impl Document { struct DocumentOps { scroll: bool, - build: bool, - render: bool, - composite: bool, + build_frame: bool, + render_frame: bool, } impl DocumentOps { fn nop() -> Self { DocumentOps { scroll: false, - build: false, - render: false, - composite: false, - } - } - - fn build() -> Self { - DocumentOps { - build: true, - ..DocumentOps::nop() - } - } - - fn render() -> Self { - DocumentOps { - render: true, - ..DocumentOps::nop() + build_frame: false, + render_frame: false, } } - - fn combine(&mut self, other: Self) { - self.scroll = self.scroll || other.scroll; - self.build = self.build || other.build; - self.render = self.render || other.render; - self.composite = self.composite || other.composite; - } } /// The unique id for WR resource identification. @@ -518,23 +430,20 @@ impl RenderBackend { document_id: DocumentId, message: SceneMsg, frame_counter: u32, + txn: &mut Transaction, ipc_profile_counters: &mut IpcProfileCounters, - ) -> DocumentOps { + ) { let doc = self.documents.get_mut(&document_id).expect("No document?"); match message { SceneMsg::UpdateEpoch(pipeline_id, epoch) => { - doc.pending.scene.update_epoch(pipeline_id, epoch); - - DocumentOps::nop() + txn.epoch_updates.push((pipeline_id, epoch)); } SceneMsg::SetPageZoom(factor) => { doc.view.page_zoom_factor = factor.get(); - DocumentOps::nop() } SceneMsg::SetPinchZoom(factor) => { doc.view.pinch_zoom_factor = factor.get(); - DocumentOps::nop() } SceneMsg::SetWindowParameters { window_size, @@ -544,7 +453,6 @@ impl RenderBackend { doc.view.window_size = window_size; doc.view.inner_rect = inner_rect; doc.view.device_pixel_ratio = device_pixel_ratio; - DocumentOps::nop() } SceneMsg::SetDisplayList { epoch, @@ -588,16 +496,14 @@ impl RenderBackend { built_display_list.times(); let display_list_received_time = precise_time_ns(); - { - doc.pending.scene.set_display_list( - pipeline_id, - epoch, - built_display_list, - background, - viewport_size, - content_size, - ); - } + txn.display_list_updates.push(DisplayListUpdate { + built_display_list, + pipeline_id, + epoch, + background, + viewport_size, + content_size, + }); if let Some(ref mut ros) = doc.render_on_scroll { *ros = false; //wait for `GenerateFrame` @@ -616,25 +522,16 @@ impl RenderBackend { display_list_consumed_time, display_list_len, ); - - DocumentOps::build() } SceneMsg::SetRootPipeline(pipeline_id) => { profile_scope!("SetRootPipeline"); - doc.pending.scene.set_root_pipeline_id(pipeline_id); - if doc.pending.scene.pipelines.get(&pipeline_id).is_some() { - DocumentOps::build() - } else { - DocumentOps::nop() - } + txn.set_root_pipeline = Some(pipeline_id); } SceneMsg::RemovePipeline(pipeline_id) => { profile_scope!("RemovePipeline"); - doc.pending.scene.remove_pipeline(pipeline_id); - doc.pending.removed_pipelines.push(pipeline_id); - DocumentOps::nop() + txn.removed_pipelines.push(pipeline_id); } } } @@ -662,25 +559,16 @@ impl RenderBackend { while let Ok(msg) = self.scene_rx.try_recv() { match msg { - SceneBuilderResult::Transaction { - document_id, - mut built_scene, - resource_updates, - frame_ops, - render, - result_tx, - rasterized_blobs, - blob_rasterizer, - } => { - let mut ops = DocumentOps::nop(); - if let Some(doc) = self.documents.get_mut(&document_id) { - if let Some(mut built_scene) = built_scene.take() { + SceneBuilderResult::Transaction(mut txn, result_tx) => { + let has_built_scene = txn.built_scene.is_some(); + if let Some(doc) = self.documents.get_mut(&txn.document_id) { + + doc.removed_pipelines.append(&mut txn.removed_pipelines); + + if let Some(mut built_scene) = txn.built_scene.take() { doc.new_async_scene_ready(built_scene); - // After applying the new scene we need to - // rebuild the hit-tester, so we trigger a render - // step. - ops = DocumentOps::render(); } + if let Some(tx) = result_tx { let (resume_tx, resume_rx) = channel(); tx.send(SceneSwapResult::Complete(resume_tx)).unwrap(); @@ -700,28 +588,23 @@ impl RenderBackend { continue; } - let transaction_msg = TransactionMsg { - scene_ops: Vec::new(), - frame_ops, - resource_updates, - generate_frame: render, - use_scene_builder_thread: false, - }; - - self.resource_cache.add_rasterized_blob_images(rasterized_blobs); - if let Some(rasterizer) = blob_rasterizer { + self.resource_cache.add_rasterized_blob_images( + replace(&mut txn.rasterized_blobs, Vec::new()) + ); + if let Some(rasterizer) = txn.blob_rasterizer.take() { self.resource_cache.set_blob_rasterizer(rasterizer); } - if !transaction_msg.is_empty() || ops.render { + if txn.build_frame || !txn.resource_updates.is_empty() || !txn.frame_ops.is_empty() { self.update_document( - document_id, - transaction_msg, - &[], + txn.document_id, + replace(&mut txn.resource_updates, Vec::new()), + replace(&mut txn.frame_ops, Vec::new()), + txn.build_frame, + txn.render_frame, &mut frame_counter, &mut profile_counters, - ops, - true, + has_built_scene, ); } }, @@ -817,7 +700,6 @@ impl RenderBackend { } ApiMsg::AddDocument(document_id, initial_size, layer) => { let document = Document::new( - self.frame_config.clone(), initial_size, layer, self.enable_render_on_scroll, @@ -827,6 +709,9 @@ impl RenderBackend { } ApiMsg::DeleteDocument(document_id) => { self.documents.remove(&document_id); + self.scene_tx.send( + SceneBuilderRequest::DeleteDocument(document_id) + ).unwrap(); } ApiMsg::ExternalEvent(evt) => { self.notifier.external_event(evt); @@ -869,11 +754,6 @@ impl RenderBackend { self.frame_config .dual_source_blending_is_enabled = enable; - // Set for any existing documents. - for (_, doc) in &mut self.documents { - doc.frame_builder_config.dual_source_blending_is_enabled = enable; - } - self.scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig( self.frame_config.clone() )).unwrap(); @@ -904,7 +784,7 @@ impl RenderBackend { for (id, doc) in &self.documents { let captured = CapturedDocument { document_id: *id, - root_pipeline_id: doc.current.scene.root_pipeline_id, + root_pipeline_id: doc.scene.root_pipeline_id, window_size: doc.view.window_size, }; tx.send(captured).unwrap(); @@ -925,76 +805,109 @@ impl RenderBackend { ApiMsg::ShutDown => { return false; } - ApiMsg::UpdateDocument(document_id, mut doc_msgs) => { - let blob_requests = get_blob_image_updates(&doc_msgs.resource_updates); - - self.resource_cache.pre_scene_building_update( - &mut doc_msgs.resource_updates, - &mut profile_counters.resources, - ); - - self.update_document( + ApiMsg::UpdateDocument(document_id, transaction_msg) => { + self.prepare_transaction( document_id, - doc_msgs, - &blob_requests, + transaction_msg, frame_counter, profile_counters, - DocumentOps::nop(), - false, - ) + ); } } true } - fn update_document( + fn prepare_transaction( &mut self, document_id: DocumentId, mut transaction_msg: TransactionMsg, - blob_requests: &[ImageKey], frame_counter: &mut u32, profile_counters: &mut BackendProfileCounters, - initial_op: DocumentOps, - has_built_scene: bool, ) { - let mut op = initial_op; + let mut txn = Box::new(Transaction { + document_id, + display_list_updates: Vec::new(), + removed_pipelines: Vec::new(), + epoch_updates: Vec::new(), + request_scene_build: None, + blob_rasterizer: None, + blob_requests: Vec::new(), + resource_updates: transaction_msg.resource_updates, + frame_ops: transaction_msg.frame_ops, + set_root_pipeline: None, + build_frame: transaction_msg.generate_frame, + render_frame: transaction_msg.generate_frame, + }); - if !blob_requests.is_empty() { - transaction_msg.use_scene_builder_thread = true; - } + self.resource_cache.pre_scene_building_update( + &mut txn.resource_updates, + &mut profile_counters.resources, + ); for scene_msg in transaction_msg.scene_ops.drain(..) { let _timer = profile_counters.total_time.timer(); - op.combine( - self.process_scene_msg( - document_id, - scene_msg, - *frame_counter, - &mut profile_counters.ipc, - ) - ); + self.process_scene_msg( + document_id, + scene_msg, + *frame_counter, + &mut txn, + &mut profile_counters.ipc, + ) } - if !has_built_scene && (op.build || transaction_msg.use_scene_builder_thread) { - let scene_id = self.make_unique_scene_id(); - let doc = self.documents.get_mut(&document_id).unwrap(); + let blobs_to_rasterize = get_blob_image_updates(&txn.resource_updates); + if !blobs_to_rasterize.is_empty() { + let (blob_rasterizer, blob_requests) = self.resource_cache + .create_blob_scene_builder_requests(&blobs_to_rasterize); - doc.forward_transaction_to_scene_builder( - transaction_msg, - blob_requests, - &op, - document_id, - scene_id, - &mut self.resource_cache, - &self.scene_tx, + txn.blob_requests = blob_requests; + txn.blob_rasterizer = blob_rasterizer; + } + + if !transaction_msg.use_scene_builder_thread && txn.can_skip_scene_builder() { + self.update_document( + txn.document_id, + replace(&mut txn.resource_updates, Vec::new()), + replace(&mut txn.frame_ops, Vec::new()), + txn.build_frame, + txn.render_frame, + frame_counter, + profile_counters, + false ); return; } + let scene_id = self.make_unique_scene_id(); + let doc = self.documents.get_mut(&document_id).unwrap(); + + if txn.should_build_scene() { + txn.request_scene_build = Some(SceneRequest { + view: doc.view.clone(), + font_instances: self.resource_cache.get_font_instances(), + output_pipelines: doc.output_pipelines.clone(), + scene_id, + }); + } + + self.scene_tx.send(SceneBuilderRequest::Transaction(txn)).unwrap(); + } + + fn update_document( + &mut self, + document_id: DocumentId, + resource_updates: Vec, + mut frame_ops: Vec, + mut build_frame: bool, + mut render_frame: bool, + frame_counter: &mut u32, + profile_counters: &mut BackendProfileCounters, + has_built_scene: bool, + ) { self.resource_cache.post_scene_building_update( - transaction_msg.resource_updates, + resource_updates, &mut profile_counters.resources, ); @@ -1003,46 +916,55 @@ impl RenderBackend { // fiddle with things after a potentially long scene build, but just // before rendering. This is useful for rendering with the latest // async transforms. - if op.render || transaction_msg.generate_frame { + if build_frame { if let Some(ref sampler) = self.sampler { - transaction_msg.frame_ops.append(&mut sampler.sample()); + frame_ops.append(&mut sampler.sample()); } } + let doc = self.documents.get_mut(&document_id).unwrap(); - for frame_msg in transaction_msg.frame_ops { + let mut scroll = false; + for frame_msg in frame_ops { let _timer = profile_counters.total_time.timer(); - op.combine(doc.process_frame_msg(frame_msg)); + let op = doc.process_frame_msg(frame_msg); + build_frame |= op.build_frame; + render_frame |= op.render_frame; + scroll |= op.scroll; } + // After applying the new scene we need to + // rebuild the hit-tester, so we trigger a frame generation + // step. + // + // TODO: We could avoid some the cost of building the frame by only + // building the information required for hit-testing (See #2807). + build_frame |= has_built_scene; + if doc.dynamic_properties.flush_pending_updates() { - op.render = true; + build_frame = true; } - if transaction_msg.generate_frame { + if render_frame { if let Some(ref mut ros) = doc.render_on_scroll { *ros = true; } - - if doc.current.scene.root_pipeline_id.is_some() { - op.render = true; - op.composite = true; - } } if !doc.can_render() { // TODO: this happens if we are building the first scene asynchronously and // scroll at the same time. we should keep track of the fact that we skipped // composition here and do it as soon as we receive the scene. - op.render = false; - op.composite = false; + build_frame = false; + render_frame = false; } - debug_assert!(op.render || !op.composite); + // If we don't generate a frame it makes no sense to render. + debug_assert!(build_frame || !render_frame); - let mut render_time = None; - if op.render && doc.has_pixels() { + let mut frame_build_time = None; + if build_frame && doc.has_pixels() { profile_scope!("generate frame"); *frame_counter += 1; @@ -1050,9 +972,9 @@ impl RenderBackend { // borrow ck hack for profile_counters let (pending_update, rendered_document) = { let _timer = profile_counters.total_time.timer(); - let render_start_time = precise_time_ns(); + let frame_build_start_time = precise_time_ns(); - let rendered_document = doc.render( + let rendered_document = doc.build_frame( &mut self.resource_cache, &mut self.gpu_cache, &mut profile_counters.resources, @@ -1065,7 +987,7 @@ impl RenderBackend { let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); self.result_tx.send(msg).unwrap(); - render_time = Some(precise_time_ns() - render_start_time); + frame_build_time = Some(precise_time_ns() - frame_build_start_time); let pending_update = self.resource_cache.pending_updates(); (pending_update, rendered_document) @@ -1083,17 +1005,17 @@ impl RenderBackend { ); self.result_tx.send(msg).unwrap(); profile_counters.reset(); - } else if op.render { + } else if build_frame { // WR-internal optimization to avoid doing a bunch of render work if // there's no pixels. We still want to pretend to render and request - // a composite to make sure that the callbacks (particularly the + // a render to make sure that the callbacks (particularly the // new_frame_ready callback below) has the right flags. let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info()); self.result_tx.send(msg).unwrap(); } - if transaction_msg.generate_frame { - self.notifier.new_frame_ready(document_id, op.scroll, op.composite, render_time); + if render_frame { + self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time); } } @@ -1149,7 +1071,7 @@ impl RenderBackend { for (_, doc) in &self.documents { let mut debug_doc = debug_server::TreeNode::new("document"); - for (_, pipeline) in &doc.current.scene.pipelines { + 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); @@ -1267,10 +1189,10 @@ impl RenderBackend { debug!("\tdocument {:?}", id); if config.bits.contains(CaptureBits::SCENE) { let file_name = format!("scene-{}-{}", (id.0).0, id.1); - config.serialize(&doc.current.scene, file_name); + config.serialize(&doc.scene, file_name); } if config.bits.contains(CaptureBits::FRAME) { - let rendered_document = doc.render( + let rendered_document = doc.build_frame( &mut self.resource_cache, &mut self.gpu_cache, &mut profile_counters.resources, @@ -1357,6 +1279,8 @@ impl RenderBackend { self.frame_config = backend.frame_config; self.enable_render_on_scroll = backend.enable_render_on_scroll; + let mut scenes_to_build = Vec::new(); + let mut last_scene_id = backend.last_scene_id; for (id, view) in backend.documents { debug!("\tdocument {:?}", id); @@ -1365,18 +1289,11 @@ impl RenderBackend { .expect(&format!("Unable to open {}.ron", scene_name)); let mut doc = Document { - current: SceneData { - scene: scene.clone(), - removed_pipelines: Vec::new(), - }, - pending: SceneData { - scene, - removed_pipelines: Vec::new(), - }, + scene: scene.clone(), + removed_pipelines: Vec::new(), view: view.clone(), clip_scroll_tree: ClipScrollTree::new(), frame_id: FrameId(0), - frame_builder_config: self.frame_config.clone(), frame_builder: Some(FrameBuilder::empty()), output_pipelines: FastHashSet::default(), render_on_scroll: None, @@ -1385,46 +1302,53 @@ impl RenderBackend { }; let frame_name = format!("frame-{}-{}", (id.0).0, id.1); - let render_doc = match CaptureConfig::deserialize::(root, frame_name) { + let frame = CaptureConfig::deserialize::(root, frame_name); + let build_frame = match frame { Some(frame) => { info!("\tloaded a built frame with {} passes", frame.passes.len()); - RenderedDocument { frame, is_new_scene: true } - } - None => { - last_scene_id += 1; - let built_scene = build_scene(&self.frame_config, SceneRequest { - scene: doc.pending.scene.clone(), - view, - font_instances: self.resource_cache.get_font_instances(), - output_pipelines: doc.output_pipelines.clone(), - removed_pipelines: Vec::new(), - scene_id: last_scene_id, - }); - doc.new_async_scene_ready(built_scene); - doc.render( - &mut self.resource_cache, - &mut self.gpu_cache, - &mut profile_counters.resources, - true, - ) + + let msg_update = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); + self.result_tx.send(msg_update).unwrap(); + + let msg_publish = ResultMsg::PublishDocument( + id, + RenderedDocument { frame, is_new_scene: true }, + self.resource_cache.pending_updates(), + profile_counters.clone(), + ); + self.result_tx.send(msg_publish).unwrap(); + profile_counters.reset(); + + self.notifier.new_frame_ready(id, false, true, None); + + // We deserialized the state of the frame so we don't want to build + // it (but we do want to update the scene builder's state) + false } + None => true, }; - let msg_update = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); - self.result_tx.send(msg_update).unwrap(); + last_scene_id += 1; - let msg_publish = ResultMsg::PublishDocument( - id, - render_doc, - self.resource_cache.pending_updates(), - profile_counters.clone(), - ); - self.result_tx.send(msg_publish).unwrap(); - profile_counters.reset(); + scenes_to_build.push(LoadScene { + document_id: id, + scene: doc.scene.clone(), + view: view.clone(), + config: self.frame_config.clone(), + output_pipelines: doc.output_pipelines.clone(), + font_instances: self.resource_cache.get_font_instances(), + scene_id: last_scene_id, + build_frame, + }); - self.notifier.new_frame_ready(id, false, true, None); self.documents.insert(id, doc); } + + if !scenes_to_build.is_empty() { + self.scene_tx.send( + SceneBuilderRequest::LoadScenes(scenes_to_build) + ).unwrap(); + } } } diff --git a/webrender/src/scene.rs b/webrender/src/scene.rs index ce0891917d..b77d452cac 100644 --- a/webrender/src/scene.rs +++ b/webrender/src/scene.rs @@ -188,6 +188,14 @@ impl Scene { pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) { self.pipeline_epochs.insert(pipeline_id, epoch); } + + pub fn has_root_pipeline(&self) -> bool { + if let Some(ref root_id) = self.root_pipeline_id { + return self.pipelines.contains_key(root_id); + } + + false + } } /// An arbitrary number which we assume opacity is invisible below. diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index 642b05ca11..4ecec13963 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -3,48 +3,119 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult}; -use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate}; +use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch}; +use api::{BuiltDisplayList, ColorF, LayoutSize}; use api::channel::MsgSender; -use display_list_flattener::build_scene; use frame_builder::{FrameBuilderConfig, FrameBuilder}; use clip_scroll_tree::ClipScrollTree; -use internal_types::FastHashSet; +use display_list_flattener::DisplayListFlattener; +use internal_types::{FastHashMap, FastHashSet}; use resource_cache::FontInstanceMap; use render_backend::DocumentView; use renderer::{PipelineInfo, SceneBuilderHooks}; use scene::Scene; use std::sync::mpsc::{channel, Receiver, Sender}; +use std::mem::replace; use time::precise_time_ns; +/// Represents the work associated to a transaction before scene building. +pub struct Transaction { + pub document_id: DocumentId, + pub display_list_updates: Vec, + pub removed_pipelines: Vec, + pub epoch_updates: Vec<(PipelineId, Epoch)>, + pub request_scene_build: Option, + pub blob_requests: Vec, + pub blob_rasterizer: Option>, + pub resource_updates: Vec, + pub frame_ops: Vec, + pub set_root_pipeline: Option, + pub build_frame: bool, + pub render_frame: bool, +} + +impl Transaction { + pub fn can_skip_scene_builder(&self) -> bool { + self.request_scene_build.is_none() && + self.display_list_updates.is_empty() && + self.epoch_updates.is_empty() && + self.removed_pipelines.is_empty() && + self.blob_requests.is_empty() && + self.set_root_pipeline.is_none() + } + + pub fn should_build_scene(&self) -> bool { + !self.display_list_updates.is_empty() || + self.set_root_pipeline.is_some() + } +} + +/// 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, + pub resource_updates: Vec, + pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, + pub blob_rasterizer: Option>, + pub frame_ops: Vec, + pub removed_pipelines: Vec, + pub scene_build_start_time: u64, + pub scene_build_end_time: u64, + pub build_frame: bool, + pub render_frame: bool, +} + +pub struct DisplayListUpdate { + pub pipeline_id: PipelineId, + pub epoch: Epoch, + pub built_display_list: BuiltDisplayList, + pub background: Option, + pub viewport_size: LayoutSize, + pub content_size: LayoutSize, +} + +/// Contains the render backend data needed to build a scene. +pub struct SceneRequest { + pub view: DocumentView, + pub font_instances: FontInstanceMap, + pub output_pipelines: FastHashSet, + pub scene_id: u64, +} + +#[cfg(feature = "replay")] +pub struct LoadScene { + pub document_id: DocumentId, + pub scene: Scene, + pub scene_id: u64, + pub output_pipelines: FastHashSet, + pub font_instances: FontInstanceMap, + pub view: DocumentView, + pub config: FrameBuilderConfig, + pub build_frame: bool, +} + +pub struct BuiltScene { + pub scene: Scene, + pub frame_builder: FrameBuilder, + pub clip_scroll_tree: ClipScrollTree, +} + // Message from render backend to scene builder. pub enum SceneBuilderRequest { - Transaction { - document_id: DocumentId, - scene: Option, - blob_requests: Vec, - blob_rasterizer: Option>, - resource_updates: Vec, - frame_ops: Vec, - render: bool, - }, + Transaction(Box), + DeleteDocument(DocumentId), WakeUp, Flush(MsgSender<()>), SetFrameBuilderConfig(FrameBuilderConfig), - Stop + Stop, + #[cfg(feature = "replay")] + LoadScenes(Vec), } // Message from scene builder to render backend. pub enum SceneBuilderResult { - Transaction { - document_id: DocumentId, - built_scene: Option, - resource_updates: Vec, - rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, - blob_rasterizer: Option>, - frame_ops: Vec, - render: bool, - result_tx: Option>, - }, + Transaction(Box, Option>), FlushComplete(MsgSender<()>), Stopped, } @@ -57,24 +128,8 @@ pub enum SceneSwapResult { Aborted, } -/// Contains the render backend data needed to build a scene. -pub struct SceneRequest { - pub scene: Scene, - pub view: DocumentView, - pub font_instances: FontInstanceMap, - pub output_pipelines: FastHashSet, - pub removed_pipelines: Vec, - pub scene_id: u64, -} - -pub struct BuiltScene { - pub scene: Scene, - pub frame_builder: FrameBuilder, - pub clip_scroll_tree: ClipScrollTree, - pub removed_pipelines: Vec, -} - pub struct SceneBuilder { + documents: FastHashMap, rx: Receiver, tx: Sender, api_tx: MsgSender, @@ -92,6 +147,7 @@ impl SceneBuilder { let (out_tx, out_rx) = channel(); ( SceneBuilder { + documents: FastHashMap::default(), rx: in_rx, tx: out_tx, api_tx, @@ -103,6 +159,7 @@ impl SceneBuilder { ) } + /// The scene builder thread's event loop. pub fn run(&mut self) { if let Some(ref hooks) = self.hooks { hooks.register(); @@ -110,10 +167,30 @@ impl SceneBuilder { loop { match self.rx.recv() { - Ok(msg) => { - if !self.process_message(msg) { - break; - } + Ok(SceneBuilderRequest::WakeUp) => {} + Ok(SceneBuilderRequest::Flush(tx)) => { + self.tx.send(SceneBuilderResult::FlushComplete(tx)).unwrap(); + let _ = self.api_tx.send(ApiMsg::WakeUp); + } + Ok(SceneBuilderRequest::Transaction(mut txn)) => { + let built_txn = self.process_transaction(&mut txn); + self.forward_built_transaction(built_txn); + } + Ok(SceneBuilderRequest::DeleteDocument(document_id)) => { + self.documents.remove(&document_id); + } + Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => { + self.config = cfg; + } + #[cfg(feature = "replay")] + Ok(SceneBuilderRequest::LoadScenes(msg)) => { + self.load_scenes(msg); + } + Ok(SceneBuilderRequest::Stop) => { + self.tx.send(SceneBuilderResult::Stopped).unwrap(); + // We don't need to send a WakeUp to api_tx because we only + // get the Stop when the RenderBackend loop is exiting. + break; } Err(_) => { break; @@ -130,97 +207,179 @@ impl SceneBuilder { } } - fn process_message(&mut self, msg: SceneBuilderRequest) -> bool { - match msg { - SceneBuilderRequest::WakeUp => {} - SceneBuilderRequest::Flush(tx) => { - self.tx.send(SceneBuilderResult::FlushComplete(tx)).unwrap(); - let _ = self.api_tx.send(ApiMsg::WakeUp); - } - SceneBuilderRequest::Transaction { - document_id, - scene, - blob_requests, - mut blob_rasterizer, - resource_updates, - frame_ops, - render, - } => { - let scenebuild_start_time = precise_time_ns(); - let built_scene = scene.map(|request|{ - build_scene(&self.config, request) + #[cfg(feature = "replay")] + fn load_scenes(&mut self, scenes: Vec) { + for item in scenes { + self.config = item.config; + + let scene_build_start_time = precise_time_ns(); + + let mut built_scene = None; + if item.scene.has_root_pipeline() { + let mut clip_scroll_tree = ClipScrollTree::new(); + let mut new_scene = Scene::new(); + + let frame_builder = DisplayListFlattener::create_frame_builder( + FrameBuilder::empty(), + &item.scene, + &mut clip_scroll_tree, + item.font_instances, + &item.view, + &item.output_pipelines, + &self.config, + &mut new_scene, + item.scene_id, + ); + + built_scene = Some(BuiltScene { + scene: new_scene, + frame_builder, + clip_scroll_tree, }); + } + + self.documents.insert(item.document_id, item.scene); + + let txn = Box::new(BuiltTransaction { + document_id: item.document_id, + build_frame: true, + render_frame: item.build_frame, + built_scene, + resource_updates: Vec::new(), + rasterized_blobs: Vec::new(), + blob_rasterizer: None, + frame_ops: Vec::new(), + removed_pipelines: Vec::new(), + scene_build_start_time, + scene_build_end_time: precise_time_ns(), + }); + + self.forward_built_transaction(txn); + } + } + + /// Do the bulk of the work of the scene builder thread. + fn process_transaction(&mut self, txn: &mut Transaction) -> Box { + + let scene_build_start_time = precise_time_ns(); + + let scene = self.documents.entry(txn.document_id).or_insert(Scene::new()); - let rasterized_blobs = blob_rasterizer.as_mut().map_or( - Vec::new(), - |rasterizer| rasterizer.rasterize(&blob_requests), + for update in txn.display_list_updates.drain(..) { + scene.set_display_list( + update.pipeline_id, + update.epoch, + update.built_display_list, + update.background, + update.viewport_size, + update.content_size, + ); + } + + for &(pipeline_id, epoch) in &txn.epoch_updates { + scene.update_epoch(pipeline_id, epoch); + } + + if let Some(id) = txn.set_root_pipeline { + scene.set_root_pipeline_id(id); + } + + for pipeline_id in &txn.removed_pipelines { + scene.remove_pipeline(*pipeline_id) + } + + let mut built_scene = None; + if scene.has_root_pipeline() { + if let Some(request) = txn.request_scene_build.take() { + let mut clip_scroll_tree = ClipScrollTree::new(); + let mut new_scene = Scene::new(); + + let frame_builder = DisplayListFlattener::create_frame_builder( + FrameBuilder::empty(), + &scene, + &mut clip_scroll_tree, + request.font_instances, + &request.view, + &request.output_pipelines, + &self.config, + &mut new_scene, + request.scene_id, ); - // We only need the pipeline info and the result channel if we - // have a hook callback *and* if this transaction actually built - // a new scene that is going to get swapped in. In other cases - // pipeline_info can be None and we can avoid some overhead from - // invoking the hooks and blocking on the channel. - let (pipeline_info, result_tx, result_rx) = match (&self.hooks, &built_scene) { - (&Some(ref hooks), &Some(ref built)) => { - let info = PipelineInfo { - epochs: built.scene.pipeline_epochs.clone(), - removed_pipelines: built.removed_pipelines.clone(), - }; - let (tx, rx) = channel(); - - let scenebuild_time = precise_time_ns() - scenebuild_start_time; - hooks.pre_scene_swap(scenebuild_time); - - (Some(info), Some(tx), Some(rx)) - } - _ => (None, None, None), + built_scene = Some(BuiltScene { + scene: new_scene, + frame_builder, + clip_scroll_tree, + }); + } + } + + let blob_requests = replace(&mut txn.blob_requests, Vec::new()); + let rasterized_blobs = txn.blob_rasterizer.as_mut().map_or( + Vec::new(), + |rasterizer| rasterizer.rasterize(&blob_requests), + ); + + Box::new(BuiltTransaction { + document_id: txn.document_id, + build_frame: txn.build_frame || built_scene.is_some(), + render_frame: txn.render_frame, + built_scene, + 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()), + scene_build_start_time, + scene_build_end_time: precise_time_ns(), + }) + } + + /// Send the result of process_transaction back to the render backend. + fn forward_built_transaction(&mut self, txn: Box) { + // We only need the pipeline info and the result channel if we + // have a hook callback *and* if this transaction actually built + // a new scene that is going to get swapped in. In other cases + // pipeline_info can be None and we can avoid some overhead from + // invoking the hooks and blocking on the channel. + let (pipeline_info, result_tx, result_rx) = match (&self.hooks, &txn.built_scene) { + (&Some(ref hooks), &Some(ref built)) => { + let info = PipelineInfo { + epochs: built.scene.pipeline_epochs.clone(), + removed_pipelines: txn.removed_pipelines.clone(), }; + let (tx, rx) = channel(); - let sceneswap_start_time = precise_time_ns(); - let has_resources_updates = !resource_updates.is_empty(); - self.tx.send(SceneBuilderResult::Transaction { - document_id, - built_scene, - resource_updates, - rasterized_blobs, - blob_rasterizer, - frame_ops, - render, - result_tx, - }).unwrap(); - - let _ = self.api_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 sceneswap_time = precise_time_ns() - sceneswap_start_time; - self.hooks.as_ref().unwrap().post_scene_swap(pipeline_info, sceneswap_time); - // Once the hook is done, allow the RB thread to resume - match swap_result { - Ok(SceneSwapResult::Complete(resume_tx)) => { - resume_tx.send(()).ok(); - }, - _ => (), - }; - } else if has_resources_updates { - if let &Some(ref hooks) = &self.hooks { - hooks.post_resource_update(); - } - } - } - SceneBuilderRequest::Stop => { - self.tx.send(SceneBuilderResult::Stopped).unwrap(); - // We don't need to send a WakeUp to api_tx because we only - // get the Stop when the RenderBackend loop is exiting. - return false; + hooks.pre_scene_swap(txn.scene_build_end_time - txn.scene_build_start_time); + + (Some(info), Some(tx), Some(rx)) } - SceneBuilderRequest::SetFrameBuilderConfig(cfg) => { - self.config = cfg; + _ => (None, None, None), + }; + + let scene_swap_start_time = precise_time_ns(); + let has_resources_updates = !txn.resource_updates.is_empty(); + + self.tx.send(SceneBuilderResult::Transaction(txn, result_tx)).unwrap(); + + let _ = self.api_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(pipeline_info, scene_swap_time); + // Once the hook is done, allow the RB thread to resume + match swap_result { + Ok(SceneSwapResult::Complete(resume_tx)) => { + resume_tx.send(()).ok(); + }, + _ => (), + }; + } else if has_resources_updates { + if let &Some(ref hooks) = &self.hooks { + hooks.post_resource_update(); } } - - true } }