Bug 1608280 - Part 1 - Add a valid_rect to picture cache and composite tiles. r=mstange
authorGlenn Watson <gw@intuitionlibrary.com>
Mon, 03 Feb 2020 23:16:54 +0000
changeset 512426 42095e4b17091e46bd63d72f17145505ab1f8faf
parent 512425 494913315ee37f08e9a2a5fb7b1eb7af20f516ab
child 512427 58e428bf7b1439155cb055e06ef9e67b0128ef74
push id37087
push usernbeleuzu@mozilla.com
push dateTue, 04 Feb 2020 04:04:16 +0000
treeherdermozilla-central@c32017c40f19 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1608280
milestone74.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 1608280 - Part 1 - Add a valid_rect to picture cache and composite tiles. r=mstange This patch introduces a per-tile valid rect. In the initial implementation, this only uses the bounds of the overall picture cache bounding rect. The next part of this patch will make use of true per-tile valid regions, to improve performance where there are holes in a single cache slice. Differential Revision: https://phabricator.services.mozilla.com/D61378
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/composite.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/renderer.rs
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -1207,25 +1207,22 @@ impl BatchBuilder {
                                 // present modes during render, such as partial present etc.
                                 let tile_cache = picture.tile_cache.as_ref().unwrap();
                                 let map_local_to_world = SpaceMapper::new_with_target(
                                     ROOT_SPATIAL_NODE_INDEX,
                                     tile_cache.spatial_node_index,
                                     ctx.screen_world_rect,
                                     ctx.spatial_tree,
                                 );
-                                let local_tile_clip_rect = LayoutRect::from_untyped(&tile_cache.local_rect.to_untyped());
-                                let local_tile_clip_rect = match local_tile_clip_rect.intersection(&prim_info.combined_local_clip_rect) {
-                                    Some(rect) => rect,
-                                    None => {
-                                        return;
-                                    }
-                                };
+                                // TODO(gw): As a follow up to the valid_rect work, see why we use
+                                //           prim_info.combined_local_clip_rect here instead of the
+                                //           local_clip_rect built in the TileCacheInstance. Perhaps
+                                //           these can be unified or are different for a good reason?
                                 let world_clip_rect = map_local_to_world
-                                    .map(&local_tile_clip_rect)
+                                    .map(&prim_info.combined_local_clip_rect)
                                     .expect("bug: unable to map clip rect");
                                 let device_clip_rect = (world_clip_rect * ctx.global_device_pixel_scale).round();
                                 let z_id = composite_state.z_generator.next();
 
                                 composite_state.push_surface(
                                     tile_cache,
                                     device_clip_rect,
                                     z_id,
--- a/gfx/wr/webrender/src/composite.rs
+++ b/gfx/wr/webrender/src/composite.rs
@@ -61,16 +61,17 @@ pub enum CompositeTileSurface {
 /// Describes the geometry and surface of a tile to be composited
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CompositeTile {
     pub surface: CompositeTileSurface,
     pub rect: DeviceRect,
     pub clip_rect: DeviceRect,
     pub dirty_rect: DeviceRect,
+    pub valid_rect: DeviceRect,
     pub z_id: ZBufferId,
     pub tile_id: TileId,
 }
 
 /// Public interface specified in `RendererOptions` that configures
 /// how WR compositing will operate.
 pub enum CompositorConfig {
     /// Let WR draw tiles via normal batching. This requires no special OS support.
@@ -298,27 +299,44 @@ impl CompositeState {
         tile_cache: &TileCacheInstance,
         device_clip_rect: DeviceRect,
         z_id: ZBufferId,
         global_device_pixel_scale: DevicePixelScale,
         resource_cache: &ResourceCache,
     ) {
         let mut visible_tile_count = 0;
 
+        // TODO(gw): For now, we apply the valid rect as part of the clip rect
+        //           during native compositing. This works for the initial
+        //           implementation, since the valid rect is determined only
+        //           by the bounding rect of the picture cache slice. When
+        //           we implement proper per-tile valid rects, we will need to
+        //           supply the valid rect directly to the compositor interface.
+        let mut combined_valid_rect = DeviceRect::zero();
+
         for key in &tile_cache.tiles_to_draw {
             let tile = &tile_cache.tiles[key];
             if !tile.is_visible {
                 // This can occur when a tile is found to be occluded during frame building.
                 continue;
             }
 
             visible_tile_count += 1;
 
             let device_rect = (tile.world_tile_rect * global_device_pixel_scale).round();
             let dirty_rect = (tile.world_dirty_rect * global_device_pixel_scale).round();
+            // The device rect is guaranteed to be aligned on a device pixel - the round
+            // above is just to deal with float accuracy. However, the valid rect is not
+            // always aligned to a device pixel. To handle this, round out to get all
+            // required pixels, and intersect with the tile device rect.
+            let valid_rect = (tile.world_valid_rect * global_device_pixel_scale)
+                .round_out()
+                .intersection(&device_rect)
+                .unwrap_or_else(DeviceRect::zero);
+            combined_valid_rect = combined_valid_rect.union(&valid_rect);
             let surface = tile.surface.as_ref().expect("no tile surface set!");
 
             let (surface, is_opaque) = match surface {
                 TileSurface::Color { color } => {
                     (CompositeTileSurface::Color { color: *color }, true)
                 }
                 TileSurface::Clear => {
                     (CompositeTileSurface::Clear, false)
@@ -330,34 +348,37 @@ impl CompositeState {
                         tile.is_opaque || tile_cache.is_opaque(),
                     )
                 }
             };
 
             let tile = CompositeTile {
                 surface,
                 rect: device_rect,
+                valid_rect,
                 dirty_rect,
                 clip_rect: device_clip_rect,
                 z_id,
                 tile_id: tile.id,
             };
 
             self.push_tile(tile, is_opaque);
         }
 
         if visible_tile_count > 0 {
-            self.descriptor.surfaces.push(
-                CompositeSurfaceDescriptor {
-                    slice: tile_cache.slice,
-                    surface_id: tile_cache.native_surface_id,
-                    offset: tile_cache.device_position,
-                    clip_rect: device_clip_rect,
-                }
-            );
+            if let Some(clip_rect) = device_clip_rect.intersection(&combined_valid_rect) {
+                self.descriptor.surfaces.push(
+                    CompositeSurfaceDescriptor {
+                        slice: tile_cache.slice,
+                        surface_id: tile_cache.native_surface_id,
+                        offset: tile_cache.device_position,
+                        clip_rect,
+                    }
+                );
+            }
         }
     }
 
     /// Add a tile to the appropriate array, depending on tile properties and compositor mode.
     fn push_tile(
         &mut self,
         tile: CompositeTile,
         is_opaque: bool,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -401,23 +401,23 @@ struct TilePreUpdateContext {
     /// require invalidation of all tiles.
     fract_offset: PictureVector2D,
 
     /// The optional background color of the picture cache instance
     background_color: Option<ColorF>,
 
     /// The visible part of the screen in world coords.
     global_screen_world_rect: WorldRect,
+
+    /// The local rect of the overall picture cache
+    local_rect: PictureRect,
 }
 
 // Immutable context passed to picture cache tiles during post_update
 struct TilePostUpdateContext<'a> {
-    /// The local rect of the overall picture cache
-    local_rect: PictureRect,
-
     /// The local clip rect (in picture space) of the entire picture cache
     local_clip_rect: PictureRect,
 
     /// The calculated backdrop information for this cache instance.
     backdrop: BackdropInfo,
 
     /// Information about transform node differences from last frame.
     spatial_nodes: &'a FastHashMap<SpatialNodeIndex, SpatialNodeDependency>,
@@ -662,16 +662,20 @@ pub struct Tile {
     /// The current local rect of this tile.
     pub local_tile_rect: PictureRect,
     /// The picture space dirty rect for this tile.
     local_dirty_rect: PictureRect,
     /// The world space dirty rect for this tile.
     /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
     ///           expose these as multiple dirty rects, which will help in some cases.
     pub world_dirty_rect: WorldRect,
+    /// Picture space rect that contains valid pixels region of this tile.
+    local_valid_rect: PictureRect,
+    /// World space rect that contains valid pixels region of this tile.
+    pub world_valid_rect: WorldRect,
     /// Uniquely describes the content of this tile, in a way that can be
     /// (reasonably) efficiently hashed and compared.
     pub current_descriptor: TileDescriptor,
     /// The content descriptor for this tile from the previous frame.
     pub prev_descriptor: TileDescriptor,
     /// Handle to the backing surface for this tile.
     pub surface: Option<TileSurface>,
     /// If true, this tile is marked valid, and the existing texture
@@ -702,16 +706,18 @@ pub struct Tile {
 impl Tile {
     /// Construct a new, invalid tile.
     fn new(
         id: TileId,
     ) -> Self {
         Tile {
             local_tile_rect: PictureRect::zero(),
             world_tile_rect: WorldRect::zero(),
+            local_valid_rect: PictureRect::zero(),
+            world_valid_rect: WorldRect::zero(),
             local_dirty_rect: PictureRect::zero(),
             world_dirty_rect: WorldRect::zero(),
             surface: None,
             current_descriptor: TileDescriptor::new(),
             prev_descriptor: TileDescriptor::new(),
             is_valid: false,
             is_visible: false,
             fract_offset: PictureVector2D::zero(),
@@ -814,22 +820,27 @@ impl Tile {
     /// Called during pre_update of a tile cache instance. Allows the
     /// tile to setup state before primitive dependency calculations.
     fn pre_update(
         &mut self,
         local_tile_rect: PictureRect,
         ctx: &TilePreUpdateContext,
     ) {
         self.local_tile_rect = local_tile_rect;
+        self.local_valid_rect = local_tile_rect.intersection(&ctx.local_rect).unwrap();
         self.invalidation_reason  = None;
 
         self.world_tile_rect = ctx.pic_to_world_mapper
             .map(&self.local_tile_rect)
             .expect("bug: map local tile rect");
 
+        self.world_valid_rect = ctx.pic_to_world_mapper
+            .map(&self.local_valid_rect)
+            .expect("bug: map local valid rect");
+
         // Check if this tile is currently on screen.
         self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect);
 
         // If the tile isn't visible, early exit, skipping the normal set up to
         // validate dependencies. Instead, we will only compare the current tile
         // dependencies the next time it comes into view.
         if !self.is_visible {
             return;
@@ -970,19 +981,18 @@ impl Tile {
             }
 
             return false;
         }
 
         // Check if this tile can be considered opaque. Opacity state must be updated only
         // after all early out checks have been performed. Otherwise, we might miss updating
         // the native surface next time this tile becomes visible.
-        let clipped_rect = self.local_tile_rect
-            .intersection(&ctx.local_rect)
-            .and_then(|r| r.intersection(&ctx.local_clip_rect))
+        let clipped_rect = self.local_valid_rect
+            .intersection(&ctx.local_clip_rect)
             .unwrap_or_else(PictureRect::zero);
         self.is_opaque = ctx.backdrop.rect.contains_rect(&clipped_rect);
 
         // Check if the selected composite mode supports dirty rect updates. For Draw composite
         // mode, we can always update the content with smaller dirty rects. For native composite
         // mode, we can only use dirty rects if the compositor supports partial surface updates.
         let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind {
             CompositorKind::Draw { .. } => {
@@ -2191,16 +2201,17 @@ impl TileCacheInstance {
         self.tile_bounds_p0 = TileOffset::new(x0, y0);
         self.tile_bounds_p1 = TileOffset::new(x1, y1);
 
         let mut world_culling_rect = WorldRect::zero();
 
         mem::swap(&mut self.tiles, &mut self.old_tiles);
 
         let ctx = TilePreUpdateContext {
+            local_rect: self.local_rect,
             pic_to_world_mapper,
             fract_offset: self.fract_offset,
             background_color: self.background_color,
             global_screen_world_rect: frame_context.global_screen_world_rect,
         };
 
         self.tiles.clear();
         for y in y0 .. y1 {
@@ -2714,17 +2725,16 @@ impl TileCacheInstance {
 
             self.spatial_nodes.insert(spatial_node_index, SpatialNodeDependency {
                 changed,
                 value,
             });
         }
 
         let ctx = TilePostUpdateContext {
-            local_rect: self.local_rect,
             local_clip_rect: self.local_clip_rect,
             backdrop: self.backdrop,
             spatial_nodes: &self.spatial_nodes,
             opacity_bindings: &self.opacity_bindings,
             current_tile_size: self.current_tile_size,
         };
 
         let mut state = TilePostUpdateState {
@@ -4019,41 +4029,37 @@ impl PicturePrimitive {
                         Some((render_task_id, render_task_id))
                     }
                     PictureCompositeMode::TileCache { .. } => {
                         let tile_cache = self.tile_cache.as_mut().unwrap();
                         let mut first = true;
 
                         // Get the overall world space rect of the picture cache. Used to clip
                         // the tile rects below for occlusion testing to the relevant area.
-                        let local_clip_rect = tile_cache.local_rect
-                            .intersection(&tile_cache.local_clip_rect)
-                            .unwrap_or_else(PictureRect::zero);
-
                         let world_clip_rect = map_pic_to_world
-                            .map(&local_clip_rect)
+                            .map(&tile_cache.local_clip_rect)
                             .expect("bug: unable to map clip rect");
 
                         for key in &tile_cache.tiles_to_draw {
                             let tile = tile_cache.tiles.get_mut(key).expect("bug: no tile found!");
 
                             // Get the world space rect that this tile will actually occupy on screem
-                            let tile_draw_rect = match world_clip_rect.intersection(&tile.world_tile_rect) {
+                            let world_draw_rect = match world_clip_rect.intersection(&tile.world_valid_rect) {
                                 Some(rect) => rect,
                                 None => {
                                     tile.is_visible = false;
                                     continue;
                                 }
                             };
 
                             // If that draw rect is occluded by some set of tiles in front of it,
                             // then mark it as not visible and skip drawing. When it's not occluded
                             // it will fail this test, and get rasterized by the render task setup
                             // code below.
-                            if frame_state.composite_state.is_tile_occluded(tile_cache.slice, tile_draw_rect) {
+                            if frame_state.composite_state.is_tile_occluded(tile_cache.slice, world_draw_rect) {
                                 // If this tile has an allocated native surface, free it, since it's completely
                                 // occluded. We will need to re-allocate this surface if it becomes visible,
                                 // but that's likely to be rare (e.g. when there is no content display list
                                 // for a frame or two during a tab switch).
                                 let surface = tile.surface.as_mut().expect("no tile surface set!");
 
                                 if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface {
                                     if let Some(id) = id.take() {
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -4216,16 +4216,22 @@ impl Renderer {
                 None => tile.rect,
             };
 
             let clip_rect = match partial_clip_rect.intersection(&tile.clip_rect) {
                 Some(rect) => rect,
                 None => continue,
             };
 
+            // Only composite the part of the tile that contains valid pixels
+            let clip_rect = match clip_rect.intersection(&tile.valid_rect) {
+                Some(rect) => rect,
+                None => continue,
+            };
+
             // Flush this batch if the textures aren't compatible
             if !current_textures.is_compatible_with(&textures) {
                 self.draw_instanced_batch(
                     &instances,
                     VertexArrayKind::Composite,
                     &current_textures,
                     stats,
                 );