Bug 1505934 - WR: Don't establish a raster root in pictures with sufficiently large local bounds (take 2) r=jrmuizel
authorDzmitry Malyshau <dmalyshau@mozilla.com>
Tue, 15 Jan 2019 15:23:28 +0000
changeset 453936 97ae7728e2432a8cbd9c682b6eebf689ae69bdcc
parent 453935 0c8548aec7059aa1f19da3a3d2ad02ef43f88622
child 453937 67ac015ae0802854815cc136baf520791588e754
push id35380
push userdluca@mozilla.com
push dateTue, 15 Jan 2019 22:13:12 +0000
treeherdermozilla-central@a51d26029042 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1505934
milestone66.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 1505934 - WR: Don't establish a raster root in pictures with sufficiently large local bounds (take 2) r=jrmuizel re-open of D16335, which got backed out due to Wrench test failing. The test was failing because of different AA on a plane-splitting case, which isn't guaranteed anyway. This revision updates the test. Differential Revision: https://phabricator.services.mozilla.com/D16560
gfx/tests/crashtests/1505934-1.html
gfx/tests/crashtests/crashtests.list
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/spatial_node.rs
gfx/wr/wrench/reftests/split/same-plane.png
new file mode 100644
--- /dev/null
+++ b/gfx/tests/crashtests/1505934-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+  body {
+      background-color: limegreen;
+  }
+  .A {
+      transform: scale(0.01) perspective(1000px);
+      transform-origin: 0% 0%;
+      filter: grayscale(40%);
+  }
+  .B {
+      background-color: black;
+      width: 10000px;
+      height: 5000px;
+      border-radius: 2000px;
+  }
+</style>
+<body>
+  <div class="A">
+      <div class = "B"/>
+  </div>
+</body>
--- a/gfx/tests/crashtests/crashtests.list
+++ b/gfx/tests/crashtests/crashtests.list
@@ -174,10 +174,11 @@ load 1490704-1.html
 load 1501518.html
 load 1503986-1.html
 load 1505426-1.html
 load 1508811.html
 load 1508822.html
 load 1509099.html
 load 1513133.html
 load 1496194.html
+load 1505934-1.html
 load 1509123.html
 load texture-allocator-zero-region.html
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -81,16 +81,27 @@ pub struct TileIndex(pub usize);
 
 /// The size in device pixels of a cached tile. The currently chosen
 /// size is arbitrary. We should do some profiling to find the best
 /// size for real world pages.
 pub const TILE_SIZE_WIDTH: i32 = 1024;
 pub const TILE_SIZE_HEIGHT: i32 = 256;
 const FRAMES_BEFORE_CACHING: usize = 2;
 
+/// The maximum size per axis of texture cache item,
+///  in WorldPixel coordinates.
+// TODO(gw): This size is quite arbitrary - we should do some
+//           profiling / telemetry to see when it makes sense
+//           to cache a picture.
+const MAX_CACHE_SIZE: f32 = 2048.0;
+/// The maximum size per axis of a surface,
+///  in WorldPixel coordinates.
+const MAX_SURFACE_SIZE: f32 = 4096.0;
+
+
 #[derive(Debug)]
 pub struct GlobalTransformInfo {
     /// Current (quantized) value of the transform, that is
     /// independent of the value of the spatial node index.
     /// Only calculated on first use.
     current: Option<TransformKey>,
     /// Tiles check this to see if the dependencies have changed.
     changed: bool,
@@ -1273,16 +1284,21 @@ impl SurfaceInfo {
             surface: None,
             raster_spatial_node_index,
             surface_spatial_node_index,
             tasks: Vec::new(),
             inflation_factor,
         }
     }
 
+    pub fn fits_surface_size_limits(&self) -> bool {
+        self.map_local_to_surface.bounds.size.width <= MAX_SURFACE_SIZE &&
+        self.map_local_to_surface.bounds.size.height <= MAX_SURFACE_SIZE
+    }
+
     /// Take the set of child render tasks for this surface. This is
     /// used when constructing the render task tree.
     pub fn take_render_tasks(&mut self) -> Vec<RenderTaskId> {
         mem::replace(&mut self.tasks, Vec::new())
     }
 }
 
 #[derive(Debug)]
@@ -1816,16 +1832,17 @@ impl PicturePrimitive {
                 Some(PlaneSplitter::new())
             }
             Picture3DContext::In { root_data: None, .. } => {
                 None
             }
         };
 
         let state = PictureState {
+            //TODO: check for MAX_CACHE_SIZE here?
             is_cacheable: true,
             map_local_to_pic,
             map_pic_to_world,
             map_pic_to_raster,
             map_raster_to_world,
             plane_splitter,
         };
 
@@ -2015,85 +2032,85 @@ impl PicturePrimitive {
             mode => mode,
         };
 
         if let Some(composite_mode) = actual_composite_mode {
             // Retrieve the positioning node information for the parent surface.
             let parent_raster_spatial_node_index = state.current_surface().raster_spatial_node_index;
             let surface_spatial_node_index = self.spatial_node_index;
 
-            // Check if there is perspective, and thus whether a new
-            // rasterization root should be established.
-            let xf = frame_context.clip_scroll_tree.get_relative_transform(
-                parent_raster_spatial_node_index,
-                surface_spatial_node_index,
-            ).expect("BUG: unable to get relative transform");
-
-            // TODO(gw): A temporary hack here to revert behavior to
-            //           always raster in screen-space. This is not
-            //           a problem yet, since we're not taking advantage
-            //           of this for caching yet. This is a workaround
-            //           for some existing issues with handling scale
-            //           when rasterizing in local space mode. Once
-            //           the fixes for those are in-place, we can
-            //           remove this hack!
-            //let local_scale = raster_space.local_scale();
-            // let wants_raster_root = xf.has_perspective_component() ||
-            //                         local_scale.is_some();
-            let establishes_raster_root = xf.has_perspective_component();
-
             // TODO(gw): For now, we always raster in screen space. Soon,
             //           we will be able to respect the requested raster
             //           space, and/or override the requested raster root
             //           if it makes sense to.
             let raster_space = RasterSpace::Screen;
 
-            let raster_spatial_node_index = if establishes_raster_root {
-                surface_spatial_node_index
-            } else {
-                parent_raster_spatial_node_index
-            };
-
             let inflation_factor = match composite_mode {
                 PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
                     // The amount of extra space needed for primitives inside
                     // this picture to ensure the visibility check is correct.
                     BLUR_SAMPLE_SCALE * blur_radius
                 }
                 _ => {
                     0.0
                 }
             };
 
-            let surface_index = state.push_surface(
+            let mut surface = {
+                // Check if there is perspective, and thus whether a new
+                // rasterization root should be established.
+                let xf = frame_context.clip_scroll_tree.get_relative_transform(
+                    parent_raster_spatial_node_index,
+                    surface_spatial_node_index,
+                ).expect("BUG: unable to get relative transform");
+
+                let establishes_raster_root = xf.has_perspective_component();
+
                 SurfaceInfo::new(
                     surface_spatial_node_index,
-                    raster_spatial_node_index,
+                    if establishes_raster_root {
+                        surface_spatial_node_index
+                    } else {
+                        parent_raster_spatial_node_index
+                    },
                     inflation_factor,
                     frame_context.screen_world_rect,
                     &frame_context.clip_scroll_tree,
                 )
-            );
+            };
 
-            self.raster_config = Some(RasterConfig {
-                composite_mode,
-                surface_index,
-                establishes_raster_root,
-            });
+            if surface_spatial_node_index != parent_raster_spatial_node_index &&
+                !surface.fits_surface_size_limits()
+            {
+                // fall back to the parent raster root
+                surface = SurfaceInfo::new(
+                    surface_spatial_node_index,
+                    parent_raster_spatial_node_index,
+                    inflation_factor,
+                    frame_context.screen_world_rect,
+                    &frame_context.clip_scroll_tree,
+                );
+            };
 
             // If we have a cache key / descriptor for this surface,
             // update any transforms it cares about.
             if let Some(ref mut surface_desc) = self.surface_desc {
                 surface_desc.update(
                     surface_spatial_node_index,
-                    raster_spatial_node_index,
+                    surface.raster_spatial_node_index,
                     frame_context.clip_scroll_tree,
                     raster_space,
                 );
             }
+
+            self.raster_config = Some(RasterConfig {
+                composite_mode,
+                establishes_raster_root: surface_spatial_node_index == surface.raster_spatial_node_index,
+                surface_index: state.push_surface(surface),
+            });
         }
 
         Some(mem::replace(&mut self.prim_list.pictures, SmallVec::new()))
     }
 
     /// Update the primitive dependencies for any active tile caches,
     /// but only *if* the transforms have made the mappings out of date.
     pub fn update_prim_dependencies(
@@ -2149,17 +2166,17 @@ impl PicturePrimitive {
                     Picture3DContext::In { root_data: None, ancestor_index } => {
                         ancestor_index
                     }
                 };
 
                 let map_local_to_containing_block: SpaceMapper<LayoutPixel, LayoutPixel> = SpaceMapper::new_with_target(
                     containing_block_index,
                     cluster.spatial_node_index,
-                    LayoutRect::zero(),     // bounds aren't going to be used for this mapping
+                    LayoutRect::zero(), // bounds aren't going to be used for this mapping
                     &frame_context.clip_scroll_tree,
                 );
 
                 match map_local_to_containing_block.visible_face() {
                     VisibleFace::Back => continue,
                     VisibleFace::Front => {}
                 }
             }
@@ -2336,20 +2353,16 @@ impl PicturePrimitive {
                 // dimensions, so that it can be reused as it scrolls into
                 // view etc. However, if the unclipped size of the surface is
                 // too big, then it will be very expensive to draw, and may
                 // even be bigger than the maximum hardware render target
                 // size. In these cases, it's probably best to not cache the
                 // picture, and just draw a minimal portion of the picture
                 // (clipped to screen bounds) to a temporary target each frame.
 
-                // TODO(gw): This size is quite arbitrary - we should do some
-                //           profiling / telemetry to see when it makes sense
-                //           to cache a picture.
-                const MAX_CACHE_SIZE: f32 = 2048.0;
                 let too_big_to_cache = unclipped.size.width > MAX_CACHE_SIZE ||
                                        unclipped.size.height > MAX_CACHE_SIZE;
 
                 // If we can't create a valid cache key for this descriptor (e.g.
                 // due to it referencing old non-interned style primitives), then
                 // don't try to cache it.
                 let has_valid_cache_key = self.surface_desc.is_some();
 
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -126,17 +126,17 @@ impl SpatialNode {
             LayoutFastTransform::identity, |perspective| perspective.into());
         let info = ReferenceFrameInfo {
             transform_style,
             source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
             source_perspective,
             origin_in_parent_reference_frame,
             invertible: true,
         };
-        Self::new(pipeline_id, parent_index, SpatialNodeType:: ReferenceFrame(info))
+        Self::new(pipeline_id, parent_index, SpatialNodeType::ReferenceFrame(info))
     }
 
     pub fn new_sticky_frame(
         parent_index: SpatialNodeIndex,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) -> Self {
         Self::new(pipeline_id, Some(parent_index), SpatialNodeType::StickyFrame(sticky_frame_info))
index 13b3419a39b2a752b261e019b6f9cb56715551bf..842a7a5e56f24c1c909c0a0ff9c1a62d424397de
GIT binary patch
literal 7442
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