| author | Glenn Watson <gw@intuitionlibrary.com> |
| Sun, 01 Mar 2020 03:55:54 +0000 | |
| changeset 516285 | 6814870342efc284b2edec7c9aeb45240cb0fe09 |
| parent 516284 | 810e31bf46f160c31e8cfa8d77aead4c902f2276 |
| child 516286 | 8f267ed8f2e3d09ced8c665af0a6db3694197116 |
| push id | 37172 |
| push user | malexandru@mozilla.com |
| push date | Mon, 02 Mar 2020 09:48:18 +0000 |
| treeherder | mozilla-central@51efc4b931f7 [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| reviewers | 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
|
--- a/gfx/wr/webrender/src/composite.rs +++ b/gfx/wr/webrender/src/composite.rs @@ -231,17 +231,16 @@ struct Occluder { device_rect: DeviceIntRect, } /// Describes the properties that identify a tile composition uniquely. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(PartialEq, Clone)] pub struct CompositeSurfaceDescriptor { - pub slice: usize, pub surface_id: Option<NativeSurfaceId>, pub offset: DevicePoint, pub clip_rect: DeviceRect, } /// Describes surface properties used to composite a frame. This /// is used to compare compositions between frames. #[cfg_attr(feature = "capture", derive(Serialize))] @@ -380,52 +379,54 @@ impl CompositeState { tile_cache: &TileCacheInstance, device_clip_rect: DeviceRect, z_id: ZBufferId, global_device_pixel_scale: DevicePixelScale, resource_cache: &ResourceCache, gpu_cache: &mut GpuCache, deferred_resolves: &mut Vec<DeferredResolve>, ) { - let mut visible_tile_count = 0; + let mut visible_opaque_tile_count = 0; + let mut visible_alpha_tile_count = 0; 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(); let surface = tile.surface.as_ref().expect("no tile surface set!"); - let (surface, is_opaque) = match surface { + let (surface, is_opaque, tile_id) = match surface { TileSurface::Color { color } => { - (CompositeTileSurface::Color { color: *color }, true) + (CompositeTileSurface::Color { color: *color }, true, None) } TileSurface::Clear => { - (CompositeTileSurface::Clear, false) + (CompositeTileSurface::Clear, false, None) } TileSurface::Texture { descriptor, .. } => { let surface = descriptor.resolve(resource_cache, tile_cache.current_tile_size); + let tile_id = match surface { + ResolvedSurfaceTexture::Native { id, .. } => Some(id), + ResolvedSurfaceTexture::TextureCache { .. } => None, + }; ( CompositeTileSurface::Texture { surface }, tile.is_opaque || tile_cache.is_opaque(), + tile_id, ) } }; - let tile_id = tile_cache.native_surface_id.map(|surface_id| { - NativeTileId { - surface_id, - x: tile.tile_offset.x, - y: tile.tile_offset.y, - } - }); + if is_opaque { + visible_opaque_tile_count += 1; + } else { + visible_alpha_tile_count += 1; + } // Determine ordering of this tile, based on presence of compositor // surfaces that intersect the tile. let mut mode = TileCompositeMode::Default; if tile.has_compositor_surface { // TODO(gw): This will almost always select over blend, due to the // background rectangle. In future, we can optimize this @@ -520,21 +521,30 @@ impl CompositeState { yuv_color_space: external_surface.yuv_color_space, yuv_format: external_surface.yuv_format, yuv_rescale: external_surface.yuv_rescale, image_buffer_kind: get_buffer_kind(yuv_planes[0].texture), yuv_planes, }); } - if visible_tile_count > 0 { + if visible_opaque_tile_count > 0 { self.descriptor.surfaces.push( CompositeSurfaceDescriptor { - slice: tile_cache.slice, - surface_id: tile_cache.native_surface_id, + surface_id: tile_cache.native_surface.as_ref().map(|s| s.opaque), + offset: tile_cache.device_position, + clip_rect: device_clip_rect, + } + ); + } + + if visible_alpha_tile_count > 0 { + self.descriptor.surfaces.push( + CompositeSurfaceDescriptor { + surface_id: tile_cache.native_surface.as_ref().map(|s| s.alpha), offset: tile_cache.device_position, clip_rect: device_clip_rect, } ); } } /// Add a tile to the appropriate array, depending on tile properties and compositor mode.
--- a/gfx/wr/webrender/src/frame_builder.rs +++ b/gfx/wr/webrender/src/frame_builder.rs @@ -397,18 +397,19 @@ impl FrameBuilder { // and surfaces that match. Once the `update_visibility` call above is // complete, any tiles that are left remaining in the `retained_tiles` // map are not needed and will be dropped. For simple compositing mode, // this is fine, since texture cache handles are garbage collected at // the end of each frame. However, if we're in native compositor mode, // we need to manually clean up any native compositor surfaces that were // allocated by these tiles. for (_, mut cache_state) in visibility_state.retained_tiles.caches.drain() { - if let Some(native_surface_id) = cache_state.native_surface_id.take() { - visibility_state.resource_cache.destroy_compositor_surface(native_surface_id); + if let Some(native_surface) = cache_state.native_surface.take() { + visibility_state.resource_cache.destroy_compositor_surface(native_surface.opaque); + visibility_state.resource_cache.destroy_compositor_surface(native_surface.alpha); } } } let mut frame_state = FrameBuildingState { render_tasks, profile_counters, clip_store: &mut scene.clip_store,
--- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -269,19 +269,17 @@ pub struct PictureCacheState { color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>, /// The current transform of the picture cache root spatial node root_transform: TransformKey, /// The current tile size in device pixels current_tile_size: DeviceIntSize, /// Various allocations we want to avoid re-doing. 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 native_surface: Option<NativeSurface>, } pub struct PictureCacheRecycledAllocations { old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>, old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>, compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>, } @@ -1207,17 +1205,36 @@ impl Tile { .unwrap_or_else(DeviceRect::zero); // 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.current_descriptor.local_valid_rect .intersection(&ctx.local_clip_rect) .unwrap_or_else(PictureRect::zero); - self.is_opaque = ctx.backdrop.rect.contains_rect(&clipped_rect); + let is_opaque = ctx.backdrop.rect.contains_rect(&clipped_rect); + + if is_opaque != self.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 + // consider other options, such as per-tile opacity (natively supported + // on CoreAnimation, and supported if backed by non-virtual surfaces in + // DirectComposition). + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface { + if let Some(id) = id.take() { + state.resource_cache.destroy_compositor_tile(id); + } + } + + // Invalidate the entire tile to force a redraw. + self.invalidate(None, InvalidationReason::SurfaceOpacityChanged { became_opaque: is_opaque }); + self.is_opaque = is_opaque; + } // 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 { .. } => { (true, true) } @@ -2033,16 +2050,34 @@ impl TileCacheLogger { output.write_all(b"// interning data\n").unwrap(); output.write_all(self.frames[index].update_lists.to_ron().as_bytes()).unwrap(); files_written = files_written + 1; } } } +/// Represents the native surfaces created for a picture cache, if using +/// a native compositor. An opaque and alpha surface is always created, +/// but tiles are added to a surface based on current opacity. If the +/// calculated opacity of a tile changes, the tile is invalidated and +/// attached to a different native surface. This means that we don't +/// need to invalidate the entire surface if only some tiles are changing +/// opacity. It also means we can take advantage of opaque tiles on cache +/// slices where only some of the tiles are opaque. There is an assumption +/// that creating a native surface is cheap, and only when a tile is added +/// to a surface is there a significant cost. This assumption holds true +/// for the current native compositor implementations on Windows and Mac. +pub struct NativeSurface { + /// Native surface for opaque tiles + pub opaque: NativeSurfaceId, + /// Native surface for alpha tiles + pub alpha: NativeSurfaceId, +} + /// Represents a cache of tiles that make up a picture primitives. pub struct TileCacheInstance { /// Index of the tile cache / slice for this frame builder. It's determined /// by the setup_picture_caching method during flattening, which splits the /// picture tree into multiple slices. It's used as a simple input to the tile /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed /// between display lists - this seems very unlikely to occur on most pages, but /// can be revisited if we ever notice that. @@ -2118,25 +2153,23 @@ pub struct TileCacheInstance { /// we don't want to constantly invalidate and reallocate different tile size /// configuration each frame. frames_until_size_eval: usize, /// The current fractional offset of the cached picture fract_offset: PictureVector2D, /// keep around the hash map used as compare_cache to avoid reallocating it each /// frame. compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>, - /// The allocated compositor surface for this picture cache. May be None if + /// The allocated compositor surfaces for this picture cache. May be None if /// not using native compositor, or if the surface was destroyed and needs /// to be reallocated next time this surface contains valid tiles. - pub native_surface_id: Option<NativeSurfaceId>, + pub native_surface: Option<NativeSurface>, /// The current device position of this cache. Used to set the compositor /// offset of the surface when building the visual tree. pub device_position: DevicePoint, - /// True if the entire picture cache surface is opaque. - is_opaque: bool, /// The currently considered tile size override. Used to check if we should /// re-evaluate tile size, even if the frame timer hasn't expired. tile_size_override: Option<DeviceIntSize>, /// List of external surfaces that have been promoted from primitives /// in this tile cache. pub external_surfaces: Vec<ExternalSurfaceDescriptor>, } @@ -2182,19 +2215,18 @@ impl TileCacheInstance { subpixel_mode: SubpixelMode::Allow, root_transform: TransformKey::Local, shared_clips, shared_clip_chain, current_tile_size: DeviceIntSize::zero(), frames_until_size_eval: 0, fract_offset: PictureVector2D::zero(), compare_cache: FastHashMap::default(), - native_surface_id: None, + native_surface: None, device_position: DevicePoint::zero(), - is_opaque: true, tile_size_override: None, external_surfaces: Vec::new(), } } /// Returns true if this tile cache is considered opaque. pub fn is_opaque(&self) -> bool { // If known opaque due to background clear color and being the first slice. @@ -2309,18 +2341,17 @@ impl TileCacheInstance { // If there are pending retained state, retrieve it. if let Some(prev_state) = frame_state.retained_tiles.caches.remove(&self.slice) { self.tiles.extend(prev_state.tiles); self.root_transform = prev_state.root_transform; self.spatial_nodes = prev_state.spatial_nodes; self.opacity_bindings = prev_state.opacity_bindings; self.color_bindings = prev_state.color_bindings; self.current_tile_size = prev_state.current_tile_size; - self.native_surface_id = prev_state.native_surface_id; - self.is_opaque = prev_state.is_opaque; + self.native_surface = prev_state.native_surface; fn recycle_map<K: std::cmp::Eq + std::hash::Hash, V>( ideal_len: usize, dest: &mut FastHashMap<K, V>, src: FastHashMap<K, V>, ) { if dest.capacity() < src.capacity() { if src.capacity() < 3 * ideal_len { @@ -2372,18 +2403,19 @@ impl TileCacheInstance { } }; // If the desired tile size has changed, then invalidate and drop any // existing tiles. if desired_tile_size != self.current_tile_size { // Destroy any native surfaces on the tiles that will be dropped due // to resizing. - if let Some(native_surface_id) = self.native_surface_id.take() { - frame_state.resource_cache.destroy_compositor_surface(native_surface_id); + if let Some(native_surface) = self.native_surface.take() { + frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); + frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); } self.tiles.clear(); self.current_tile_size = desired_tile_size; } // Reset counter until next evaluating the desired tile size. This is an // arbitrary value. self.frames_until_size_eval = 120; @@ -2567,43 +2599,45 @@ impl TileCacheInstance { // 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); } } // 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, - ); + match frame_context.config.compositor_kind { + CompositorKind::Draw { .. } => { for tile in self.tiles.values_mut() { - tile.surface = None; - // Invalidate the entire tile to force a redraw. - tile.invalidate(None, InvalidationReason::CompositorKindChanged); + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { + if let Some(id) = id.take() { + frame_state.resource_cache.destroy_compositor_tile(id); + tile.surface = None; + // Invalidate the entire tile to force a redraw. + tile.invalidate(None, InvalidationReason::CompositorKindChanged); + } + } } - if let Some(native_surface_id) = self.native_surface_id.take() { - frame_state.resource_cache.destroy_compositor_surface(native_surface_id); + + if let Some(native_surface) = self.native_surface.take() { + frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); + frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); } } - (CompositorKind::Native { .. }, None) => { + CompositorKind::Native { .. } => { // This could hit even when compositor mode is not changed, // then we need to check if there are incompatible tiles. for tile in self.tiles.values_mut() { if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface { tile.surface = None; // Invalidate the entire tile to force a redraw. tile.invalidate(None, InvalidationReason::CompositorKindChanged); } } } - (_, _) => {} } world_culling_rect } /// Update the dependencies for each tile for a given primitive instance. pub fn update_prim_dependencies( &mut self, @@ -3188,48 +3222,18 @@ impl TileCacheInstance { let mut state = TilePostUpdateState { 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 tile in self.tiles.values_mut() { - if tile.post_update(&ctx, &mut state, frame_context) { - 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 - // consider other options, such as per-tile opacity (natively supported - // on CoreAnimation, and supported if backed by non-virtual surfaces in - // DirectComposition). - if self.is_opaque != tile_cache_is_opaque { - if let Some(native_surface_id) = self.native_surface_id.take() { - // Since the native surface will be destroyed, need to clear the compositor tile - // handle for all tiles. This means the tiles will be reallocated on demand - // when the tiles are added to render tasks. - for tile in self.tiles.values_mut() { - if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { - *id = None; - } - // Invalidate the entire tile to force a redraw. - tile.invalidate(None, InvalidationReason::SurfaceOpacityChanged { - became_opaque: tile_cache_is_opaque }); - } - // Destroy the compositor surface. It will be reallocated with the correct - // opacity flag when render tasks are generated for tiles. - frame_state.resource_cache.destroy_compositor_surface(native_surface_id); - } - self.is_opaque = tile_cache_is_opaque; + tile.post_update(&ctx, &mut state, frame_context); } // When under test, record a copy of the dirty region to support // invalidation testing in wrench. if frame_context.config.testing { frame_state.scratch.recorded_dirty_regions.push(self.dirty_region.record()); } } @@ -4051,29 +4055,28 @@ impl PicturePrimitive { /// Destroy an existing picture. This is called just before /// a frame builder is replaced with a newly built scene. It /// gives a picture a chance to retain any cached tiles that /// may be useful during the next scene build. pub fn destroy( &mut self, retained_tiles: &mut RetainedTiles, ) { - if let Some(mut tile_cache) = self.tile_cache.take() { + if let Some(tile_cache) = self.tile_cache.take() { if !tile_cache.tiles.is_empty() { retained_tiles.caches.insert( tile_cache.slice, PictureCacheState { tiles: tile_cache.tiles, spatial_nodes: tile_cache.spatial_nodes, opacity_bindings: tile_cache.opacity_bindings, color_bindings: tile_cache.color_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, + native_surface: tile_cache.native_surface, allocations: PictureCacheRecycledAllocations { old_opacity_bindings: tile_cache.old_opacity_bindings, old_color_bindings: tile_cache.old_color_bindings, compare_cache: tile_cache.compare_cache, }, }, ); } @@ -4619,30 +4622,46 @@ impl PicturePrimitive { ); } } SurfaceTextureDescriptor::Native { id } => { if id.is_none() { // Allocate a native surface id if we're in native compositing mode, // and we don't have a surface yet (due to first frame, or destruction // due to tile size changing etc). - if tile_cache.native_surface_id.is_none() { - let surface_id = frame_state + if tile_cache.native_surface.is_none() { + let opaque = frame_state + .resource_cache + .create_compositor_surface( + tile_cache.current_tile_size, + true, + ); + + let alpha = frame_state .resource_cache .create_compositor_surface( tile_cache.current_tile_size, - tile_cache.is_opaque, + false, ); - tile_cache.native_surface_id = Some(surface_id); + tile_cache.native_surface = Some(NativeSurface { + opaque, + alpha, + }); } // Create the tile identifier and allocate it. + let surface_id = if tile.is_opaque { + tile_cache.native_surface.as_ref().unwrap().opaque + } else { + tile_cache.native_surface.as_ref().unwrap().alpha + }; + let tile_id = NativeTileId { - surface_id: tile_cache.native_surface_id.unwrap(), + surface_id, x: tile.tile_offset.x, y: tile.tile_offset.y, }; frame_state.resource_cache.create_compositor_tile(tile_id); *id = Some(tile_id); }