diff --git a/webrender/src/composite.rs b/webrender/src/composite.rs index 1e82c592cf..fab67657b6 100644 --- a/webrender/src/composite.rs +++ b/webrender/src/composite.rs @@ -9,7 +9,7 @@ use crate::batch::{resolve_image, get_buffer_kind}; use crate::gpu_cache::GpuCache; use crate::gpu_types::{ZBufferId, ZBufferIdGenerator}; use crate::internal_types::TextureSource; -use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileSurface}; +use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileId, TileSurface}; use crate::prim_store::DeferredResolve; use crate::renderer::ImageBufferKind; use crate::resource_cache::{ImageRequest, ResourceCache}; @@ -246,7 +246,40 @@ struct Occluder { device_rect: DeviceIntRect, } -/// Describes the properties that identify a tile composition uniquely. +/// The backing surface kind for a tile. Same as `TileSurface`, minus +/// the texture cache handles, visibility masks etc. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(PartialEq, Clone)] +pub enum TileSurfaceKind { + Texture, + Color { + color: ColorF, + }, + Clear, +} + +impl From<&TileSurface> for TileSurfaceKind { + fn from(surface: &TileSurface) -> Self { + match surface { + TileSurface::Texture { .. } => TileSurfaceKind::Texture, + TileSurface::Color { color } => TileSurfaceKind::Color { color: *color }, + TileSurface::Clear => TileSurfaceKind::Clear, + } + } +} + +/// Describes properties that identify a tile composition uniquely. +/// The backing surface for this tile. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(PartialEq, Clone)] +pub struct CompositeTileDescriptor { + pub tile_id: TileId, + pub surface_kind: TileSurfaceKind, +} + +/// Describes the properties that identify a surface composition uniquely. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(PartialEq, Clone)] @@ -259,6 +292,8 @@ pub struct CompositeSurfaceDescriptor { // thing that has changed is the generation of an compositor surface // image dependency. pub image_dependencies: [ImageDependency; 3], + // List of the surface information for each tile added to this virtual surface + pub tile_descriptors: Vec, } /// Describes surface properties used to composite a frame. This @@ -406,6 +441,8 @@ impl CompositeState { ) { let mut visible_opaque_tile_count = 0; let mut visible_alpha_tile_count = 0; + let mut opaque_tile_descriptors = Vec::new(); + let mut alpha_tile_descriptors = Vec::new(); for tile in tile_cache.tiles.values() { if !tile.is_visible { @@ -416,6 +453,11 @@ impl CompositeState { let device_rect = (tile.world_tile_rect * global_device_pixel_scale).round(); let surface = tile.surface.as_ref().expect("no tile surface set!"); + let descriptor = CompositeTileDescriptor { + surface_kind: surface.into(), + tile_id: tile.id, + }; + let (surface, is_opaque) = match surface { TileSurface::Color { color } => { (CompositeTileSurface::Color { color: *color }, true) @@ -433,8 +475,10 @@ impl CompositeState { }; if is_opaque { + opaque_tile_descriptors.push(descriptor); visible_opaque_tile_count += 1; } else { + alpha_tile_descriptors.push(descriptor); visible_alpha_tile_count += 1; } @@ -450,6 +494,13 @@ impl CompositeState { self.push_tile(tile, is_opaque); } + // Sort the tile descriptor lists, since iterating values in the tile_cache.tiles + // hashmap doesn't provide any ordering guarantees, but we want to detect the + // composite descriptor as equal if the tiles list is the same, regardless of + // ordering. + opaque_tile_descriptors.sort_by_key(|desc| desc.tile_id); + alpha_tile_descriptors.sort_by_key(|desc| desc.tile_id); + // Add opaque surface before any compositor surfaces if visible_opaque_tile_count > 0 { self.descriptor.surfaces.push( @@ -458,6 +509,7 @@ impl CompositeState { offset: tile_cache.device_position, clip_rect: device_clip_rect, image_dependencies: [ImageDependency::INVALID; 3], + tile_descriptors: opaque_tile_descriptors, } ); } @@ -562,6 +614,7 @@ impl CompositeState { offset: tile.rect.origin, clip_rect: tile.clip_rect, image_dependencies: external_surface.image_dependencies, + tile_descriptors: Vec::new(), } ); @@ -576,6 +629,7 @@ impl CompositeState { offset: tile_cache.device_position, clip_rect: device_clip_rect, image_dependencies: [ImageDependency::INVALID; 3], + tile_descriptors: alpha_tile_descriptors, } ); } diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 9dc072f4b7..167bf72cf2 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -122,7 +122,7 @@ use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, RectangleKey}; use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex}; use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveVisibilityFlags}; use crate::print_tree::{PrintTree, PrintTreePrinter}; -use crate::render_backend::DataStores; +use crate::render_backend::{DataStores, FrameId}; use crate::render_task_graph::RenderTaskId; use crate::render_target::RenderTargetKind; use crate::render_task::{RenderTask, RenderTaskLocation, BlurTaskCache, ClearMode}; @@ -132,6 +132,7 @@ use crate::spatial_tree::CoordinateSystemId; use smallvec::SmallVec; use std::{mem, u8, marker, u32}; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::collections::hash_map::Entry; use crate::texture_cache::TextureCacheHandle; use crate::util::{MaxRect, VecHelper, RectHelpers, MatrixHelpers}; use crate::filterdata::{FilterDataHandle}; @@ -264,7 +265,7 @@ pub struct PictureCacheState { /// The tiles retained by this picture cache. pub tiles: FastHashMap>, /// State of the spatial nodes from previous frame - spatial_nodes: FastHashMap, + spatial_node_comparer: SpatialNodeComparer, /// State of opacity bindings from previous frame opacity_bindings: FastHashMap, /// State of color bindings from previous frame @@ -281,6 +282,8 @@ pub struct PictureCacheState { pub external_native_surface_cache: FastHashMap, /// The retained virtual offset for this slice between display lists. virtual_offset: DeviceIntPoint, + /// Current frame ID of this picture cache + frame_id: FrameId, } pub struct PictureCacheRecycledAllocations { @@ -456,13 +459,104 @@ pub type OpacityBindingInfo = BindingInfo; pub type ColorBinding = Binding; pub type ColorBindingInfo = BindingInfo; -/// Information about the state of a spatial node value -#[derive(Debug)] -pub struct SpatialNodeDependency { - /// The current value retrieved from the spatial tree. - value: TransformKey, - /// True if it was changed (or is new) since the last frame build. - changed: bool, +/// A dependency for a transform is defined by the spatial node index + frame it was used +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SpatialNodeKey { + spatial_node_index: SpatialNodeIndex, + frame_id: FrameId, +} + +/// A helper for comparing spatial nodes between frames. The comparisons +/// are done by-value, so that if the shape of the spatial node tree +/// changes, invalidations aren't done simply due to the spatial node +/// index changing between display lists. +struct SpatialNodeComparer { + /// The root spatial node index of the tile cache + ref_spatial_node_index: SpatialNodeIndex, + /// Maintains a map of currently active transform keys + spatial_nodes: FastHashMap, + /// A cache of recent comparisons between prev and current spatial nodes + compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>, + /// A set of frames that we need to retain spatial node entries for + referenced_frames: FastHashSet, +} + +impl SpatialNodeComparer { + /// Construct a new comparer + fn new() -> Self { + SpatialNodeComparer { + ref_spatial_node_index: ROOT_SPATIAL_NODE_INDEX, + spatial_nodes: FastHashMap::default(), + compare_cache: FastHashMap::default(), + referenced_frames: FastHashSet::default(), + } + } + + /// Advance to the next frame + fn next_frame( + &mut self, + ref_spatial_node_index: SpatialNodeIndex, + ) { + // Drop any node information for unreferenced frames, to ensure that the + // hashmap doesn't grow indefinitely! + let referenced_frames = &self.referenced_frames; + self.spatial_nodes.retain(|key, _| { + referenced_frames.contains(&key.frame_id) + }); + + // Update the root spatial node for this comparer + self.ref_spatial_node_index = ref_spatial_node_index; + self.compare_cache.clear(); + self.referenced_frames.clear(); + } + + /// Register a transform that is used, and build the transform key for it if new. + fn register_used_transform( + &mut self, + spatial_node_index: SpatialNodeIndex, + frame_id: FrameId, + spatial_tree: &SpatialTree, + ) { + let key = SpatialNodeKey { + spatial_node_index, + frame_id, + }; + + if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) { + entry.insert( + get_transform_key( + spatial_node_index, + self.ref_spatial_node_index, + spatial_tree, + ) + ); + } + } + + /// Return true if the transforms for two given spatial nodes are considered equivalent + fn are_transforms_equivalent( + &mut self, + prev_spatial_node_key: &SpatialNodeKey, + curr_spatial_node_key: &SpatialNodeKey, + ) -> bool { + let key = (*prev_spatial_node_key, *curr_spatial_node_key); + let spatial_nodes = &self.spatial_nodes; + + *self.compare_cache + .entry(key) + .or_insert_with(|| { + let prev = &spatial_nodes[&prev_spatial_node_key]; + let curr = &spatial_nodes[&curr_spatial_node_key]; + curr == prev + }) + } + + /// Ensure that the comparer won't GC any nodes for a given frame id + fn retain_for_frame(&mut self, frame_id: FrameId) { + self.referenced_frames.insert(frame_id); + } } // Immutable context passed to picture cache tiles during pre_update @@ -482,6 +576,9 @@ struct TilePreUpdateContext { /// Current size of tiles in picture units. tile_size: PictureSize, + + /// The current frame id for this picture cache + frame_id: FrameId, } // Immutable context passed to picture cache tiles during post_update @@ -498,9 +595,6 @@ struct TilePostUpdateContext<'a> { /// The calculated backdrop information for this cache instance. backdrop: BackdropInfo, - /// Information about transform node differences from last frame. - spatial_nodes: &'a FastHashMap, - /// Information about opacity bindings from the picture cache. opacity_bindings: &'a FastHashMap, @@ -537,6 +631,9 @@ struct TilePostUpdateState<'a> { /// A cache of comparison results to avoid re-computation during invalidation. compare_cache: &'a mut FastHashMap, + + /// Information about transform node differences from last frame. + spatial_node_comparer: &'a mut SpatialNodeComparer, } /// Information about the dependencies of a single primitive instance. @@ -596,7 +693,7 @@ impl PrimitiveDependencyInfo { /// A stable ID for a given tile, to help debugging. These are also used /// as unique identifiers for tile surfaces when using a native compositor. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct TileId(pub usize); @@ -759,7 +856,7 @@ pub enum PrimitiveCompareResultDetail { }, /// The value of the transform changed Transform { - detail: CompareHelperResult, + detail: CompareHelperResult, }, /// An image dependency was dirty Image { @@ -894,6 +991,9 @@ pub struct Tile { fg_local_valid_rect: PictureRect, /// z-buffer id for this tile, which is one of z_id_opaque or z_id_alpha, depending on tile opacity pub z_id: ZBufferId, + /// The last frame this tile had its dependencies updated (dependency updating is + /// skipped if a tile is off-screen). + pub last_updated_frame_id: FrameId, } impl Tile { @@ -923,6 +1023,7 @@ impl Tile { bg_local_valid_rect: PictureRect::zero(), fg_local_valid_rect: PictureRect::zero(), z_id: ZBufferId::invalid(), + last_updated_frame_id: FrameId::INVALID, } } @@ -949,7 +1050,7 @@ impl Tile { &self.prev_descriptor, &self.current_descriptor, state.resource_cache, - ctx.spatial_nodes, + state.spatial_node_comparer, ctx.opacity_bindings, ctx.color_bindings, ); @@ -1086,6 +1187,10 @@ impl Tile { ); self.current_descriptor.clear(); self.root.clear(self.local_tile_rect); + + // Since this tile is determined to be visible, it will get updated + // dependencies, so update the frame id we are storing dependencies for. + self.last_updated_frame_id = ctx.frame_id; } /// Add dependencies for a given primitive to this tile. @@ -1130,7 +1235,14 @@ impl Tile { self.current_descriptor.clips.extend_from_slice(&info.clips); // Include any transforms that this primitive depends on. - self.current_descriptor.transforms.extend_from_slice(&info.spatial_nodes); + for spatial_node_index in &info.spatial_nodes { + self.current_descriptor.transforms.push( + SpatialNodeKey { + spatial_node_index: *spatial_node_index, + frame_id: self.last_updated_frame_id, + } + ); + } // Include any color bindings this primitive depends on. if info.color_binding.is_some() { @@ -1205,6 +1317,12 @@ impl Tile { state: &mut TilePostUpdateState, frame_context: &FrameVisibilityContext, ) -> bool { + // Register the frame id of this tile with the spatial node comparer, to ensure + // that it doesn't GC any spatial nodes from the comparer that are referenced + // by this tile. Must be done before we early exit below, so that we retain + // spatial node info even for tiles that are currently not visible. + state.spatial_node_comparer.retain_for_frame(self.last_updated_frame_id); + // If tile is not visible, just early out from here - we don't update dependencies // so don't want to invalidate, merge, split etc. The tile won't need to be drawn // (and thus updated / invalidated) until it is on screen again. @@ -1519,9 +1637,9 @@ impl<'a, T> CompareHelper<'a, T> where T: Copy + PartialEq { &self, prev_count: u8, curr_count: u8, - f: F, + mut f: F, opt_detail: Option<&mut CompareHelperResult>, - ) -> bool where F: Fn(&T) -> bool { + ) -> bool where F: FnMut(&T, &T) -> bool { // If the number of items is different, trivial reject. if prev_count != curr_count { if let Some(detail) = opt_detail { *detail = CompareHelperResult::Count{ prev_count, curr_count }; } @@ -1546,14 +1664,7 @@ impl<'a, T> CompareHelper<'a, T> where T: Copy + PartialEq { let prev_items = &self.prev_items[self.offset_prev .. end_prev]; for (curr, prev) in curr_items.iter().zip(prev_items.iter()) { - if prev != curr { - if let Some(detail) = opt_detail { - *detail = CompareHelperResult::NotEqual{ prev: *prev, curr: *curr }; - } - return false; - } - - if f(curr) { + if !f(prev, curr) { if let Some(detail) = opt_detail { *detail = CompareHelperResult::PredicateTrue{ curr: *curr }; } return false; } @@ -1597,7 +1708,7 @@ pub struct TileDescriptor { /// List of the effects of transforms that we care about /// tracking for this tile. - transforms: Vec, + transforms: Vec, /// Picture space rect that contains valid pixels region of this tile. local_valid_rect: PictureRect, @@ -2186,16 +2297,8 @@ pub struct TileCacheInstance { opacity_bindings: FastHashMap, /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating. old_opacity_bindings: FastHashMap, - /// List of spatial nodes, with some extra information - /// about whether they changed since last frame. - spatial_nodes: FastHashMap, - /// Switch back and forth between old and new spatial nodes hashmaps to avoid re-allocating. - old_spatial_nodes: FastHashMap, - /// A set of spatial nodes that primitives / clips depend on found - /// during dependency creation. This is used to avoid trying to - /// calculate invalid relative transforms when building the spatial - /// nodes hash above. - used_spatial_nodes: FastHashSet, + /// A helper to compare transforms between previous and current frame. + spatial_node_comparer: SpatialNodeComparer, /// List of color bindings, with some extra information /// about whether they changed since last frame. color_bindings: FastHashMap, @@ -2269,6 +2372,8 @@ pub struct TileCacheInstance { pub z_id_opaque: ZBufferId, /// A cache of compositor surfaces that are retained between frames pub external_native_surface_cache: FastHashMap, + /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting + frame_id: FrameId, } impl TileCacheInstance { @@ -2298,9 +2403,7 @@ impl TileCacheInstance { ), opacity_bindings: FastHashMap::default(), old_opacity_bindings: FastHashMap::default(), - spatial_nodes: FastHashMap::default(), - old_spatial_nodes: FastHashMap::default(), - used_spatial_nodes: FastHashSet::default(), + spatial_node_comparer: SpatialNodeComparer::new(), color_bindings: FastHashMap::default(), old_color_bindings: FastHashMap::default(), dirty_region: DirtyRegion::new(), @@ -2332,6 +2435,7 @@ impl TileCacheInstance { external_surfaces: Vec::new(), z_id_opaque: ZBufferId::invalid(), external_native_surface_cache: FastHashMap::default(), + frame_id: FrameId::INVALID, } } @@ -2455,13 +2559,14 @@ impl TileCacheInstance { 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.spatial_node_comparer = prev_state.spatial_node_comparer; 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 = prev_state.native_surface; self.external_native_surface_cache = prev_state.external_native_surface_cache; self.virtual_offset = prev_state.virtual_offset; + self.frame_id = prev_state.frame_id; fn recycle_map( ideal_len: usize, @@ -2494,6 +2599,14 @@ impl TileCacheInstance { ); } + // Advance the current frame ID counter for this picture cache (must be done + // after any retained prev state is taken above). + self.frame_id.advance(); + + // Notify the spatial node comparer that a new frame has started, and the + // current reference spatial node for this tile cache. + self.spatial_node_comparer.next_frame(self.spatial_node_index); + // At the start of the frame, step through each current compositor surface // and mark it as unused. Later, this is used to free old compositor surfaces. // TODO(gw): In future, we might make this more sophisticated - for example, @@ -2757,6 +2870,7 @@ impl TileCacheInstance { background_color: self.background_color, global_screen_world_rect: frame_context.global_screen_world_rect, tile_size: self.tile_size, + frame_id: self.frame_id, }; // Pre-update each tile @@ -3326,7 +3440,13 @@ impl TileCacheInstance { } // Record any new spatial nodes in the used list. - self.used_spatial_nodes.extend(&prim_info.spatial_nodes); + for spatial_node_index in &prim_info.spatial_nodes { + self.spatial_node_comparer.register_used_transform( + *spatial_node_index, + self.frame_id, + frame_context.spatial_tree, + ); + } // Truncate the lengths of dependency arrays to the max size we can handle. // Any arrays this size or longer will invalidate every frame. @@ -3485,40 +3605,6 @@ impl TileCacheInstance { frame_state.composite_state.dirty_rects_are_valid = false; } - // Diff the state of the spatial nodes between last frame build and now. - mem::swap(&mut self.spatial_nodes, &mut self.old_spatial_nodes); - - // TODO(gw): Maybe remove the used_spatial_nodes set and just mutate / create these - // diffs inside add_prim_dependency? - self.spatial_nodes.clear(); - for spatial_node_index in self.used_spatial_nodes.drain() { - // Get the current relative transform. - let mut value = get_transform_key( - spatial_node_index, - self.spatial_node_index, - frame_context.spatial_tree, - ); - - // Check if the transform has changed from last frame - let mut changed = true; - if let Some(old_info) = self.old_spatial_nodes.remove(&spatial_node_index) { - if old_info.value == value { - // Since the transform key equality check applies epsilon, if we - // consider the value to be the same, store that old value to avoid - // missing very slow drifts in the value over time. - // TODO(gw): We should change ComparableVec to use a trait for comparison - // rather than PartialEq. - value = old_info.value; - changed = false; - } - } - - self.spatial_nodes.insert(spatial_node_index, SpatialNodeDependency { - changed, - value, - }); - } - let pic_to_world_mapper = SpaceMapper::new_with_target( ROOT_SPATIAL_NODE_INDEX, self.spatial_node_index, @@ -3534,7 +3620,6 @@ impl TileCacheInstance { global_device_pixel_scale: frame_context.global_device_pixel_scale, local_clip_rect: self.local_clip_rect, backdrop: self.backdrop, - spatial_nodes: &self.spatial_nodes, opacity_bindings: &self.opacity_bindings, color_bindings: &self.color_bindings, current_tile_size: self.current_tile_size, @@ -3548,6 +3633,7 @@ impl TileCacheInstance { resource_cache: frame_state.resource_cache, composite_state: frame_state.composite_state, compare_cache: &mut self.compare_cache, + spatial_node_comparer: &mut self.spatial_node_comparer, }; // Step through each tile and invalidate if the dependencies have changed. Determine @@ -4403,7 +4489,7 @@ impl PicturePrimitive { tile_cache.slice, PictureCacheState { tiles: tile_cache.tiles, - spatial_nodes: tile_cache.spatial_nodes, + spatial_node_comparer: tile_cache.spatial_node_comparer, opacity_bindings: tile_cache.opacity_bindings, color_bindings: tile_cache.color_bindings, root_transform: tile_cache.root_transform, @@ -4411,6 +4497,7 @@ impl PicturePrimitive { native_surface: tile_cache.native_surface, external_native_surface_cache: tile_cache.external_native_surface_cache, virtual_offset: tile_cache.virtual_offset, + frame_id: tile_cache.frame_id, allocations: PictureCacheRecycledAllocations { old_opacity_bindings: tile_cache.old_opacity_bindings, old_color_bindings: tile_cache.old_color_bindings, @@ -6171,12 +6258,12 @@ impl ImageDependency { /// A helper struct to compare a primitive and all its sub-dependencies. struct PrimitiveComparer<'a> { clip_comparer: CompareHelper<'a, ItemUid>, - transform_comparer: CompareHelper<'a, SpatialNodeIndex>, + transform_comparer: CompareHelper<'a, SpatialNodeKey>, image_comparer: CompareHelper<'a, ImageDependency>, opacity_comparer: CompareHelper<'a, OpacityBinding>, color_comparer: CompareHelper<'a, ColorBinding>, resource_cache: &'a ResourceCache, - spatial_nodes: &'a FastHashMap, + spatial_node_comparer: &'a mut SpatialNodeComparer, opacity_bindings: &'a FastHashMap, color_bindings: &'a FastHashMap, } @@ -6186,7 +6273,7 @@ impl<'a> PrimitiveComparer<'a> { prev: &'a TileDescriptor, curr: &'a TileDescriptor, resource_cache: &'a ResourceCache, - spatial_nodes: &'a FastHashMap, + spatial_node_comparer: &'a mut SpatialNodeComparer, opacity_bindings: &'a FastHashMap, color_bindings: &'a FastHashMap, ) -> Self { @@ -6222,7 +6309,7 @@ impl<'a> PrimitiveComparer<'a> { opacity_comparer, color_comparer, resource_cache, - spatial_nodes, + spatial_node_comparer, opacity_bindings, color_bindings, } @@ -6260,7 +6347,7 @@ impl<'a> PrimitiveComparer<'a> { opt_detail: Option<&mut PrimitiveCompareResultDetail>, ) -> PrimitiveCompareResult { let resource_cache = self.resource_cache; - let spatial_nodes = self.spatial_nodes; + let spatial_node_comparer = &mut self.spatial_node_comparer; let opacity_bindings = self.opacity_bindings; let color_bindings = self.color_bindings; @@ -6277,8 +6364,8 @@ impl<'a> PrimitiveComparer<'a> { if !self.clip_comparer.is_same( prev.clip_dep_count, curr.clip_dep_count, - |_| { - false + |prev, curr| { + prev == curr }, if opt_detail.is_some() { Some(&mut clip_result) } else { None } ) { @@ -6291,8 +6378,8 @@ impl<'a> PrimitiveComparer<'a> { if !self.transform_comparer.is_same( prev.transform_dep_count, curr.transform_dep_count, - |curr| { - spatial_nodes[curr].changed + |prev, curr| { + spatial_node_comparer.are_transforms_equivalent(prev, curr) }, if opt_detail.is_some() { Some(&mut transform_result) } else { None }, ) { @@ -6307,8 +6394,9 @@ impl<'a> PrimitiveComparer<'a> { if !self.image_comparer.is_same( prev.image_dep_count, curr.image_dep_count, - |curr| { - resource_cache.get_image_generation(curr.key) != curr.generation + |prev, curr| { + prev == curr && + resource_cache.get_image_generation(curr.key) == curr.generation }, if opt_detail.is_some() { Some(&mut image_result) } else { None }, ) { @@ -6323,16 +6411,20 @@ impl<'a> PrimitiveComparer<'a> { if !self.opacity_comparer.is_same( prev.opacity_binding_dep_count, curr.opacity_binding_dep_count, - |curr| { + |prev, curr| { + if prev != curr { + return false; + } + if let OpacityBinding::Binding(id) = curr { if opacity_bindings .get(id) .map_or(true, |info| info.changed) { - return true; + return false; } } - false + true }, if opt_detail.is_some() { Some(&mut bind_result) } else { None }, ) { @@ -6347,12 +6439,16 @@ impl<'a> PrimitiveComparer<'a> { if !self.color_comparer.is_same( prev.color_binding_dep_count, curr.color_binding_dep_count, - |curr| { + |prev, curr| { + if prev != curr { + return false; + } + if let ColorBinding::Binding(id) = curr { if color_bindings .get(id) .map_or(true, |info| info.changed) { - return true; + return false; } }