From 89ab2e365e3223888fe14ead14b4dcfd45982e34 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Tue, 27 Nov 2018 16:42:03 +1000 Subject: [PATCH 1/2] Allow picture caching to work across scenes (display lists). This patch hooks up the primitive interning work with the tile caching work. Since we have a uid that guarantees uniqueness of the content of primitives and clip nodes, we can use that to construct a (reasonably) efficient hash key for the content of a tile. Along with some additional information (such as the state of the transforms used by the tile), we can construct a hash key for the content of a tile that is stable across new display lists, even if the shape of the clip-scroll tree changes in ways that don't affect the tile. This patch takes advantage of that to retain tiles when a new scene is built, where the content of a tile would result in the same output. --- webrender/src/batch.rs | 2 +- webrender/src/frame_builder.rs | 40 ++- webrender/src/picture.rs | 451 ++++++++++++++++++++++++++------ webrender/src/prim_store.rs | 40 ++- webrender/src/render_backend.rs | 27 +- webrender/src/scene.rs | 9 +- webrender/src/surface.rs | 8 +- 7 files changed, 482 insertions(+), 95 deletions(-) diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 00ac0f6350..c6923ca880 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -1001,7 +1001,7 @@ impl AlphaBatchBuilder { let tile = &tile_cache.tiles[i as usize]; // Check if the tile is visible. - if !tile.is_visible { + if !tile.is_visible || !tile.in_use { continue; } diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 0b93afe330..61a435a829 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -12,7 +12,7 @@ use gpu_cache::GpuCache; use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator}; use hit_test::{HitTester, HitTestingRun}; use internal_types::{FastHashMap, PlaneSplitter}; -use picture::{PictureSurface, PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex}; +use picture::{PictureSurface, PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex, TileDescriptor}; use prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer}; #[cfg(feature = "replay")] use prim_store::{PrimitiveStoreStats}; @@ -23,8 +23,9 @@ use resource_cache::{ResourceCache}; use scene::{ScenePipeline, SceneProperties}; use segment::SegmentBuilder; use spatial_node::SpatialNode; -use std::f32; +use std::{f32, mem}; use std::sync::Arc; +use texture_cache::TextureCacheHandle; use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext}; use tiling::{SpecialRenderPasses}; @@ -60,6 +61,9 @@ pub struct FrameBuilder { background_color: Option, window_size: DeviceIntSize, root_pic_index: PictureIndex, + /// Cache of surface tiles from the previous frame builder + /// that can optionally be consumed by this frame builder. + pending_retained_tiles: FastHashMap, pub prim_store: PrimitiveStore, pub clip_store: ClipStore, pub hit_testing_runs: Vec, @@ -144,6 +148,7 @@ impl FrameBuilder { window_size: DeviceIntSize::zero(), background_color: None, root_pic_index: PictureIndex(0), + pending_retained_tiles: FastHashMap::default(), config: FrameBuilderConfig { default_font_render_mode: FontRenderMode::Mono, dual_source_blending_is_enabled: true, @@ -153,6 +158,17 @@ impl FrameBuilder { } } + /// Provide any cached surface tiles from the previous frame builder + /// to a new frame builder. These will be consumed or dropped the + /// first time a new frame builder creates a frame. + pub fn set_retained_tiles( + &mut self, + retained_tiles: FastHashMap, + ) { + debug_assert!(self.pending_retained_tiles.is_empty()); + self.pending_retained_tiles = retained_tiles; + } + pub fn with_display_list_flattener( screen_rect: DeviceIntRect, background_color: Option, @@ -167,10 +183,24 @@ impl FrameBuilder { screen_rect, background_color, window_size, + pending_retained_tiles: FastHashMap::default(), config: flattener.config, } } + /// Destroy an existing frame builder. This is called just before + /// a frame builder is replaced with a newly built scene. + pub fn destroy( + self, + retained_tiles: &mut FastHashMap, + resource_cache: &ResourceCache, + ) { + self.prim_store.destroy( + retained_tiles, + resource_cache, + ); + } + /// Compute the contribution (bounding rectangles, and resources) of layers and their /// primitives in screen space. fn build_layer_screen_rects_and_cull_layers( @@ -227,6 +257,10 @@ impl FrameBuilder { surfaces.push(root_surface); let mut pic_update_state = PictureUpdateState::new(surfaces); + let mut retained_tiles = mem::replace( + &mut self.pending_retained_tiles, + FastHashMap::default(), + ); // The first major pass of building a frame is to walk the picture // tree. This pass must be quick (it should never touch individual @@ -240,8 +274,10 @@ impl FrameBuilder { &mut pic_update_state, &frame_context, resource_cache, + gpu_cache, &resources.prim_data_store, &self.clip_store, + &mut retained_tiles, ); let mut frame_state = FrameBuildingState { diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 30b74396fe..e49211dbeb 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -7,20 +7,20 @@ use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, Layo use api::{DevicePixelScale, RasterRect, RasterSpace, PictureSize, DeviceIntPoint, ColorF, ImageKey, DirtyRect}; use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor}; use box_shadow::{BLUR_SAMPLE_SCALE}; -use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode}; +use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode, ClipUid}; use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex}; use device::TextureFilter; use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D}; use euclid::approxeq::ApproxEq; use internal_types::{FastHashMap, PlaneSplitter}; use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext}; -use gpu_cache::{GpuCacheAddress, GpuCacheHandle}; +use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind}; use internal_types::FastHashSet; use plane_split::{Clipper, Polygon, Splitter}; -use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind}; +use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind, PrimitiveUid}; use prim_store::{get_raster_rects, PrimitiveDataInterner, PrimitiveDataStore, CoordinateSpaceMapping}; -use prim_store::{OpacityBindingStorage, PrimitiveTemplateKind, ImageInstanceStorage}; +use prim_store::{OpacityBindingStorage, PrimitiveTemplateKind, ImageInstanceStorage, OpacityBindingIndex, SizeKey}; use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit}; use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation}; use resource_cache::ResourceCache; @@ -65,21 +65,37 @@ pub const TILE_SIZE_DP: i32 = 512; /// Information about the state of a transform dependency. #[derive(Debug)] -pub struct TransformInfo { - /// Quantized transform value +pub struct TileTransformInfo { + /// The spatial node in the current clip-scroll tree that + /// this transform maps to. + spatial_node_index: SpatialNodeIndex, + /// Tiles check this to see if the dependencies have changed. + changed: bool, +} + +#[derive(Debug)] +pub struct GlobalTransformInfo { + /// Current (quantized) value of the transform, that is + /// independent of the value of the spatial node index. key: TransformKey, /// Tiles check this to see if the dependencies have changed. changed: bool, } +/// Information about the state of an opacity binding. +#[derive(Debug)] +pub struct OpacityBindingInfo { + /// The current value retrieved from dynamic scene properties. + value: f32, + /// True if it was changed (or is new) since the last frame build. + changed: bool, +} + /// Information about a cached tile. #[derive(Debug)] pub struct Tile { - // TODO(gw): We could perhaps use a bitset here instead of a hash set? - /// The set of transform values that primitives in this tile depend on. - transforms: FastHashSet, /// The set of opacity bindings that this tile depends on. - opacity_bindings: FastHashMap, + opacity_bindings: FastHashSet, /// Set of image keys that this tile depends on. image_keys: FastHashSet, /// If true, this tile is marked valid, and the existing texture @@ -95,30 +111,161 @@ pub struct Tile { /// may be false if the tile is outside the bounding rect of /// the current picture, but hasn't been discarded yet. This /// is calculated during primitive dependency updating. - in_use: bool, + pub in_use: bool, /// If true, this tile is currently visible on screen. This /// is calculated during build_dirty_regions. pub is_visible: bool, /// Handle to the cached texture for this tile. pub handle: TextureCacheHandle, + /// A map from clip-scroll tree spatial node indices to the tile + /// transforms. This allows the tile transforms to be stable + /// if the content of the tile is the same, but the shape of the + /// clip-scroll tree changes between scenes in other areas. + tile_transform_map: FastHashMap, + /// Information about the transforms that is not part of the cache key. + transform_info: Vec, + /// Uniquely describes the content of this tile, in a way that can be + /// (reasonably) efficiently hashed and compared. + descriptor: TileDescriptor, } impl Tile { /// Construct a new, invalid tile. - fn new() -> Self { + fn new(tile_offset: TileOffset) -> Self { Tile { - transforms: FastHashSet::default(), - opacity_bindings: FastHashMap::default(), + opacity_bindings: FastHashSet::default(), image_keys: FastHashSet::default(), is_valid: false, is_visible: false, is_cacheable: true, in_use: false, handle: TextureCacheHandle::invalid(), + descriptor: TileDescriptor::new(tile_offset), + tile_transform_map: FastHashMap::default(), + transform_info: Vec::new(), + } + } + + /// Add a (possibly) new transform dependency to this tile. + fn push_transform_dependency( + &mut self, + spatial_node_index: SpatialNodeIndex, + surface_spatial_node_index: SpatialNodeIndex, + clip_scroll_tree: &ClipScrollTree, + global_transforms: &[GlobalTransformInfo], + ) { + let transform_info = &mut self.transform_info; + let descriptor = &mut self.descriptor; + + // Get the mapping from unstable spatial node index to + // a local transform index within this tile. + let tile_transform_index = self + .tile_transform_map + .entry(spatial_node_index) + .or_insert_with(|| { + let index = transform_info.len(); + + let mapping: CoordinateSpaceMapping = CoordinateSpaceMapping::new( + surface_spatial_node_index, + spatial_node_index, + clip_scroll_tree, + ).expect("todo: handle invalid mappings"); + + transform_info.push(TileTransformInfo { + changed: global_transforms[spatial_node_index.0].changed, + spatial_node_index, + }); + + let key = mapping.into(); + + descriptor.transforms.push(key); + + TileTransformIndex(index as u32) + }); + + // Record the transform for this primitive / clip node. + // TODO(gw): It might be worth storing these in runs, since they + // probably don't change very often between prims. + descriptor.transform_ids.push(*tile_transform_index); + } + + /// Destroy a tile, optionally returning a handle and cache descriptor, + /// if this surface was valid and may be useful on the next scene. + fn destroy( + self, + resource_cache: &ResourceCache, + ) -> Option<(TileDescriptor, TextureCacheHandle)> { + if self.is_valid && resource_cache.texture_cache.is_allocated(&self.handle) { + Some((self.descriptor, self.handle)) + } else { + None } } } +/// Index of a transform array local to the tile. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct TileTransformIndex(u32); + +/// Uniquely describes the content of this tile, in a way that can be +/// (reasonably) efficiently hashed and compared. +#[derive(Debug, Eq, PartialEq, Hash)] +pub struct TileDescriptor { + /// List of primitive unique identifiers. The uid is guaranteed + /// to uniquely describe the content of the primitive. + pub prim_uids: Vec, + + /// List of clip node unique identifiers. The uid is guaranteed + /// to uniquely describe the content of the clip node. + pub clip_uids: Vec, + + /// List of local tile transform ids that are used to position + /// the primitive and clip items above. + pub transform_ids: Vec, + + /// List of transforms used by this tile, along with the current + /// quantized value. + pub transforms: Vec, + + /// The set of opacity bindings that this tile depends on. + // TODO(gw): Ugh, get rid of all opacity binding support! + pub opacity_bindings: Vec, + + /// Ensures that we hash to a tile in the same local position. + pub tile_offset: TileOffset, + pub local_tile_size: SizeKey, + + /// Identifies the raster configuration of the rasterization + /// root, to ensure tiles are invalidated if they are drawn in + /// screen-space with an incompatible transform. + pub raster_transform: TransformKey, +} + +impl TileDescriptor { + fn new(tile_offset: TileOffset) -> Self { + TileDescriptor { + prim_uids: Vec::new(), + clip_uids: Vec::new(), + transform_ids: Vec::new(), + opacity_bindings: Vec::new(), + transforms: Vec::new(), + tile_offset, + raster_transform: TransformKey::Local, + local_tile_size: SizeKey::zero(), + } + } + + /// Clear the dependency information for a tile, when the dependencies + /// are being rebuilt. + fn clear(&mut self) { + self.prim_uids.clear(); + self.clip_uids.clear(); + self.transform_ids.clear(); + self.transforms.clear(); + self.opacity_bindings.clear(); + } +} + /// Represents the dirty region of a tile cache picture. /// In future, we will want to support multiple dirty /// regions. @@ -142,7 +289,10 @@ pub struct TileCache { pub tile_rect: TileRect, /// List of transform keys - used to check if transforms /// have changed. - pub transforms: Vec, + pub transforms: Vec, + /// List of opacity bindings, with some extra information + /// about whether they changed since last frame. + pub opacity_bindings: FastHashMap, /// A helper struct to map local rects into picture coords. pub space_mapper: SpaceMapper, /// If true, we need to update the prim dependencies, due @@ -164,6 +314,7 @@ impl TileCache { tile_rect: TileRect::zero(), local_tile_size: PictureSize::zero(), transforms: Vec::new(), + opacity_bindings: FastHashMap::default(), needs_update: true, dirty_region: None, space_mapper: SpaceMapper::new( @@ -179,6 +330,8 @@ impl TileCache { pub fn update_transforms( &mut self, surface_spatial_node_index: SpatialNodeIndex, + raster_spatial_node_index: SpatialNodeIndex, + raster_space: RasterSpace, frame_context: &FrameBuildingContext, ) { // Initialize the space mapper with current bounds, @@ -217,11 +370,7 @@ impl TileCache { // dependencies for each tile. // TODO(gw): We could be smarter here and only rebuild for the primitives // which are affected by transforms that have changed. - self.needs_update = if self.transforms.len() == frame_context.clip_scroll_tree.spatial_nodes.len() { - // If the transform array length is the same, then we can walk the list - // and check if the values of each transform are the same. - let mut any_transforms_changed = false; - + if self.transforms.len() == frame_context.clip_scroll_tree.spatial_nodes.len() { for (i, transform) in self.transforms.iter_mut().enumerate() { let mapping: CoordinateSpaceMapping = CoordinateSpaceMapping::new( surface_spatial_node_index, @@ -232,11 +381,7 @@ impl TileCache { let key = mapping.into(); transform.changed = transform.key != key; transform.key = key; - - any_transforms_changed |= transform.changed; } - - any_transforms_changed } else { // If the size of the transforms array changed, just invalidate all the transforms for now. self.transforms.clear(); @@ -248,15 +393,88 @@ impl TileCache { frame_context.clip_scroll_tree, ).expect("todo: handle invalid mappings"); - self.transforms.push(TransformInfo { + self.transforms.push(GlobalTransformInfo { key: mapping.into(), changed: true, }); } + }; + + // Do a hacky diff of opacity binding values from the last frame. This is + // used later on during tile invalidation tests. + let current_properties = frame_context.scene_properties.float_properties(); + let old_properties = mem::replace(&mut self.opacity_bindings, FastHashMap::default()); + for (id, value) in current_properties { + let changed = match old_properties.get(id) { + Some(old_property) => !old_property.value.approx_eq(value), + None => true, + }; + self.opacity_bindings.insert(*id, OpacityBindingInfo { + value: *value, + changed, + }); + } - true + // Update the state of the transform for compositing this picture. + let raster_transform = match raster_space { + RasterSpace::Screen => { + // In general cases, if we're rasterizing a picture in screen space, then the + // value of the surface spatial node will affect the contents of the picture + // itself. However, if the surface and raster spatial nodes are in the same + // coordinate system (which is the common case!) then we are effectively drawing + // in a local space anyway, so don't care about that transform for the purposes + // of validating the surface cache contents. + let raster_spatial_node = &frame_context + .clip_scroll_tree + .spatial_nodes[raster_spatial_node_index.0]; + let surface_spatial_node = &frame_context + .clip_scroll_tree + .spatial_nodes[surface_spatial_node_index.0]; + + let mut key = CoordinateSpaceMapping::::new( + raster_spatial_node_index, + surface_spatial_node_index, + frame_context.clip_scroll_tree, + ).expect("bug: unable to get coord mapping").into(); + + if let TransformKey::ScaleOffset(ref mut key) = key { + if raster_spatial_node.coordinate_system_id == surface_spatial_node.coordinate_system_id { + key.offset_x = 0.0; + key.offset_y = 0.0; + } + } + + key + } + RasterSpace::Local(..) => { + TransformKey::local() + } }; + // Walk the transforms and see if we need to rebuild the primitive + // dependencies for each tile. + // TODO(gw): We could be smarter here and only rebuild for the primitives + // which are affected by transforms that have changed. + for tile in &mut self.tiles { + tile.descriptor.local_tile_size = self.local_tile_size.into(); + tile.descriptor.raster_transform = raster_transform.clone(); + + debug_assert_eq!(tile.transform_info.len(), tile.descriptor.transforms.len()); + for (info, transform) in tile.transform_info.iter_mut().zip(tile.descriptor.transforms.iter_mut()) { + let mapping: CoordinateSpaceMapping = CoordinateSpaceMapping::new( + surface_spatial_node_index, + info.spatial_node_index, + frame_context.clip_scroll_tree, + ).expect("todo: handle invalid mappings"); + let new_transform = mapping.into(); + + info.changed = *transform != new_transform; + *transform = new_transform; + + self.needs_update |= info.changed; + } + } + // If we need to update the dependencies for tiles, walk each tile // and clear the transforms and opacity bindings arrays. if self.needs_update { @@ -268,7 +486,9 @@ impl TileCache { for (i, mut tile) in self.tiles.drain(..).enumerate() { let y = i as i32 / self.tile_rect.size.width; let x = i as i32 % self.tile_rect.size.width; - tile.transforms.clear(); + tile.descriptor.clear(); + tile.transform_info.clear(); + tile.tile_transform_map.clear(); tile.opacity_bindings.clear(); tile.image_keys.clear(); tile.in_use = false; @@ -321,12 +541,15 @@ impl TileCache { // just resize the picture by adding / remove primitives. let tx = x0 - self.tile_rect.origin.x + x; let ty = y0 - self.tile_rect.origin.y + y; + let tile_offset = TileOffset::new(x + x0, y + y0); let tile = if tx >= 0 && ty >= 0 && tx < self.tile_rect.size.width && ty < self.tile_rect.size.height { let index = (ty * self.tile_rect.size.width + tx) as usize; - mem::replace(&mut self.tiles[index], Tile::new()) + mem::replace(&mut self.tiles[index], Tile::new(tile_offset)) } else { - self.old_tiles.remove(&TileOffset::new(x + x0, y + y0)).unwrap_or_else(Tile::new) + self.old_tiles.remove(&tile_offset).unwrap_or_else(|| { + Tile::new(tile_offset) + }) }; new_tiles.push(tile); } @@ -347,7 +570,6 @@ impl TileCache { clip_chain_nodes: &[ClipChainNode], pictures: &[PicturePrimitive], resource_cache: &ResourceCache, - scene_properties: &SceneProperties, opacity_binding_store: &OpacityBindingStorage, image_instances: &ImageInstanceStorage, ) { @@ -382,8 +604,9 @@ impl TileCache { self.reconfigure_tiles_if_required(x0, y0, x1, y1); // Build the list of resources that this primitive has dependencies on. - let mut opacity_bindings: SmallVec<[(PropertyBindingId, f32); 4]> = SmallVec::new(); + let mut opacity_bindings: SmallVec<[PropertyBindingId; 4]> = SmallVec::new(); let mut clip_chain_spatial_nodes: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new(); + let mut clip_chain_uids: SmallVec<[ClipUid; 8]> = SmallVec::new(); let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new(); let mut current_clip_chain_id = prim_instance.clip_chain_id; @@ -398,16 +621,18 @@ impl TileCache { // Pictures can depend on animated opacity bindings. let pic = &pictures[pic_index.0]; if let Some(PictureCompositeMode::Filter(FilterOp::Opacity(binding, _))) = pic.requested_composite_mode { - if let PropertyBinding::Binding(key, default) = binding { - opacity_bindings.push((key.id, default)); + if let PropertyBinding::Binding(key, _) = binding { + opacity_bindings.push(key.id); } } } PrimitiveInstanceKind::Rectangle { opacity_binding_index, .. } => { - let opacity_binding = &opacity_binding_store[opacity_binding_index]; - for binding in &opacity_binding.bindings { - if let PropertyBinding::Binding(key, default) = binding { - opacity_bindings.push((key.id, *default)); + if opacity_binding_index != OpacityBindingIndex::INVALID { + let opacity_binding = &opacity_binding_store[opacity_binding_index]; + for binding in &opacity_binding.bindings { + if let PropertyBinding::Binding(key, _) = binding { + opacity_bindings.push(key.id); + } } } } @@ -415,10 +640,12 @@ impl TileCache { let image_instance = &image_instances[image_instance_index]; let opacity_binding_index = image_instance.opacity_binding_index; - let opacity_binding = &opacity_binding_store[opacity_binding_index]; - for binding in &opacity_binding.bindings { - if let PropertyBinding::Binding(key, default) = binding { - opacity_bindings.push((key.id, *default)); + if opacity_binding_index != OpacityBindingIndex::INVALID { + let opacity_binding = &opacity_binding_store[opacity_binding_index]; + for binding in &opacity_binding.bindings { + if let PropertyBinding::Binding(key, _) = binding { + opacity_bindings.push(key.id); + } } } @@ -452,12 +679,6 @@ impl TileCache { } } - for (key, current) in &mut opacity_bindings { - if let Some(value) = scene_properties.get_float_value(*key) { - *current = value; - } - } - // The transforms of any clips that are relative to the picture may affect // the content rendered by this primitive. while current_clip_chain_id != ClipChainId::NONE { @@ -467,6 +688,7 @@ impl TileCache { // handled by the clip collector when these tiles are composited. if clip_chain_node.spatial_node_index > surface_spatial_node_index { clip_chain_spatial_nodes.push(clip_chain_node.spatial_node_index); + clip_chain_uids.push(clip_chain_node.handle.uid()); } current_clip_chain_id = clip_chain_node.parent_clip_chain_id; } @@ -488,17 +710,33 @@ impl TileCache { } // Include the transform of the primitive itself. - tile.transforms.insert(prim_instance.spatial_node_index); + tile.push_transform_dependency( + prim_instance.spatial_node_index, + surface_spatial_node_index, + clip_scroll_tree, + &self.transforms, + ); // Include the transforms of any relevant clip nodes for this primitive. for clip_chain_spatial_node in &clip_chain_spatial_nodes { - tile.transforms.insert(*clip_chain_spatial_node); + tile.push_transform_dependency( + *clip_chain_spatial_node, + surface_spatial_node_index, + clip_scroll_tree, + &self.transforms, + ); } // Include any opacity bindings this primitive depends on. - for &(id, value) in &opacity_bindings { - tile.opacity_bindings.insert(id, value); + for id in &opacity_bindings { + if tile.opacity_bindings.insert(*id) { + tile.descriptor.opacity_bindings.push(*id); + } } + + // Update the tile descriptor, used for tile comparison during scene swaps. + tile.descriptor.prim_uids.push(prim_instance.prim_data_handle.uid()); + tile.descriptor.clip_uids.extend_from_slice(&clip_chain_uids); } } } @@ -510,6 +748,8 @@ impl TileCache { surface_spatial_node_index: SpatialNodeIndex, frame_context: &FrameBuildingContext, resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + retained_tiles: &mut FastHashMap, ) { self.needs_update = false; @@ -539,6 +779,29 @@ impl TileCache { let i = y * self.tile_rect.size.width + x; let tile = &mut self.tiles[i as usize]; + // Try to reuse cached tiles from the previous scene in this new + // scene, if possible. + if !resource_cache.texture_cache.is_allocated(&tile.handle) { + // See if we have a retained tile from last scene that matches the + // exact content of this tile. + if let Some(handle) = retained_tiles.remove(&tile.descriptor) { + // Only use if not evicted from texture cache in the meantime. + if !resource_cache.texture_cache.request(&handle, gpu_cache) { + // We found a matching tile from the previous scene, so use it! + tile.handle = handle; + tile.is_valid = true; + // We know that the hash key of the descriptor validates that + // the local transforms in this tile exactly match the value + // of the current relative transforms needed for this tile, + // so we can mark those transforms as valid to avoid the + // retained tile being invalidated below. + for info in &mut tile.transform_info { + info.changed = false; + } + } + } + } + let tile_rect = PictureRect::new( PicturePoint::new( (self.tile_rect.origin.x + x) as f32 * self.local_tile_size.width, @@ -565,20 +828,22 @@ impl TileCache { } // Invalidate the tile if any dependent transforms changed - for node_index in &tile.transforms { - if self.transforms[node_index.0].changed { + for info in &tile.transform_info { + if info.changed { tile.is_valid = false; break; } } // Invalidate the tile if any opacity bindings changed. - for (id, old) in &mut tile.opacity_bindings { - if let Some(new) = frame_context.scene_properties.get_float_value(*id) { - if !new.approx_eq(old) { - tile.is_valid = false; - break; - } + for id in &tile.opacity_bindings { + let changed = match self.opacity_bindings.get(id) { + Some(info) => info.changed, + None => true, + }; + if changed { + tile.is_valid = false; + break; } } @@ -595,7 +860,7 @@ impl TileCache { // If we have an invalid tile, which is also visible, add it to the // dirty rect we will need to draw. - if !tile.is_valid && tile.is_visible { + if !tile.is_valid && tile.is_visible && tile.in_use { dirty_rect = dirty_rect.union(&tile_rect); tile_offset.x = tile_offset.x.min(x); tile_offset.y = tile_offset.y.min(y); @@ -603,6 +868,16 @@ impl TileCache { } } + // If we had any retained tiles from the last scene that were not picked + // up by the new frame, then just discard them eagerly. + // TODO(gw): Maybe it's worth keeping them around for a bit longer in + // some cases? + for (_, handle) in retained_tiles.drain() { + if resource_cache.texture_cache.is_allocated(&handle) { + resource_cache.texture_cache.mark_unused(&handle); + } + } + self.dirty_region = if dirty_rect.is_empty() { None } else { @@ -1105,6 +1380,25 @@ 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 FastHashMap, + resource_cache: &ResourceCache, + ) { + if let Some(tile_cache) = self.tile_cache.take() { + debug_assert!(tile_cache.old_tiles.is_empty()); + for tile in tile_cache.tiles { + if let Some((descriptor, texture_handle)) = tile.destroy(resource_cache) { + retained_tiles.insert(descriptor, texture_handle); + } + } + } + } + pub fn new_image( requested_composite_mode: Option, context_3d: Picture3DContext, @@ -1468,18 +1762,6 @@ impl PicturePrimitive { return None; } - // If we have a tile cache for this picture, see if any of the - // relative transforms have changed, which means we need to - // re-map the dependencies of any child primitives. - if let Some(mut tile_cache) = self.tile_cache.take() { - tile_cache.update_transforms( - self.spatial_node_index, - frame_context, - ); - - state.push_tile_cache(tile_cache); - } - // Push information about this pic on stack for children to read. state.push_picture(PictureInfo { spatial_node_index: self.spatial_node_index, @@ -1554,6 +1836,20 @@ impl PicturePrimitive { surface_index, }); + // If we have a tile cache for this picture, see if any of the + // relative transforms have changed, which means we need to + // re-map the dependencies of any child primitives. + if let Some(mut tile_cache) = self.tile_cache.take() { + tile_cache.update_transforms( + surface_spatial_node_index, + raster_spatial_node_index, + raster_space, + frame_context, + ); + + state.push_tile_cache(tile_cache); + } + // 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 { @@ -1586,17 +1882,18 @@ impl PicturePrimitive { return; } + let surface_spatial_node_index = state.current_surface().surface_spatial_node_index; + for prim_instance in &self.prim_list.prim_instances { for tile_cache in &mut state.tile_cache_stack { tile_cache.update_prim_dependencies( prim_instance, - self.spatial_node_index, + surface_spatial_node_index, &frame_context.clip_scroll_tree, prim_data_store, &clip_store.clip_chain_nodes, pictures, resource_cache, - frame_context.scene_properties, opacity_binding_store, image_instances, ); @@ -1612,6 +1909,8 @@ impl PicturePrimitive { state: &mut PictureUpdateState, frame_context: &FrameBuildingContext, resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + retained_tiles: &mut FastHashMap, ) { // Pop the state information about this picture. state.pop_picture(); @@ -1704,6 +2003,8 @@ impl PicturePrimitive { self.spatial_node_index, frame_context, resource_cache, + gpu_cache, + retained_tiles, ); self.tile_cache = Some(tile_cache); @@ -1849,7 +2150,7 @@ impl PicturePrimitive { frame_state.gpu_cache, None, UvRectKind::Rect, - Eviction::Eager, + Eviction::Auto, ); let cache_item = frame_state diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 4b14feabf5..e12b92b24f 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -15,7 +15,7 @@ use border::{BorderSegmentCacheKey, NormalBorderAu}; use clip::{ClipStore}; use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex}; use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector}; -use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale}; +use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D}; use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState}; use frame_builder::PrimitiveContext; use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT}; @@ -23,8 +23,9 @@ use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpu use gpu_types::BrushFlags; use image::{self, Repetition}; use intern; +use internal_types::FastHashMap; use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState}; -use picture::{ClusterRange, PrimitiveList, SurfaceIndex}; +use picture::{ClusterRange, PrimitiveList, SurfaceIndex, TileDescriptor}; #[cfg(debug_assertions)] use render_backend::{FrameId}; use render_backend::FrameResources; @@ -38,6 +39,7 @@ use std::{cmp, fmt, hash, mem, ops, u32, usize}; #[cfg(debug_assertions)] use std::sync::atomic::{AtomicUsize, Ordering}; use storage; +use texture_cache::TextureCacheHandle; use tiling::SpecialRenderPasses; use util::{ScaleOffset, MatrixHelpers, MaxRect, recycle_vec}; use util::{pack_as_float, project_rect, raster_rect_to_device_pixels}; @@ -550,8 +552,8 @@ impl From for LayoutSize { } } -impl From for SizeKey { - fn from(size: LayoutSize) -> SizeKey { +impl From> for SizeKey { + fn from(size: TypedSize2D) -> SizeKey { SizeKey { w: size.width, h: size.height, @@ -559,6 +561,15 @@ impl From for SizeKey { } } +impl SizeKey { + pub fn zero() -> SizeKey { + SizeKey { + w: 0.0, + h: 0.0, + } + } +} + /// Hashable radial gradient parameters, for use during prim interning. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] @@ -2602,6 +2613,21 @@ impl PrimitiveStore { } } + /// Destroy an existing primitive store. This is called just before + /// a primitive store is replaced with a newly built scene. + pub fn destroy( + self, + retained_tiles: &mut FastHashMap, + resource_cache: &ResourceCache, + ) { + for pic in self.pictures { + pic.destroy( + retained_tiles, + resource_cache, + ); + } + } + pub fn create_picture( &mut self, prim: PicturePrimitive, @@ -2621,8 +2647,10 @@ impl PrimitiveStore { state: &mut PictureUpdateState, frame_context: &FrameBuildingContext, resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, prim_data_store: &PrimitiveDataStore, clip_store: &ClipStore, + retained_tiles: &mut FastHashMap, ) { if let Some(children) = self.pictures[pic_index.0].pre_update( state, @@ -2634,8 +2662,10 @@ impl PrimitiveStore { state, frame_context, resource_cache, + gpu_cache, prim_data_store, clip_store, + retained_tiles, ); } @@ -2655,6 +2685,8 @@ impl PrimitiveStore { state, frame_context, resource_cache, + gpu_cache, + retained_tiles, ); } } diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 28fa06e9b6..949db01cc2 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -503,12 +503,32 @@ impl Document { self.clip_scroll_tree.get_scroll_node_state() } - pub fn new_async_scene_ready(&mut self, built_scene: BuiltScene) { + pub fn new_async_scene_ready( + &mut self, + mut built_scene: BuiltScene, + resource_cache: &ResourceCache, + ) { self.scene = built_scene.scene; self.frame_is_valid = false; self.hit_tester_is_valid = false; + // Give the old frame builder a chance to destroy any resources. + // Right now, all this does is build a hash map of any cached + // surface tiles, that can be provided to the next frame builder. + let mut retained_tiles = FastHashMap::default(); + if let Some(frame_builder) = self.frame_builder.take() { + frame_builder.destroy( + &mut retained_tiles, + resource_cache, + ); + } + + // Provide any cached tiles from the previous frame builder to + // the newly built one. + built_scene.frame_builder.set_retained_tiles(retained_tiles); + self.frame_builder = Some(built_scene.frame_builder); + self.scratch.recycle(); let old_scrolling_states = self.clip_scroll_tree.drain(); @@ -741,7 +761,10 @@ impl RenderBackend { doc.removed_pipelines.append(&mut txn.removed_pipelines); if let Some(mut built_scene) = txn.built_scene.take() { - doc.new_async_scene_ready(built_scene); + doc.new_async_scene_ready( + built_scene, + &self.resource_cache, + ); } if let Some(tx) = result_tx { diff --git a/webrender/src/scene.rs b/webrender/src/scene.rs index 0bb35dc23d..3707a89128 100644 --- a/webrender/src/scene.rs +++ b/webrender/src/scene.rs @@ -113,13 +113,8 @@ impl SceneProperties { } } - pub fn get_float_value( - &self, - id: PropertyBindingId, - ) -> Option { - self.float_properties - .get(&id) - .cloned() + pub fn float_properties(&self) -> &FastHashMap { + &self.float_properties } } diff --git a/webrender/src/surface.rs b/webrender/src/surface.rs index c4f39fb00e..1858aa3dc0 100644 --- a/webrender/src/surface.rs +++ b/webrender/src/surface.rs @@ -58,10 +58,10 @@ fn quantize(value: f32) -> f32 { #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, PartialEq, Clone)] pub struct ScaleOffsetKey { - scale_x: f32, - scale_y: f32, - offset_x: f32, - offset_y: f32, + pub scale_x: f32, + pub scale_y: f32, + pub offset_x: f32, + pub offset_y: f32, } impl ScaleOffsetKey { From d1e19765cb232a68f5b01d5ab9f0585350930b26 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 28 Nov 2018 06:42:34 +1000 Subject: [PATCH 2/2] Address review comments --- webrender/src/frame_builder.rs | 2 -- webrender/src/picture.rs | 28 ++++++---------------------- webrender/src/prim_store.rs | 2 -- webrender/src/render_backend.rs | 3 --- 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 61a435a829..7535538e25 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -193,11 +193,9 @@ impl FrameBuilder { pub fn destroy( self, retained_tiles: &mut FastHashMap, - resource_cache: &ResourceCache, ) { self.prim_store.destroy( retained_tiles, - resource_cache, ); } diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index e49211dbeb..af3c82c867 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -191,11 +191,8 @@ impl Tile { /// Destroy a tile, optionally returning a handle and cache descriptor, /// if this surface was valid and may be useful on the next scene. - fn destroy( - self, - resource_cache: &ResourceCache, - ) -> Option<(TileDescriptor, TextureCacheHandle)> { - if self.is_valid && resource_cache.texture_cache.is_allocated(&self.handle) { + fn destroy(self) -> Option<(TileDescriptor, TextureCacheHandle)> { + if self.is_valid { Some((self.descriptor, self.handle)) } else { None @@ -424,12 +421,6 @@ impl TileCache { // coordinate system (which is the common case!) then we are effectively drawing // in a local space anyway, so don't care about that transform for the purposes // of validating the surface cache contents. - let raster_spatial_node = &frame_context - .clip_scroll_tree - .spatial_nodes[raster_spatial_node_index.0]; - let surface_spatial_node = &frame_context - .clip_scroll_tree - .spatial_nodes[surface_spatial_node_index.0]; let mut key = CoordinateSpaceMapping::::new( raster_spatial_node_index, @@ -438,10 +429,8 @@ impl TileCache { ).expect("bug: unable to get coord mapping").into(); if let TransformKey::ScaleOffset(ref mut key) = key { - if raster_spatial_node.coordinate_system_id == surface_spatial_node.coordinate_system_id { - key.offset_x = 0.0; - key.offset_y = 0.0; - } + key.offset_x = 0.0; + key.offset_y = 0.0; } key @@ -873,9 +862,7 @@ impl TileCache { // TODO(gw): Maybe it's worth keeping them around for a bit longer in // some cases? for (_, handle) in retained_tiles.drain() { - if resource_cache.texture_cache.is_allocated(&handle) { - resource_cache.texture_cache.mark_unused(&handle); - } + resource_cache.texture_cache.mark_unused(&handle); } self.dirty_region = if dirty_rect.is_empty() { @@ -1387,14 +1374,11 @@ impl PicturePrimitive { pub fn destroy( mut self, retained_tiles: &mut FastHashMap, - resource_cache: &ResourceCache, ) { if let Some(tile_cache) = self.tile_cache.take() { debug_assert!(tile_cache.old_tiles.is_empty()); for tile in tile_cache.tiles { - if let Some((descriptor, texture_handle)) = tile.destroy(resource_cache) { - retained_tiles.insert(descriptor, texture_handle); - } + retained_tiles.extend(tile.destroy()); } } } diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index e12b92b24f..6488b21963 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -2618,12 +2618,10 @@ impl PrimitiveStore { pub fn destroy( self, retained_tiles: &mut FastHashMap, - resource_cache: &ResourceCache, ) { for pic in self.pictures { pic.destroy( retained_tiles, - resource_cache, ); } } diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 949db01cc2..b7f2d9985b 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -506,7 +506,6 @@ impl Document { pub fn new_async_scene_ready( &mut self, mut built_scene: BuiltScene, - resource_cache: &ResourceCache, ) { self.scene = built_scene.scene; self.frame_is_valid = false; @@ -519,7 +518,6 @@ impl Document { if let Some(frame_builder) = self.frame_builder.take() { frame_builder.destroy( &mut retained_tiles, - resource_cache, ); } @@ -763,7 +761,6 @@ impl RenderBackend { if let Some(mut built_scene) = txn.built_scene.take() { doc.new_async_scene_ready( built_scene, - &self.resource_cache, ); }