Bug 1652750 - Unroll WR scene building recursion r=gw
authorDzmitry Malyshau <dmalyshau@mozilla.com>
Fri, 31 Jul 2020 12:25:51 +0000
changeset 542891 3d9324ca6510ad601f203af9b4063e4bd2ec3676
parent 542890 88b60065bd1e6e19f38e1d5a2e1430a645f2d8ff
child 542892 eec8614e56fa1010f651b7708407dce115f3f718
push id37657
push usernerli@mozilla.com
push dateSat, 01 Aug 2020 09:48:10 +0000
treeherdermozilla-central@750bc4c5c4ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1652750
milestone81.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1652750 - Unroll WR scene building recursion r=gw instead of using the stack for all the scene building containers, we are making the stack explicitly, and doing all the work inside the `build_all` function. This affects reference frames, iframes, and stacking contexts. Differential Revision: https://phabricator.services.mozilla.com/D85467
gfx/wr/webrender/src/scene_building.rs
--- a/gfx/wr/webrender/src/scene_building.rs
+++ b/gfx/wr/webrender/src/scene_building.rs
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, PrimitiveFlags};
 use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
 use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData, SharedFontInstanceMap};
 use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags};
-use api::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
-use api::{Shadow, SpaceAndClipInfo, SpatialId, StackingContext, StickyFrameDisplayItem, ImageMask};
+use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
+use api::{Shadow, SpaceAndClipInfo, SpatialId, StickyFrameDisplayItem, ImageMask};
 use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
 use api::{ReferenceTransformBinding};
 use api::image_tiling::simplify_repeated_primitive;
 use api::units::*;
 use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemKeyKind};
 use crate::clip::{ClipInternData, ClipNodeKind, ClipInstance};
 use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex};
 use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
@@ -31,17 +31,17 @@ use crate::prim_store::backdrop::Backdro
 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
 use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, ConicGradientParams};
 use crate::prim_store::image::{Image, YuvImage};
 use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey, get_line_decoration_size};
 use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
 use crate::prim_store::text_run::TextRun;
 use crate::render_backend::SceneView;
 use crate::resource_cache::ImageRequest;
-use crate::scene::{Scene, BuiltScene, SceneStats, StackingContextHelpers};
+use crate::scene::{Scene, ScenePipeline, BuiltScene, SceneStats, StackingContextHelpers};
 use crate::scene_builder_thread::Interners;
 use crate::space::SpaceSnapper;
 use crate::spatial_node::{StickyFrameInfo, ScrollFrameKind};
 use crate::tile_cache::TileCacheBuilder;
 use euclid::approxeq::ApproxEq;
 use std::{f32, mem, usize, ops};
 use std::collections::vec_deque::VecDeque;
 use std::sync::Arc;
@@ -259,17 +259,16 @@ pub struct SceneBuilder<'a> {
     /// Helper struct to map stacking context coords <-> reference frame coords.
     rf_mapper: ReferenceFrameMapper,
 
     /// Helper struct to map spatial nodes to external scroll offsets.
     external_scroll_mapper: ScrollOffsetMapper,
 
     /// The current recursion depth of iframes encountered. Used to restrict picture
     /// caching slices to only the top-level content frame.
-    iframe_depth: usize,
     iframe_size: Vec<LayoutSize>,
 
     /// The current quality / performance settings for this scene.
     quality_settings: QualitySettings,
 
     /// Maintains state about the list of tile caches being built for this scene.
     tile_cache_builder: TileCacheBuilder,
 
@@ -319,48 +318,23 @@ impl<'a> SceneBuilder<'a> {
             hit_testing_scene: HitTestingScene::new(&stats.hit_test_stats),
             pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             prim_store: PrimitiveStore::new(&stats.prim_store_stats),
             clip_store: ClipStore::new(),
             interners,
             rf_mapper: ReferenceFrameMapper::new(),
             external_scroll_mapper: ScrollOffsetMapper::new(),
-            iframe_depth: 0,
             iframe_size: Vec::new(),
             quality_settings: view.quality_settings,
             tile_cache_builder: TileCacheBuilder::new(),
             snap_to_device,
         };
 
-        builder.clip_store.register_clip_template(
-            ClipId::root(root_pipeline_id),
-            ClipId::root(root_pipeline_id),
-            &[],
-        );
-
-        builder.clip_store.push_clip_root(
-            Some(ClipId::root(root_pipeline_id)),
-            false,
-        );
-
-        builder.push_root(
-            root_pipeline_id,
-            &root_pipeline.viewport_size,
-            &root_pipeline.content_size,
-        );
-
-        builder.build_items(
-            &mut root_pipeline.display_list.iter(),
-            root_pipeline.pipeline_id,
-        );
-
-        builder.clip_store.pop_clip_root();
-
-        debug_assert!(builder.sc_stack.is_empty());
+        builder.build_all(&root_pipeline);
 
         // Construct the picture cache primitive instance(s) from the tile cache builder
         let (tile_cache_config, prim_list) = builder.tile_cache_builder.build(
             &builder.config,
             &mut builder.interners,
             &mut builder.clip_store,
             &mut builder.prim_store,
         );
@@ -412,86 +386,234 @@ impl<'a> SceneBuilder<'a> {
             .external_scroll_offset(
                 spatial_node_index,
                 &self.spatial_tree,
             );
 
         rf_offset + scroll_offset
     }
 
-    fn build_items(
-        &mut self,
-        traversal: &mut BuiltDisplayListIter<'a>,
-        pipeline_id: PipelineId,
-    ) {
-        loop {
-            let item = match traversal.next() {
-                Some(item) => item,
-                None => break,
-            };
-
-            let subtraversal = match item.item() {
-                DisplayItem::PushStackingContext(ref info) => {
-                    let space = self.get_space(info.spatial_id);
-                    let mut subtraversal = item.sub_iter();
-                    self.build_stacking_context(
-                        &mut subtraversal,
-                        pipeline_id,
-                        &info.stacking_context,
-                        space,
-                        info.origin,
-                        item.filters(),
-                        &item.filter_datas(),
-                        item.filter_primitives(),
-                        info.prim_flags,
-                    );
-                    Some(subtraversal)
-                }
-                DisplayItem::PushReferenceFrame(ref info) => {
-                    let parent_space = self.get_space(info.parent_spatial_id);
-                    let mut subtraversal = item.sub_iter();
-                    self.build_reference_frame(
-                        &mut subtraversal,
-                        pipeline_id,
-                        parent_space,
-                        info.origin,
-                        &info.reference_frame,
-                    );
-                    Some(subtraversal)
-                }
-                DisplayItem::PopReferenceFrame |
-                DisplayItem::PopStackingContext => return,
-                _ => None,
-            };
-
-            // If build_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(mut subtraversal) = subtraversal {
-                subtraversal.merge_debug_stats_from(traversal);
-                *traversal = subtraversal;
-            } else {
-                self.build_item(item, pipeline_id);
+    fn build_all(&mut self, root_pipeline: &ScenePipeline) {
+        enum ContextKind<'a> {
+            Root,
+            StackingContext {
+                is_useful: bool,
+                has_clip: bool,
+            },
+            ReferenceFrame,
+            Iframe {
+                parent_traversal: BuiltDisplayListIter<'a>,
             }
         }
-
-        // TODO: factor this out to be part of capture
-        if cfg!(feature = "display_list_stats") {
-            let stats = traversal.debug_stats();
-            let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
-            println!("item, total count, total bytes, % of DL bytes, bytes per item");
-            for (label, stats) in stats {
-                println!("{}, {}, {}kb, {}%, {}",
-                    label,
-                    stats.total_count,
-                    stats.num_bytes / 1000,
-                    ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
-                    stats.num_bytes / stats.total_count.max(1));
+        struct BuildContext<'a> {
+            pipeline_id: PipelineId,
+            kind: ContextKind<'a>,
+        }
+
+        let root_clip_id = ClipId::root(root_pipeline.pipeline_id);
+        self.clip_store.register_clip_template(root_clip_id, root_clip_id, &[]);
+        self.clip_store.push_clip_root(Some(root_clip_id), false);
+        self.push_root(
+            root_pipeline.pipeline_id,
+            &root_pipeline.viewport_size,
+            &root_pipeline.content_size,
+        );
+
+        let mut stack = vec![BuildContext {
+            pipeline_id: root_pipeline.pipeline_id,
+            kind: ContextKind::Root,
+        }];
+        let mut traversal = root_pipeline.display_list.iter();
+
+        'outer: while let Some(bc) = stack.pop() {
+            loop {
+                let item = match traversal.next() {
+                    Some(item) => item,
+                    None => break,
+                };
+
+                match item.item() {
+                    DisplayItem::PushStackingContext(ref info) => {
+                        profile_scope!("build_stacking_context");
+                        let spatial_node_index = self.get_space(info.spatial_id);
+                        let mut subtraversal = item.sub_iter();
+                        // Avoid doing unnecessary work for empty stacking contexts.
+                        if subtraversal.current_stacking_context_empty() {
+                            subtraversal.skip_current_stacking_context();
+                            traversal = subtraversal;
+                            continue;
+                        }
+
+                        let composition_operations = CompositeOps::new(
+                            filter_ops_for_compositing(item.filters()),
+                            filter_datas_for_compositing(item.filter_datas()),
+                            filter_primitives_for_compositing(item.filter_primitives()),
+                            info.stacking_context.mix_blend_mode_for_compositing(),
+                        );
+
+                        let is_useful = self.push_stacking_context(
+                            bc.pipeline_id,
+                            composition_operations,
+                            info.stacking_context.transform_style,
+                            info.prim_flags,
+                            spatial_node_index,
+                            info.stacking_context.clip_id,
+                            info.stacking_context.raster_space,
+                            info.stacking_context.flags,
+                        );
+
+                        self.rf_mapper.push_offset(info.origin.to_vector());
+                        let new_context = BuildContext {
+                            pipeline_id: bc.pipeline_id,
+                            kind: ContextKind::StackingContext {
+                                is_useful,
+                                has_clip: info.stacking_context.clip_id.is_some(),
+                            },
+                        };
+                        stack.push(bc);
+                        stack.push(new_context);
+
+                        subtraversal.merge_debug_stats_from(&mut traversal);
+                        traversal = subtraversal;
+                        continue 'outer;
+                    }
+                    DisplayItem::PushReferenceFrame(ref info) => {
+                        profile_scope!("build_reference_frame");
+                        let parent_space = self.get_space(info.parent_spatial_id);
+                        let mut subtraversal = item.sub_iter();
+                        let current_offset = self.current_offset(parent_space);
+
+                        let transform = match info.reference_frame.transform {
+                            ReferenceTransformBinding::Static { binding } => binding,
+                            ReferenceTransformBinding::Computed { scale_from, vertical_flip } => {
+                                let mut transform = if let Some(scale_from) = scale_from {
+                                    let content_size = &self.iframe_size.last().unwrap();
+                                    LayoutTransform::create_scale(
+                                        content_size.width / scale_from.width,
+                                        content_size.height / scale_from.height,
+                                        1.0
+                                    )
+                                } else {
+                                    LayoutTransform::identity()
+                                };
+
+                                if vertical_flip {
+                                    let content_size = &self.iframe_size.last().unwrap();
+                                    transform = transform
+                                        .post_translate(LayoutVector3D::new(0.0, content_size.height, 0.0))
+                                        .pre_scale(1.0, -1.0, 1.0);
+                                }
+
+                                PropertyBinding::Value(transform)
+                            },
+                        };
+
+                        self.push_reference_frame(
+                            info.reference_frame.id,
+                            Some(parent_space),
+                            bc.pipeline_id,
+                            info.reference_frame.transform_style,
+                            transform,
+                            info.reference_frame.kind,
+                            current_offset + info.origin.to_vector(),
+                        );
+
+                        self.rf_mapper.push_scope();
+                        let new_context = BuildContext {
+                            pipeline_id: bc.pipeline_id,
+                            kind: ContextKind::ReferenceFrame,
+                        };
+                        stack.push(bc);
+                        stack.push(new_context);
+
+                        subtraversal.merge_debug_stats_from(&mut traversal);
+                        traversal = subtraversal;
+                        continue 'outer;
+                    }
+                    DisplayItem::PopReferenceFrame |
+                    DisplayItem::PopStackingContext => break,
+                    DisplayItem::Iframe(ref info) => {
+                        profile_scope!("iframe");
+
+                        let space = self.get_space(info.space_and_clip.spatial_id);
+                        let (size, subtraversal) = match self.push_iframe(info, space) {
+                            Some(pair) => pair,
+                            None => continue,
+                        };
+
+                        // If this is a root iframe, force a new tile cache both before and after
+                        // adding primitives for this iframe.
+                        if self.iframe_size.is_empty() {
+                            self.tile_cache_builder.add_tile_cache_barrier();
+                        }
+
+                        self.rf_mapper.push_scope();
+                        self.iframe_size.push(size);
+
+                        let new_context = BuildContext {
+                            pipeline_id: info.pipeline_id,
+                            kind: ContextKind::Iframe {
+                                parent_traversal: mem::replace(&mut traversal, subtraversal),
+                            },
+                        };
+                        stack.push(bc);
+                        stack.push(new_context);
+                        continue 'outer;
+                    }
+                    _ => {
+                        self.build_item(item, bc.pipeline_id);
+                    }
+                };
             }
-            println!();
+
+            match bc.kind {
+                ContextKind::Root => {}
+                ContextKind::StackingContext { is_useful, has_clip } => {
+                    self.rf_mapper.pop_offset();
+                    if is_useful {
+                        self.pop_stacking_context();
+                    } else if has_clip {
+                        self.clip_store.pop_clip_root();
+                    }
+                }
+                ContextKind::ReferenceFrame => {
+                    self.rf_mapper.pop_scope();
+                }
+                ContextKind::Iframe { parent_traversal } => {
+                    self.iframe_size.pop();
+                    self.rf_mapper.pop_scope();
+
+                    self.clip_store.pop_clip_root();
+                    if self.iframe_size.is_empty() {
+                        self.tile_cache_builder.add_tile_cache_barrier();
+                    }
+
+                    traversal = parent_traversal;
+                }
+            }
+
+            // TODO: factor this out to be part of capture
+            if cfg!(feature = "display_list_stats") {
+                let stats = traversal.debug_stats();
+                let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
+                println!("item, total count, total bytes, % of DL bytes, bytes per item");
+                for (label, stats) in stats {
+                    println!("{}, {}, {}kb, {}%, {}",
+                        label,
+                        stats.total_count,
+                        stats.num_bytes / 1000,
+                        ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
+                        stats.num_bytes / stats.total_count.max(1));
+                }
+                println!();
+            }
         }
+
+        self.clip_store.pop_clip_root();
+        debug_assert!(self.sc_stack.is_empty());
     }
 
     fn build_sticky_frame(
         &mut self,
         info: &StickyFrameDisplayItem,
         parent_node_index: SpatialNodeIndex,
     ) {
         let current_offset = self.current_offset(parent_node_index);
@@ -540,134 +662,27 @@ impl<'a> SceneBuilder<'a> {
             &frame_rect,
             &content_size,
             info.scroll_sensitivity,
             ScrollFrameKind::Explicit,
             info.external_scroll_offset,
         );
     }
 
-    fn build_reference_frame(
-        &mut self,
-        traversal: &mut BuiltDisplayListIter<'a>,
-        pipeline_id: PipelineId,
-        parent_spatial_node: SpatialNodeIndex,
-        origin: LayoutPoint,
-        reference_frame: &ReferenceFrame,
-    ) {
-        profile_scope!("build_reference_frame");
-        let current_offset = self.current_offset(parent_spatial_node);
-
-        let transform = match reference_frame.transform {
-            ReferenceTransformBinding::Static { binding } => binding,
-            ReferenceTransformBinding::Computed { scale_from, vertical_flip } => {
-                let mut transform = if let Some(scale_from) = scale_from {
-                    let content_size = &self.iframe_size.last().unwrap();
-                    LayoutTransform::create_scale(
-                        content_size.width / scale_from.width,
-                        content_size.height / scale_from.height,
-                        1.0
-                    )
-                } else {
-                    LayoutTransform::identity()
-                };
-
-                if vertical_flip {
-                    let content_size = &self.iframe_size.last().unwrap();
-                    transform = transform
-                        .post_translate(LayoutVector3D::new(0.0, content_size.height, 0.0))
-                        .pre_scale(1.0, -1.0, 1.0);
-                }
-
-                PropertyBinding::Value(transform)
-            },
-        };
-
-        self.push_reference_frame(
-            reference_frame.id,
-            Some(parent_spatial_node),
-            pipeline_id,
-            reference_frame.transform_style,
-            transform,
-            reference_frame.kind,
-            current_offset + origin.to_vector(),
-        );
-
-        self.rf_mapper.push_scope();
-        self.build_items(
-            traversal,
-            pipeline_id,
-        );
-        self.rf_mapper.pop_scope();
-    }
-
-    fn build_stacking_context(
-        &mut self,
-        traversal: &mut BuiltDisplayListIter<'a>,
-        pipeline_id: PipelineId,
-        stacking_context: &StackingContext,
-        spatial_node_index: SpatialNodeIndex,
-        origin: LayoutPoint,
-        filters: ItemRange<FilterOp>,
-        filter_datas: &[TempFilterData],
-        filter_primitives: ItemRange<FilterPrimitive>,
-        prim_flags: PrimitiveFlags,
-    ) {
-        profile_scope!("build_stacking_context");
-        // Avoid doing unnecessary work for empty stacking contexts.
-        if traversal.current_stacking_context_empty() {
-            traversal.skip_current_stacking_context();
-            return;
-        }
-
-        let composition_operations = {
-            CompositeOps::new(
-                filter_ops_for_compositing(filters),
-                filter_datas_for_compositing(filter_datas),
-                filter_primitives_for_compositing(filter_primitives),
-                stacking_context.mix_blend_mode_for_compositing(),
-            )
-        };
-
-        let is_useful = self.push_stacking_context(
-            pipeline_id,
-            composition_operations,
-            stacking_context.transform_style,
-            prim_flags,
-            spatial_node_index,
-            stacking_context.clip_id,
-            stacking_context.raster_space,
-            stacking_context.flags,
-        );
-
-        self.rf_mapper.push_offset(origin.to_vector());
-        self.build_items(
-            traversal,
-            pipeline_id,
-        );
-        self.rf_mapper.pop_offset();
-
-        if is_useful {
-            self.pop_stacking_context();
-        } else if stacking_context.clip_id.is_some() {
-            self.clip_store.pop_clip_root();
-        }
-    }
-
-    fn build_iframe(
+    fn push_iframe(
         &mut self,
         info: &IframeDisplayItem,
         spatial_node_index: SpatialNodeIndex,
-    ) {
+    ) -> Option<(LayoutSize, BuiltDisplayListIter<'a>)> {
         let iframe_pipeline_id = info.pipeline_id;
         let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
             Some(pipeline) => pipeline,
             None => {
                 debug_assert!(info.ignore_missing_pipeline);
-                return
+                return None
             },
         };
 
         let current_offset = self.current_offset(spatial_node_index);
         self.add_clip_node(
             ClipId::root(iframe_pipeline_id),
             &info.space_and_clip,
             ClipRegion::create_for_clip_node_with_local_clip(
@@ -708,40 +723,17 @@ impl<'a> SceneBuilder<'a> {
             iframe_pipeline_id,
             &iframe_rect,
             &content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             ScrollFrameKind::PipelineRoot,
             LayoutVector2D::zero(),
         );
 
-        // If this is a root iframe, force a new tile cache both before and after
-        // adding primitives for this iframe.
-
-        let is_root_iframe = self.iframe_depth == 0;
-        if is_root_iframe {
-            self.tile_cache_builder.add_tile_cache_barrier();
-        }
-
-        self.rf_mapper.push_scope();
-        self.iframe_depth += 1;
-        self.iframe_size.push(bounds.size);
-
-        self.build_items(
-            &mut pipeline.display_list.iter(),
-            pipeline.pipeline_id,
-        );
-        self.iframe_size.pop();
-        self.iframe_depth -= 1;
-        self.rf_mapper.pop_scope();
-
-        self.clip_store.pop_clip_root();
-        if is_root_iframe {
-            self.tile_cache_builder.add_tile_cache_barrier();
-        }
+        Some((bounds.size, pipeline.display_list.iter()))
     }
 
     fn get_space(
         &self,
         spatial_id: SpatialId,
     ) -> SpatialNodeIndex {
         self.id_to_index_mapper.get_spatial_node_index(spatial_id)
     }
@@ -1117,25 +1109,16 @@ impl<'a> SceneBuilder<'a> {
                 self.add_border(
                     spatial_node_index,
                     clip_chain_id,
                     &layout,
                     info,
                     item.gradient_stops(),
                 );
             }
-            DisplayItem::Iframe(ref info) => {
-                profile_scope!("iframe");
-
-                let space = self.get_space(info.space_and_clip.spatial_id);
-                self.build_iframe(
-                    info,
-                    space,
-                );
-            }
             DisplayItem::ImageMaskClip(ref info) => {
                 profile_scope!("image_clip");
 
                 let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
                 let current_offset = self.current_offset(parent_space);
 
                 let image_mask = ImageMask {
                     rect: info.image_mask.rect.translate(current_offset),
@@ -1249,18 +1232,19 @@ impl<'a> SceneBuilder<'a> {
             DisplayItem::SetFilterOps |
             DisplayItem::SetFilterData |
             DisplayItem::SetFilterPrimitives => {}
 
             // Special items that are handled in the parent method
             DisplayItem::PushStackingContext(..) |
             DisplayItem::PushReferenceFrame(..) |
             DisplayItem::PopReferenceFrame |
-            DisplayItem::PopStackingContext => {
-                unreachable!("Should have returned in parent method.")
+            DisplayItem::PopStackingContext |
+            DisplayItem::Iframe(_) => {
+                unreachable!("Handled in `build_all`")
             }
 
             DisplayItem::ReuseItems(key) |
             DisplayItem::RetainedItems(key) => {
                 unreachable!("Iterator logic error: {:?}", key);
             }
 
             DisplayItem::PushShadow(info) => {