1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 use api::{AsyncBlobImageRasterizer, BlobImageResult};
6 use api::{DocumentId, PipelineId, ExternalEvent, BlobImageRequest};
7 use api::{NotificationRequest, Checkpoint, IdNamespace, QualitySettings};
8 use api::{PrimitiveKeyKind, SharedFontInstanceMap};
9 use api::{GlyphDimensionRequest, GlyphIndexRequest};
10 use api::channel::{unbounded_channel, single_msg_channel, Receiver, Sender};
12 use crate::render_api::{ApiMsg, FrameMsg, SceneMsg, ResourceUpdate, TransactionMsg, MemoryReport};
13 #[cfg(feature = "capture")]
14 use crate::capture::CaptureConfig;
15 use crate::frame_builder::FrameBuilderConfig;
16 use crate::scene_building::SceneBuilder;
17 use crate::clip::{ClipIntern, PolygonIntern};
18 use crate::filterdata::FilterDataIntern;
19 use crate::intern::{Internable, Interner, UpdateList};
20 use crate::internal_types::{FastHashMap, FastHashSet};
21 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
22 use crate::prim_store::backdrop::Backdrop;
23 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
24 use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
25 use crate::prim_store::image::{Image, YuvImage};
26 use crate::prim_store::line_dec::LineDecoration;
27 use crate::prim_store::picture::Picture;
28 use crate::prim_store::text_run::TextRun;
29 use crate::profiler::{self, TransactionProfile};
30 use crate::render_backend::SceneView;
31 use crate::renderer::{FullFrameStats, PipelineInfo, SceneBuilderHooks};
32 use crate::scene::{Scene, BuiltScene, SceneStats};
34 use time::precise_time_ns;
35 use crate::util::drain_filter;
37 use std::time::Duration;
39 fn rasterize_blobs(txn: &mut TransactionMsg, is_low_priority: bool) {
40 profile_scope!("rasterize_blobs");
42 if let Some(ref mut rasterizer) = txn.blob_rasterizer {
43 let mut rasterized_blobs = rasterizer.rasterize(&txn.blob_requests, is_low_priority);
44 // try using the existing allocation if our current list is empty
45 if txn.rasterized_blobs.is_empty() {
46 txn.rasterized_blobs = rasterized_blobs;
48 txn.rasterized_blobs.append(&mut rasterized_blobs);
53 /// Represent the remaining work associated to a transaction after the scene building
54 /// phase as well as the result of scene building itself if applicable.
55 pub struct BuiltTransaction {
56 pub document_id: DocumentId,
57 pub built_scene: Option<BuiltScene>,
59 pub resource_updates: Vec<ResourceUpdate>,
60 pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
61 pub blob_rasterizer: Option<Box<dyn AsyncBlobImageRasterizer>>,
62 pub frame_ops: Vec<FrameMsg>,
63 pub removed_pipelines: Vec<(PipelineId, DocumentId)>,
64 pub notifications: Vec<NotificationRequest>,
65 pub interner_updates: Option<InternerUpdates>,
66 pub render_frame: bool,
67 pub invalidate_rendered_frame: bool,
68 pub discard_frame_state_for_pipelines: Vec<PipelineId>,
69 pub profile: TransactionProfile,
70 pub frame_stats: FullFrameStats,
73 #[cfg(feature = "replay")]
74 pub struct LoadScene {
75 pub document_id: DocumentId,
77 pub font_instances: SharedFontInstanceMap,
79 pub config: FrameBuilderConfig,
80 pub build_frame: bool,
81 pub interners: Interners,
84 /// Message to the scene builder thread.
85 pub enum SceneBuilderRequest {
86 Transactions(Vec<Box<TransactionMsg>>),
87 AddDocument(DocumentId, DeviceIntSize),
88 DeleteDocument(DocumentId),
89 GetGlyphDimensions(GlyphDimensionRequest),
90 GetGlyphIndices(GlyphIndexRequest),
91 ClearNamespace(IdNamespace),
92 SimulateLongSceneBuild(u32),
93 ExternalEvent(ExternalEvent),
96 ShutDown(Option<Sender<()>>),
98 SetFrameBuilderConfig(FrameBuilderConfig),
99 ReportMemory(Box<MemoryReport>, Sender<Box<MemoryReport>>),
100 #[cfg(feature = "capture")]
101 SaveScene(CaptureConfig),
102 #[cfg(feature = "replay")]
103 LoadScenes(Vec<LoadScene>),
104 #[cfg(feature = "capture")]
105 StartCaptureSequence(CaptureConfig),
106 #[cfg(feature = "capture")]
110 // Message from scene builder to render backend.
111 pub enum SceneBuilderResult {
112 Transactions(Vec<Box<BuiltTransaction>>, Option<Sender<SceneSwapResult>>),
113 ExternalEvent(ExternalEvent),
114 FlushComplete(Sender<()>),
115 DeleteDocument(DocumentId),
116 ClearNamespace(IdNamespace),
117 GetGlyphDimensions(GlyphDimensionRequest),
118 GetGlyphIndices(GlyphIndexRequest),
120 ShutDown(Option<Sender<()>>),
122 #[cfg(feature = "capture")]
123 /// The same as `Transactions`, but also supplies a `CaptureConfig` that the
124 /// render backend should use for sequence capture, until the next
125 /// `CapturedTransactions` or `StopCaptureSequence` result.
126 CapturedTransactions(Vec<Box<BuiltTransaction>>, CaptureConfig, Option<Sender<SceneSwapResult>>),
128 #[cfg(feature = "capture")]
129 /// The scene builder has stopped sequence capture, so the render backend
130 /// should do the same.
134 // Message from render backend to scene builder to indicate the
135 // scene swap was completed. We need a separate channel for this
136 // so that they don't get mixed with SceneBuilderRequest messages.
137 pub enum SceneSwapResult {
138 Complete(Sender<()>),
142 macro_rules! declare_interners {
143 ( $( $name:ident : $ty:ident, )+ ) => {
144 /// This struct contains all items that can be shared between
145 /// display lists. We want to intern and share the same clips,
146 /// primitives and other things between display lists so that:
147 /// - GPU cache handles remain valid, reducing GPU cache updates.
148 /// - Comparison of primitives and pictures between two
149 /// display lists is (a) fast (b) done during scene building.
150 #[cfg_attr(feature = "capture", derive(Serialize))]
151 #[cfg_attr(feature = "replay", derive(Deserialize))]
153 pub struct Interners {
155 pub $name: Interner<$ty>,
160 impl AsMut<Interner<$ty>> for Interners {
161 fn as_mut(&mut self) -> &mut Interner<$ty> {
167 pub struct InternerUpdates {
169 pub $name: UpdateList<<$ty as Internable>::Key>,
174 /// Reports CPU heap memory used by the interners.
177 ops: &mut MallocSizeOfOps,
178 r: &mut MemoryReport,
181 r.interning.interners.$name += self.$name.size_of(ops);
185 fn end_frame_and_get_pending_updates(&mut self) -> InternerUpdates {
188 $name: self.$name.end_frame_and_get_pending_updates(),
196 crate::enumerate_interners!(declare_interners);
198 // A document in the scene builder contains the current scene,
199 // as well as a persistent clip interner. This allows clips
200 // to be de-duplicated, and persisted in the GPU cache between
204 interners: Interners,
210 fn new(device_rect: DeviceIntRect, device_pixel_ratio: f32) -> Self {
213 interners: Interners::default(),
214 stats: SceneStats::empty(),
218 page_zoom_factor: 1.0,
219 quality_settings: QualitySettings::default(),
225 pub struct SceneBuilderThread {
226 documents: FastHashMap<DocumentId, Document>,
227 rx: Receiver<SceneBuilderRequest>,
229 config: FrameBuilderConfig,
230 default_device_pixel_ratio: f32,
231 font_instances: SharedFontInstanceMap,
232 size_of_ops: Option<MallocSizeOfOps>,
233 hooks: Option<Box<dyn SceneBuilderHooks + Send>>,
234 simulate_slow_ms: u32,
235 removed_pipelines: FastHashSet<PipelineId>,
236 #[cfg(feature = "capture")]
237 capture_config: Option<CaptureConfig>,
240 pub struct SceneBuilderThreadChannels {
241 rx: Receiver<SceneBuilderRequest>,
245 impl SceneBuilderThreadChannels {
248 ) -> (Self, Sender<SceneBuilderRequest>) {
249 let (in_tx, in_rx) = unbounded_channel();
260 impl SceneBuilderThread {
262 config: FrameBuilderConfig,
263 default_device_pixel_ratio: f32,
264 font_instances: SharedFontInstanceMap,
265 size_of_ops: Option<MallocSizeOfOps>,
266 hooks: Option<Box<dyn SceneBuilderHooks + Send>>,
267 channels: SceneBuilderThreadChannels,
269 let SceneBuilderThreadChannels { rx, tx } = channels;
272 documents: Default::default(),
276 default_device_pixel_ratio,
281 removed_pipelines: FastHashSet::default(),
282 #[cfg(feature = "capture")]
283 capture_config: None,
287 /// Send a message to the render backend thread.
289 /// We first put something in the result queue and then send a wake-up
290 /// message to the api queue that the render backend is blocking on.
291 pub fn send(&self, msg: SceneBuilderResult) {
292 self.tx.send(ApiMsg::SceneBuilderResult(msg)).unwrap();
295 /// The scene builder thread's event loop.
296 pub fn run(&mut self) {
297 if let Some(ref hooks) = self.hooks {
302 tracy_begin_frame!("scene_builder_thread");
304 match self.rx.recv() {
305 Ok(SceneBuilderRequest::WakeUp) => {}
306 Ok(SceneBuilderRequest::Flush(tx)) => {
307 self.send(SceneBuilderResult::FlushComplete(tx));
309 Ok(SceneBuilderRequest::Transactions(txns)) => {
310 let built_txns : Vec<Box<BuiltTransaction>> = txns.into_iter()
311 .map(|txn| self.process_transaction(*txn))
313 #[cfg(feature = "capture")]
314 match built_txns.iter().any(|txn| txn.built_scene.is_some()) {
315 true => self.save_capture_sequence(),
318 self.forward_built_transactions(built_txns);
320 Ok(SceneBuilderRequest::AddDocument(document_id, initial_size)) => {
321 let old = self.documents.insert(document_id, Document::new(
323 self.default_device_pixel_ratio,
325 debug_assert!(old.is_none());
327 Ok(SceneBuilderRequest::DeleteDocument(document_id)) => {
328 self.documents.remove(&document_id);
329 self.send(SceneBuilderResult::DeleteDocument(document_id));
331 Ok(SceneBuilderRequest::ClearNamespace(id)) => {
332 self.documents.retain(|doc_id, _doc| doc_id.namespace_id != id);
333 self.send(SceneBuilderResult::ClearNamespace(id));
335 Ok(SceneBuilderRequest::ExternalEvent(evt)) => {
336 self.send(SceneBuilderResult::ExternalEvent(evt));
338 Ok(SceneBuilderRequest::GetGlyphDimensions(request)) => {
339 self.send(SceneBuilderResult::GetGlyphDimensions(request));
341 Ok(SceneBuilderRequest::GetGlyphIndices(request)) => {
342 self.send(SceneBuilderResult::GetGlyphIndices(request));
344 Ok(SceneBuilderRequest::StopRenderBackend) => {
345 self.send(SceneBuilderResult::StopRenderBackend);
347 Ok(SceneBuilderRequest::ShutDown(sync)) => {
348 self.send(SceneBuilderResult::ShutDown(sync));
351 Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => {
352 self.simulate_slow_ms = time_ms
354 Ok(SceneBuilderRequest::ReportMemory(mut report, tx)) => {
355 (*report) += self.report_memory();
356 tx.send(report).unwrap();
358 Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => {
361 #[cfg(feature = "replay")]
362 Ok(SceneBuilderRequest::LoadScenes(msg)) => {
363 self.load_scenes(msg);
365 #[cfg(feature = "capture")]
366 Ok(SceneBuilderRequest::SaveScene(config)) => {
367 self.save_scene(config);
369 #[cfg(feature = "capture")]
370 Ok(SceneBuilderRequest::StartCaptureSequence(config)) => {
371 self.start_capture_sequence(config);
373 #[cfg(feature = "capture")]
374 Ok(SceneBuilderRequest::StopCaptureSequence) => {
375 // FIXME(aosmond): clear config for frames and resource cache without scene
377 self.capture_config = None;
378 self.send(SceneBuilderResult::StopCaptureSequence);
385 if let Some(ref hooks) = self.hooks {
389 tracy_end_frame!("scene_builder_thread");
392 if let Some(ref hooks) = self.hooks {
397 #[cfg(feature = "capture")]
398 fn save_scene(&mut self, config: CaptureConfig) {
399 for (id, doc) in &self.documents {
400 let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id);
401 config.serialize_for_scene(&doc.interners, interners_name);
403 use crate::render_api::CaptureBits;
404 if config.bits.contains(CaptureBits::SCENE) {
405 let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
406 config.serialize_for_scene(&doc.scene, file_name);
411 #[cfg(feature = "replay")]
412 fn load_scenes(&mut self, scenes: Vec<LoadScene>) {
413 for mut item in scenes {
414 self.config = item.config;
416 let mut built_scene = None;
417 let mut interner_updates = None;
419 if item.scene.has_root_pipeline() {
420 built_scene = Some(SceneBuilder::build(
426 &SceneStats::empty(),
429 interner_updates = Some(
430 item.interners.end_frame_and_get_pending_updates()
434 self.documents.insert(
438 interners: item.interners,
439 stats: SceneStats::empty(),
440 view: item.view.clone(),
444 let txns = vec![Box::new(BuiltTransaction {
445 document_id: item.document_id,
446 render_frame: item.build_frame,
447 invalidate_rendered_frame: false,
450 resource_updates: Vec::new(),
451 rasterized_blobs: Vec::new(),
452 blob_rasterizer: None,
453 frame_ops: Vec::new(),
454 removed_pipelines: Vec::new(),
455 discard_frame_state_for_pipelines: Vec::new(),
456 notifications: Vec::new(),
458 profile: TransactionProfile::new(),
459 frame_stats: FullFrameStats::default(),
462 self.forward_built_transactions(txns);
466 #[cfg(feature = "capture")]
467 fn save_capture_sequence(
470 if let Some(ref mut config) = self.capture_config {
471 config.prepare_scene();
472 for (id, doc) in &self.documents {
473 let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id);
474 config.serialize_for_scene(&doc.interners, interners_name);
476 use crate::render_api::CaptureBits;
477 if config.bits.contains(CaptureBits::SCENE) {
478 let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
479 config.serialize_for_scene(&doc.scene, file_name);
485 #[cfg(feature = "capture")]
486 fn start_capture_sequence(
488 config: CaptureConfig,
490 self.capture_config = Some(config);
491 self.save_capture_sequence();
494 /// Do the bulk of the work of the scene builder thread.
495 fn process_transaction(&mut self, mut txn: TransactionMsg) -> Box<BuiltTransaction> {
496 profile_scope!("process_transaction");
498 if let Some(ref hooks) = self.hooks {
499 hooks.pre_scene_build();
502 let doc = self.documents.get_mut(&txn.document_id).unwrap();
503 let scene = &mut doc.scene;
505 let mut profile = txn.profile.take();
507 let scene_build_start = precise_time_ns();
508 let mut discard_frame_state_for_pipelines = Vec::new();
509 let mut removed_pipelines = Vec::new();
510 let mut rebuild_scene = false;
511 let mut frame_stats = FullFrameStats::default();
513 for message in txn.scene_ops.drain(..) {
515 SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
516 scene.update_epoch(pipeline_id, epoch);
518 SceneMsg::SetPageZoom(factor) => {
519 doc.view.page_zoom_factor = factor.get();
521 SceneMsg::SetQualitySettings { settings } => {
522 doc.view.quality_settings = settings;
524 SceneMsg::SetDocumentView { device_rect, device_pixel_ratio } => {
525 doc.view.device_rect = device_rect;
526 doc.view.device_pixel_ratio = device_pixel_ratio;
528 SceneMsg::SetDisplayList {
534 preserve_frame_state,
536 let (builder_start_time_ns, builder_end_time_ns, send_time_ns) =
537 display_list.times();
538 let content_send_time = profiler::ns_to_ms(precise_time_ns() - send_time_ns);
539 let dl_build_time = profiler::ns_to_ms(builder_end_time_ns - builder_start_time_ns);
540 profile.set(profiler::CONTENT_SEND_TIME, content_send_time);
541 profile.set(profiler::DISPLAY_LIST_BUILD_TIME, dl_build_time);
542 profile.set(profiler::DISPLAY_LIST_MEM, profiler::bytes_to_mb(display_list.data().len()));
544 let (gecko_display_list_time, full_display_list) = display_list.gecko_display_list_stats();
545 frame_stats.full_display_list = full_display_list;
546 frame_stats.gecko_display_list_time = gecko_display_list_time;
547 frame_stats.wr_display_list_time += dl_build_time;
549 if self.removed_pipelines.contains(&pipeline_id) {
553 // Note: We could further reduce the amount of unnecessary scene
554 // building by keeping track of which pipelines are used by the
555 // scene (bug 1490751).
556 rebuild_scene = true;
558 scene.set_display_list(
566 if !preserve_frame_state {
567 discard_frame_state_for_pipelines.push(pipeline_id);
570 SceneMsg::SetRootPipeline(pipeline_id) => {
571 if scene.root_pipeline_id != Some(pipeline_id) {
572 rebuild_scene = true;
573 scene.set_root_pipeline_id(pipeline_id);
576 SceneMsg::RemovePipeline(pipeline_id) => {
577 scene.remove_pipeline(pipeline_id);
578 self.removed_pipelines.insert(pipeline_id);
579 removed_pipelines.push((pipeline_id, txn.document_id));
584 self.removed_pipelines.clear();
586 let mut built_scene = None;
587 let mut interner_updates = None;
588 if scene.has_root_pipeline() && rebuild_scene {
590 let built = SceneBuilder::build(
592 self.font_instances.clone(),
599 // Update the allocation stats for next scene
600 doc.stats = built.get_stats();
602 // Retrieve the list of updates from the clip interner.
603 interner_updates = Some(
604 doc.interners.end_frame_and_get_pending_updates()
607 built_scene = Some(built);
610 let scene_build_time_ms =
611 profiler::ns_to_ms(precise_time_ns() - scene_build_start);
612 profile.set(profiler::SCENE_BUILD_TIME, scene_build_time_ms);
614 frame_stats.scene_build_time += scene_build_time_ms;
616 if !txn.blob_requests.is_empty() {
617 profile.start_time(profiler::BLOB_RASTERIZATION_TIME);
619 let is_low_priority = false;
620 rasterize_blobs(&mut txn, is_low_priority);
622 profile.end_time(profiler::BLOB_RASTERIZATION_TIME);
626 &mut txn.notifications,
627 |n| { n.when() == Checkpoint::SceneBuilt },
631 if self.simulate_slow_ms > 0 {
632 thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
635 Box::new(BuiltTransaction {
636 document_id: txn.document_id,
637 render_frame: txn.generate_frame.as_bool(),
638 invalidate_rendered_frame: txn.invalidate_rendered_frame,
641 rasterized_blobs: txn.rasterized_blobs,
642 resource_updates: txn.resource_updates,
643 blob_rasterizer: txn.blob_rasterizer,
644 frame_ops: txn.frame_ops,
646 discard_frame_state_for_pipelines,
647 notifications: txn.notifications,
654 /// Send the results of process_transaction back to the render backend.
655 fn forward_built_transactions(&mut self, txns: Vec<Box<BuiltTransaction>>) {
656 let (pipeline_info, result_tx, result_rx) = match self.hooks {
658 if txns.iter().any(|txn| txn.built_scene.is_some()) {
659 let info = PipelineInfo {
661 .filter(|txn| txn.built_scene.is_some())
663 txn.built_scene.as_ref().unwrap()
664 .pipeline_epochs.iter()
665 .zip(iter::repeat(txn.document_id))
666 .map(|((&pipeline_id, &epoch), document_id)| ((pipeline_id, document_id), epoch))
667 }).flatten().collect(),
668 removed_pipelines: txns.iter()
669 .map(|txn| txn.removed_pipelines.clone())
670 .flatten().collect(),
673 let (tx, rx) = single_msg_channel();
674 let txn = txns.iter().find(|txn| txn.built_scene.is_some()).unwrap();
675 hooks.pre_scene_swap((txn.profile.get(profiler::SCENE_BUILD_TIME).unwrap() * 1000000.0) as u64);
677 (Some(info), Some(tx), Some(rx))
682 _ => (None, None, None)
685 let scene_swap_start_time = precise_time_ns();
686 let document_ids = txns.iter().map(|txn| txn.document_id).collect();
687 let have_resources_updates : Vec<DocumentId> = if pipeline_info.is_none() {
689 .filter(|txn| !txn.resource_updates.is_empty() || txn.invalidate_rendered_frame)
690 .map(|txn| txn.document_id)
696 #[cfg(feature = "capture")]
697 match self.capture_config {
698 Some(ref config) => self.send(SceneBuilderResult::CapturedTransactions(txns, config.clone(), result_tx)),
699 None => self.send(SceneBuilderResult::Transactions(txns, result_tx)),
702 #[cfg(not(feature = "capture"))]
703 self.send(SceneBuilderResult::Transactions(txns, result_tx));
705 if let Some(pipeline_info) = pipeline_info {
706 // Block until the swap is done, then invoke the hook.
707 let swap_result = result_rx.unwrap().recv();
708 let scene_swap_time = precise_time_ns() - scene_swap_start_time;
709 self.hooks.as_ref().unwrap().post_scene_swap(&document_ids,
710 pipeline_info, scene_swap_time);
711 // Once the hook is done, allow the RB thread to resume
712 if let Ok(SceneSwapResult::Complete(resume_tx)) = swap_result {
713 resume_tx.send(()).ok();
715 } else if !have_resources_updates.is_empty() {
716 if let Some(ref hooks) = self.hooks {
717 hooks.post_resource_update(&have_resources_updates);
719 } else if let Some(ref hooks) = self.hooks {
720 hooks.post_empty_scene_build();
724 /// Reports CPU heap memory used by the SceneBuilder.
725 fn report_memory(&mut self) -> MemoryReport {
726 let ops = self.size_of_ops.as_mut().unwrap();
727 let mut report = MemoryReport::default();
728 for doc in self.documents.values() {
729 doc.interners.report_memory(ops, &mut report);
730 doc.scene.report_memory(ops, &mut report);
737 /// A scene builder thread which executes expensive operations such as blob rasterization
738 /// with a lower priority than the normal scene builder thread.
740 /// After rasterizing blobs, the secene building request is forwarded to the normal scene
741 /// builder where the FrameBuilder is generated.
742 pub struct LowPrioritySceneBuilderThread {
743 pub rx: Receiver<SceneBuilderRequest>,
744 pub tx: Sender<SceneBuilderRequest>,
747 impl LowPrioritySceneBuilderThread {
748 pub fn run(&mut self) {
750 match self.rx.recv() {
751 Ok(SceneBuilderRequest::Transactions(mut txns)) => {
752 let txns : Vec<Box<TransactionMsg>> = txns.drain(..)
753 .map(|txn| self.process_transaction(txn))
755 self.tx.send(SceneBuilderRequest::Transactions(txns)).unwrap();
757 Ok(SceneBuilderRequest::ShutDown(sync)) => {
758 self.tx.send(SceneBuilderRequest::ShutDown(sync)).unwrap();
762 self.tx.send(other).unwrap();
771 fn process_transaction(&mut self, mut txn: Box<TransactionMsg>) -> Box<TransactionMsg> {
772 let is_low_priority = true;
773 rasterize_blobs(&mut txn, is_low_priority);
774 txn.blob_requests = Vec::new();