| author | Glenn Watson <gw@intuitionlibrary.com> |
| Thu, 13 Feb 2020 18:08:16 +0000 | |
| changeset 513808 | 0c0eb80974b03f6cc153616fe21c8dc229b25c3e |
| parent 513807 | 19c9be9294f4a813cd5e6224e094e1ac42c60139 |
| child 513809 | 3ba58bc194382fc128e44027779cb85a763bcf0a |
| push id | 37122 |
| push user | csabou@mozilla.com |
| push date | Fri, 14 Feb 2020 10:05:11 +0000 |
| treeherder | mozilla-central@ccbbd26e4bec [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| reviewers | nical, Bert |
| bugs | 1579235 |
| milestone | 75.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
|
| gfx/wr/webrender/src/composite.rs | file | annotate | diff | comparison | revisions | |
| gfx/wr/webrender/src/picture.rs | file | annotate | diff | comparison | revisions |
--- a/gfx/wr/webrender/src/composite.rs +++ b/gfx/wr/webrender/src/composite.rs @@ -299,18 +299,17 @@ 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; - for key in &tile_cache.tiles_to_draw { - let tile = &tile_cache.tiles[key]; + for tile in tile_cache.tiles.values() { 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(); @@ -330,18 +329,18 @@ impl CompositeState { tile.is_opaque || tile_cache.is_opaque(), ) } }; let tile_id = tile_cache.native_surface_id.map(|surface_id| { NativeTileId { surface_id, - x: key.x, - y: key.y, + x: tile.tile_offset.x, + y: tile.tile_offset.y, } }); let tile = CompositeTile { surface, rect: device_rect, valid_rect: tile.device_valid_rect.translate(-device_rect.origin.to_vector()), dirty_rect: tile.device_dirty_rect.translate(-device_rect.origin.to_vector()),
--- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -222,17 +222,17 @@ impl<Src, Dst> From<CoordinateSpaceMappi struct PictureInfo { /// The spatial node for this picture. _spatial_node_index: SpatialNodeIndex, } /// Picture-caching state to keep between scenes. pub struct PictureCacheState { /// The tiles retained by this picture cache. - pub tiles: FastHashMap<TileOffset, Tile>, + pub tiles: FastHashMap<TileOffset, Box<Tile>>, /// State of the spatial nodes from previous frame spatial_nodes: FastHashMap<SpatialNodeIndex, SpatialNodeDependency>, /// State of opacity bindings from previous frame opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>, /// The current transform of the picture cache root spatial node root_transform: TransformKey, /// The current tile size in device pixels current_tile_size: DeviceIntSize, @@ -240,17 +240,16 @@ pub struct PictureCacheState { allocations: PictureCacheRecycledAllocations, /// Currently allocated native compositor surface for this picture cache. pub native_surface_id: Option<NativeSurfaceId>, /// True if the entire picture cache is opaque. is_opaque: bool, } pub struct PictureCacheRecycledAllocations { - old_tiles: FastHashMap<TileOffset, Tile>, old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>, compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>, } /// Stores a list of cached picture tiles that are retained /// between new scenes. #[cfg_attr(feature = "capture", derive(Serialize))] pub struct RetainedTiles { @@ -410,16 +409,19 @@ 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, + + /// Current size of tiles in picture units. + tile_size: PictureSize, } // Immutable context passed to picture cache tiles during post_update struct TilePostUpdateContext<'a> { /// Maps from picture cache coords -> world space coords. pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>, /// Global scale factor from world -> device pixels. @@ -738,16 +740,18 @@ pub struct TileCacheInstanceSerializer { pub slice: usize, pub tiles: FastHashMap<TileOffset, TileSerializer>, pub background_color: Option<ColorF>, pub fract_offset: PictureVector2D, } /// Information about a cached tile. pub struct Tile { + /// The grid position of this tile within the picture cache + pub tile_offset: TileOffset, /// The current world rect of this tile. pub world_tile_rect: WorldRect, /// 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 device space dirty rect for this tile. /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future, @@ -784,20 +788,21 @@ pub struct Tile { /// The last rendered background color on this tile. background_color: Option<ColorF>, /// The first reason the tile was invalidated this frame. invalidation_reason: Option<InvalidationReason>, } impl Tile { /// Construct a new, invalid tile. - fn new( - id: TileId, - ) -> Self { + fn new(tile_offset: TileOffset) -> Self { + let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed)); + Tile { + tile_offset, local_tile_rect: PictureRect::zero(), world_tile_rect: WorldRect::zero(), device_valid_rect: DeviceRect::zero(), local_dirty_rect: PictureRect::zero(), device_dirty_rect: DeviceRect::zero(), surface: None, current_descriptor: TileDescriptor::new(), prev_descriptor: TileDescriptor::new(), @@ -908,20 +913,28 @@ impl Tile { self.invalidation_reason = Some(reason); } } /// 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; + // Ensure each tile is offset by the appropriate amount from the + // origin, such that the content origin will be a whole number and + // the snapping will be consistent. + self.local_tile_rect = PictureRect::new( + PicturePoint::new( + self.tile_offset.x as f32 * ctx.tile_size.width + ctx.fract_offset.x, + self.tile_offset.y as f32 * ctx.tile_size.height + ctx.fract_offset.y, + ), + ctx.tile_size, + ); self.invalidation_reason = None; self.world_tile_rect = ctx.pic_to_world_mapper .map(&self.local_tile_rect) .expect("bug: map local tile rect"); // Check if this tile is currently on screen. self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect); @@ -953,17 +966,17 @@ impl Tile { // Clear any dependencies so that when we rebuild them we // can compare if the tile has the same content. mem::swap( &mut self.current_descriptor, &mut self.prev_descriptor, ); self.current_descriptor.clear(); - self.root.clear(local_tile_rect); + self.root.clear(self.local_tile_rect); } /// Add dependencies for a given primitive to this tile. fn add_prim_dependency( &mut self, info: &PrimitiveDependencyInfo, ) { // If this tile isn't currently visible, we don't want to update the dependencies @@ -1088,16 +1101,17 @@ impl Tile { // be composited. If the tile subsequently gets new primitives added to it, the // surface will be re-allocated when it's added to the composite draw list. if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() { if let Some(id) = id.take() { state.resource_cache.destroy_compositor_tile(id); } } + self.is_visible = false; return false; } let world_valid_rect = ctx.pic_to_world_mapper .map(&self.current_descriptor.local_valid_rect) .expect("bug: map local valid rect"); // The device rect is guaranteed to be aligned on a device pixel - the round @@ -1936,19 +1950,17 @@ pub struct TileCacheInstance { pub slice: usize, /// Propagated information about the slice pub slice_flags: SliceFlags, /// The currently selected tile size to use for this cache pub current_tile_size: DeviceIntSize, /// The positioning node for this tile cache. pub spatial_node_index: SpatialNodeIndex, /// Hash of tiles present in this picture. - pub tiles: FastHashMap<TileOffset, Tile>, - /// Switch back and forth between old and new tiles hashmaps to avoid re-allocating. - old_tiles: FastHashMap<TileOffset, Tile>, + pub tiles: FastHashMap<TileOffset, Box<Tile>>, /// A helper struct to map local rects into surface coords. map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>, /// A helper struct to map child picture rects into picture cache surface coords. map_child_pic_to_surface: SpaceMapper<PicturePixel, PicturePixel>, /// List of opacity bindings, with some extra information /// about whether they changed since last frame. opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>, /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating. @@ -1972,18 +1984,16 @@ pub struct TileCacheInstance { /// Pre-calculated versions of the tile_rect above, used to speed up the /// calculations in get_tile_coords_for_rect. tile_bounds_p0: TileOffset, tile_bounds_p1: TileOffset, /// Local rect (unclipped) of the picture this cache covers. pub local_rect: PictureRect, /// The local clip rect, from the shared clips of this picture. local_clip_rect: PictureRect, - /// A list of tiles that are valid and visible, which should be drawn to the main scene. - pub tiles_to_draw: Vec<TileOffset>, /// The surface index that this tile cache will be drawn into. surface_index: SurfaceIndex, /// The background color from the renderer. If this is set opaque, we know it's /// fine to clear the tiles to this and allow subpixel text on the first slice. pub background_color: Option<ColorF>, /// Information about the calculated backdrop content of this cache. backdrop: BackdropInfo, /// The allowed subpixel mode for this surface, which depends on the detected @@ -2032,17 +2042,16 @@ impl TileCacheInstance { shared_clips: Vec<ClipDataHandle>, shared_clip_chain: ClipChainId, ) -> Self { TileCacheInstance { slice, slice_flags, spatial_node_index, tiles: FastHashMap::default(), - old_tiles: FastHashMap::default(), map_local_to_surface: SpaceMapper::new( ROOT_SPATIAL_NODE_INDEX, PictureRect::zero(), ), map_child_pic_to_surface: SpaceMapper::new( ROOT_SPATIAL_NODE_INDEX, PictureRect::zero(), ), @@ -2053,17 +2062,16 @@ impl TileCacheInstance { used_spatial_nodes: FastHashSet::default(), dirty_region: DirtyRegion::new(), tile_size: PictureSize::zero(), tile_rect: TileRect::zero(), tile_bounds_p0: TileOffset::zero(), tile_bounds_p1: TileOffset::zero(), local_rect: PictureRect::zero(), local_clip_rect: PictureRect::zero(), - tiles_to_draw: Vec::new(), surface_index: SurfaceIndex(0), background_color, backdrop: BackdropInfo::empty(), subpixel_mode: SubpixelMode::Allow, root_transform: TransformKey::Local, shared_clips, shared_clip_chain, current_tile_size: DeviceIntSize::zero(), @@ -2207,21 +2215,16 @@ impl TileCacheInstance { *dest = src; } else { dest.clear(); dest.reserve(ideal_len); } } } recycle_map( - self.tiles.len(), - &mut self.old_tiles, - prev_state.allocations.old_tiles, - ); - recycle_map( self.opacity_bindings.len(), &mut self.old_opacity_bindings, prev_state.allocations.old_opacity_bindings, ); recycle_map( prev_state.allocations.compare_cache.len(), &mut self.compare_cache, prev_state.allocations.compare_cache, @@ -2355,98 +2358,90 @@ impl TileCacheInstance { let x0 = (p0.x / local_tile_rect.size.width).floor() as i32; let x1 = (p1.x / local_tile_rect.size.width).ceil() as i32; let y0 = (p0.y / local_tile_rect.size.height).floor() as i32; let y1 = (p1.y / local_tile_rect.size.height).ceil() as i32; let x_tiles = x1 - x0; let y_tiles = y1 - y0; - self.tile_rect = TileRect::new( + let new_tile_rect = TileRect::new( TileOffset::new(x0, y0), TileSize::new(x_tiles, y_tiles), ); + + // Rebuild the tile grid if the picture cache rect has changed. + if new_tile_rect != self.tile_rect { + let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default()); + self.tiles.reserve(new_tile_rect.size.area() as usize); + + for y in y0 .. y1 { + for x in x0 .. x1 { + let key = TileOffset::new(x, y); + let tile = old_tiles + .remove(&key) + .unwrap_or_else(|| { + Box::new(Tile::new(key)) + }); + self.tiles.insert(key, tile); + } + } + + // When old tiles that remain after the loop, dirty rects are not valid. + if !old_tiles.is_empty() { + frame_state.composite_state.dirty_rects_are_valid = false; + } + + // Any old tiles that remain after the loop above are going to be dropped. For + // simple composite mode, the texture cache handle will expire and be collected + // by the texture cache. For native compositor mode, we need to explicitly + // invoke a callback to the client to destroy that surface. + frame_state.composite_state.destroy_native_tiles( + old_tiles.values_mut(), + frame_state.resource_cache, + ); + } + // This is duplicated information from tile_rect, but cached here to avoid // redundant calculations during get_tile_coords_for_rect self.tile_bounds_p0 = TileOffset::new(x0, y0); self.tile_bounds_p1 = TileOffset::new(x1, y1); + self.tile_rect = new_tile_rect; let mut world_culling_rect = WorldRect::zero(); - mem::swap(&mut self.tiles, &mut self.old_tiles); - let ctx = TilePreUpdateContext { pic_to_world_mapper, fract_offset: self.fract_offset, background_color: self.background_color, global_screen_world_rect: frame_context.global_screen_world_rect, + tile_size: self.tile_size, }; - self.tiles.clear(); - for y in y0 .. y1 { - for x in x0 .. x1 { - let key = TileOffset::new(x, y); - - let mut tile = self.old_tiles - .remove(&key) - .unwrap_or_else(|| { - let next_id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed)); - Tile::new(next_id) - }); - - // Ensure each tile is offset by the appropriate amount from the - // origin, such that the content origin will be a whole number and - // the snapping will be consistent. - let rect = PictureRect::new( - PicturePoint::new( - x as f32 * self.tile_size.width + self.fract_offset.x, - y as f32 * self.tile_size.height + self.fract_offset.y, - ), - self.tile_size, - ); - - tile.pre_update( - rect, - &ctx, - ); - - // Only include the tiles that are currently in view into the world culling - // rect. This is a very important optimization for a couple of reasons: - // (1) Primitives that intersect with tiles in the grid that are not currently - // visible can be skipped from primitive preparation, clip chain building - // and tile dependency updates. - // (2) When we need to allocate an off-screen surface for a child picture (for - // example a CSS filter) we clip the size of the GPU surface to the world - // culling rect below (to ensure we draw enough of it to be sampled by any - // tiles that reference it). Making the world culling rect only affected - // by visible tiles (rather than the entire virtual tile display port) can - // result in allocating _much_ smaller GPU surfaces for cases where the - // true off-screen surface size is very large. - if tile.is_visible { - world_culling_rect = world_culling_rect.union(&tile.world_tile_rect); - } - - self.tiles.insert(key, tile); + // Pre-update each tile + for tile in self.tiles.values_mut() { + tile.pre_update(&ctx); + + // Only include the tiles that are currently in view into the world culling + // rect. This is a very important optimization for a couple of reasons: + // (1) Primitives that intersect with tiles in the grid that are not currently + // visible can be skipped from primitive preparation, clip chain building + // and tile dependency updates. + // (2) When we need to allocate an off-screen surface for a child picture (for + // example a CSS filter) we clip the size of the GPU surface to the world + // culling rect below (to ensure we draw enough of it to be sampled by any + // tiles that reference it). Making the world culling rect only affected + // by visible tiles (rather than the entire virtual tile display port) can + // result in allocating _much_ smaller GPU surfaces for cases where the + // true off-screen surface size is very large. + if tile.is_visible { + world_culling_rect = world_culling_rect.union(&tile.world_tile_rect); } } - // When old tiles that remain after the loop, dirty rects are not valid. - if !self.old_tiles.is_empty() { - frame_state.composite_state.dirty_rects_are_valid = false; - } - - // Any old tiles that remain after the loop above are going to be dropped. For - // simple composite mode, the texture cache handle will expire and be collected - // by the texture cache. For native compositor mode, we need to explicitly - // invoke a callback to the client to destroy that surface. - frame_state.composite_state.destroy_native_tiles( - self.old_tiles.values_mut(), - frame_state.resource_cache, - ); - // If compositor mode is changed, need to drop all incompatible tiles. match (frame_context.config.compositor_kind, self.native_surface_id) { (CompositorKind::Draw { .. }, Some(_)) => { frame_state.composite_state.destroy_native_tiles( self.tiles.values_mut(), frame_state.resource_cache, ); for tile in self.tiles.values_mut() { @@ -2816,17 +2811,16 @@ impl TileCacheInstance { /// Apply any updates after prim dependency updates. This applies /// any late tile invalidations, and sets up the dirty rect and /// set of tile blits. pub fn post_update( &mut self, frame_context: &FrameVisibilityContext, frame_state: &mut FrameVisibilityState, ) { - self.tiles_to_draw.clear(); self.dirty_region.clear(); // Register the opaque region of this tile cache as an occluder, which // is used later in the frame to occlude other tiles. if self.backdrop.rect.is_well_formed_and_nonempty() { let backdrop_rect = self.backdrop.rect .intersection(&self.local_rect) .and_then(|r| { @@ -2924,19 +2918,18 @@ impl TileCacheInstance { resource_cache: frame_state.resource_cache, composite_state: frame_state.composite_state, compare_cache: &mut self.compare_cache, }; // Step through each tile and invalidate if the dependencies have changed. Determine // the current opacity setting and whether it's changed. let mut tile_cache_is_opaque = true; - for (key, tile) in self.tiles.iter_mut() { + for tile in self.tiles.values_mut() { if tile.post_update(&ctx, &mut state, frame_context) { - self.tiles_to_draw.push(*key); tile_cache_is_opaque &= tile.is_opaque; } } // If opacity changed, the native compositor surface and all tiles get invalidated. // (this does nothing if not using native compositor mode). // TODO(gw): This property probably changes very rarely, so it is OK to invalidate // everything in this case. If it turns out that this isn't true, we could @@ -3800,17 +3793,16 @@ impl PicturePrimitive { tiles: tile_cache.tiles, spatial_nodes: tile_cache.spatial_nodes, opacity_bindings: tile_cache.opacity_bindings, root_transform: tile_cache.root_transform, current_tile_size: tile_cache.current_tile_size, native_surface_id: tile_cache.native_surface_id.take(), is_opaque: tile_cache.is_opaque, allocations: PictureCacheRecycledAllocations { - old_tiles: tile_cache.old_tiles, old_opacity_bindings: tile_cache.old_opacity_bindings, compare_cache: tile_cache.compare_cache, }, }, ); } } } @@ -4225,18 +4217,20 @@ impl PicturePrimitive { // 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 world_clip_rect = map_pic_to_world .map(&tile_cache.local_clip_rect) .expect("bug: unable to map clip rect"); let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round(); - for key in &tile_cache.tiles_to_draw { - let tile = tile_cache.tiles.get_mut(key).expect("bug: no tile found!"); + for tile in tile_cache.tiles.values_mut() { + if !tile.is_visible { + continue; + } // Get the world space rect that this tile will actually occupy on screem let device_draw_rect = match device_clip_rect.intersection(&tile.device_valid_rect) { Some(rect) => rect, None => { tile.is_visible = false; continue; } @@ -4362,18 +4356,18 @@ impl PicturePrimitive { ); tile_cache.native_surface_id = Some(surface_id); } // Create the tile identifier and allocate it. let tile_id = NativeTileId { surface_id: tile_cache.native_surface_id.unwrap(), - x: key.x, - y: key.y, + x: tile.tile_offset.x, + y: tile.tile_offset.y, }; frame_state.resource_cache.create_compositor_tile(tile_id); *id = Some(tile_id); } } } @@ -5950,17 +5944,17 @@ impl TileNode { } } } } } impl CompositeState { // A helper function to destroy all native surfaces for a given list of tiles - pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Tile>>( + pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Box<Tile>>>( &mut self, tiles_iter: I, resource_cache: &mut ResourceCache, ) { // Any old tiles that remain after the loop above are going to be dropped. For // simple composite mode, the texture cache handle will expire and be collected // by the texture cache. For native compositor mode, we need to explicitly // invoke a callback to the client to destroy that surface.