compare.rs (15007B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 //! Dependency tracking for tile invalidation 6 //! 7 //! This module contains types and logic for tracking dependencies that affect 8 //! tile invalidation, including transform comparisons, spatial node tracking, 9 //! and primitive comparison. 10 11 use api::{ImageKey, PropertyBindingId, ColorU}; 12 use euclid::approxeq::ApproxEq; 13 use crate::invalidation::PrimitiveCompareResult; 14 use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, CoordinateSpaceMapping}; 15 use crate::internal_types::{FastHashMap, FastHashSet, FrameId}; 16 use crate::intern::ItemUid; 17 use crate::resource_cache::{ResourceCache, ImageGeneration}; 18 use crate::invalidation::cached_surface::{PrimitiveDependencyIndex, PrimitiveDescriptor, CachedSurfaceDescriptor}; 19 use peek_poke::{PeekPoke, peek_from_slice}; 20 use std::collections::hash_map::Entry; 21 22 /// A comparable transform matrix, that compares with epsilon checks. 23 #[derive(Debug, Clone)] 24 pub struct MatrixKey { 25 pub m: [f32; 16], 26 } 27 28 impl PartialEq for MatrixKey { 29 fn eq(&self, other: &Self) -> bool { 30 const EPSILON: f32 = 0.001; 31 32 // TODO(gw): It's possible that we may need to adjust the epsilon 33 // to be tighter on most of the matrix, except the 34 // translation parts? 35 for (i, j) in self.m.iter().zip(other.m.iter()) { 36 if !i.approx_eq_eps(j, &EPSILON) { 37 return false; 38 } 39 } 40 41 true 42 } 43 } 44 45 /// A comparable scale-offset, that compares with epsilon checks. 46 #[derive(Debug, Clone)] 47 pub struct ScaleOffsetKey { 48 pub sx: f32, 49 pub sy: f32, 50 pub tx: f32, 51 pub ty: f32, 52 } 53 54 impl PartialEq for ScaleOffsetKey { 55 fn eq(&self, other: &Self) -> bool { 56 const EPSILON: f32 = 0.001; 57 58 self.sx.approx_eq_eps(&other.sx, &EPSILON) && 59 self.sy.approx_eq_eps(&other.sy, &EPSILON) && 60 self.tx.approx_eq_eps(&other.tx, &EPSILON) && 61 self.ty.approx_eq_eps(&other.ty, &EPSILON) 62 } 63 } 64 65 /// A comparable / hashable version of a coordinate space mapping. Used to determine 66 /// if a transform dependency for a tile has changed. 67 #[derive(Debug, PartialEq, Clone)] 68 pub enum TransformKey { 69 Local, 70 ScaleOffset { 71 so: ScaleOffsetKey, 72 }, 73 Transform { 74 m: MatrixKey, 75 } 76 } 77 78 impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey { 79 fn from(transform: CoordinateSpaceMapping<Src, Dst>) -> TransformKey { 80 match transform { 81 CoordinateSpaceMapping::Local => { 82 TransformKey::Local 83 } 84 CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { 85 TransformKey::ScaleOffset { 86 so: ScaleOffsetKey { 87 sx: scale_offset.scale.x, 88 sy: scale_offset.scale.y, 89 tx: scale_offset.offset.x, 90 ty: scale_offset.offset.y, 91 } 92 } 93 } 94 CoordinateSpaceMapping::Transform(ref m) => { 95 TransformKey::Transform { 96 m: MatrixKey { 97 m: m.to_array(), 98 }, 99 } 100 } 101 } 102 } 103 } 104 105 /// Get the transform key for a spatial node relative to a cache spatial node. 106 pub fn get_transform_key( 107 spatial_node_index: SpatialNodeIndex, 108 cache_spatial_node_index: SpatialNodeIndex, 109 spatial_tree: &SpatialTree, 110 ) -> TransformKey { 111 spatial_tree.get_relative_transform( 112 spatial_node_index, 113 cache_spatial_node_index, 114 ).into() 115 } 116 117 118 /// Information about the state of a binding. 119 #[derive(Debug)] 120 pub struct BindingInfo<T> { 121 /// The current value retrieved from dynamic scene properties. 122 pub value: T, 123 /// True if it was changed (or is new) since the last frame build. 124 pub changed: bool, 125 } 126 127 /// Information stored in a tile descriptor for a binding. 128 #[derive(Debug, PartialEq, Clone, Copy, PeekPoke)] 129 #[cfg_attr(feature = "capture", derive(Serialize))] 130 #[cfg_attr(feature = "replay", derive(Deserialize))] 131 pub enum Binding<T> { 132 Value(T), 133 Binding(PropertyBindingId), 134 } 135 136 impl<T: Default> Default for Binding<T> { 137 fn default() -> Self { 138 Binding::Value(T::default()) 139 } 140 } 141 142 impl<T> From<api::PropertyBinding<T>> for Binding<T> { 143 fn from(binding: api::PropertyBinding<T>) -> Binding<T> { 144 match binding { 145 api::PropertyBinding::Binding(key, _) => Binding::Binding(key.id), 146 api::PropertyBinding::Value(value) => Binding::Value(value), 147 } 148 } 149 } 150 151 pub type OpacityBinding = Binding<f32>; 152 pub type OpacityBindingInfo = BindingInfo<f32>; 153 154 pub type ColorBinding = Binding<ColorU>; 155 pub type ColorBindingInfo = BindingInfo<ColorU>; 156 157 /// Types of dependencies that a primitive can have 158 #[derive(PeekPoke)] 159 pub enum PrimitiveDependency { 160 OpacityBinding { 161 binding: OpacityBinding, 162 }, 163 ColorBinding { 164 binding: ColorBinding, 165 }, 166 SpatialNode { 167 index: SpatialNodeIndex, 168 }, 169 Clip { 170 clip: ItemUid, 171 }, 172 Image { 173 image: ImageDependency, 174 }, 175 } 176 177 /// Information stored an image dependency 178 #[derive(Debug, Copy, Clone, PartialEq, PeekPoke, Default)] 179 #[cfg_attr(feature = "capture", derive(Serialize))] 180 #[cfg_attr(feature = "replay", derive(Deserialize))] 181 pub struct ImageDependency { 182 pub key: ImageKey, 183 pub generation: ImageGeneration, 184 } 185 186 impl ImageDependency { 187 pub const INVALID: ImageDependency = ImageDependency { 188 key: ImageKey::DUMMY, 189 generation: ImageGeneration::INVALID, 190 }; 191 } 192 193 /// A dependency for a transform is defined by the spatial node index + frame it was used 194 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PeekPoke, Default)] 195 #[cfg_attr(feature = "capture", derive(Serialize))] 196 #[cfg_attr(feature = "replay", derive(Deserialize))] 197 pub struct SpatialNodeKey { 198 pub spatial_node_index: SpatialNodeIndex, 199 pub frame_id: FrameId, 200 } 201 202 /// A helper for comparing spatial nodes between frames. The comparisons 203 /// are done by-value, so that if the shape of the spatial node tree 204 /// changes, invalidations aren't done simply due to the spatial node 205 /// index changing between display lists. 206 pub struct SpatialNodeComparer { 207 /// The root spatial node index of the tile cache 208 ref_spatial_node_index: SpatialNodeIndex, 209 /// Maintains a map of currently active transform keys 210 spatial_nodes: FastHashMap<SpatialNodeKey, TransformKey>, 211 /// A cache of recent comparisons between prev and current spatial nodes 212 compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>, 213 /// A set of frames that we need to retain spatial node entries for 214 referenced_frames: FastHashSet<FrameId>, 215 } 216 217 impl SpatialNodeComparer { 218 /// Construct a new comparer 219 pub fn new() -> Self { 220 SpatialNodeComparer { 221 ref_spatial_node_index: SpatialNodeIndex::INVALID, 222 spatial_nodes: FastHashMap::default(), 223 compare_cache: FastHashMap::default(), 224 referenced_frames: FastHashSet::default(), 225 } 226 } 227 228 /// Advance to the next frame 229 pub fn next_frame( 230 &mut self, 231 ref_spatial_node_index: SpatialNodeIndex, 232 ) { 233 // Drop any node information for unreferenced frames, to ensure that the 234 // hashmap doesn't grow indefinitely! 235 let referenced_frames = &self.referenced_frames; 236 self.spatial_nodes.retain(|key, _| { 237 referenced_frames.contains(&key.frame_id) 238 }); 239 240 // Update the root spatial node for this comparer 241 self.ref_spatial_node_index = ref_spatial_node_index; 242 self.compare_cache.clear(); 243 self.referenced_frames.clear(); 244 } 245 246 /// Register a transform that is used, and build the transform key for it if new. 247 pub fn register_used_transform( 248 &mut self, 249 spatial_node_index: SpatialNodeIndex, 250 frame_id: FrameId, 251 spatial_tree: &SpatialTree, 252 ) { 253 let key = SpatialNodeKey { 254 spatial_node_index, 255 frame_id, 256 }; 257 258 if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) { 259 entry.insert( 260 get_transform_key( 261 spatial_node_index, 262 self.ref_spatial_node_index, 263 spatial_tree, 264 ) 265 ); 266 } 267 } 268 269 /// Return true if the transforms for two given spatial nodes are considered equivalent 270 pub fn are_transforms_equivalent( 271 &mut self, 272 prev_spatial_node_key: &SpatialNodeKey, 273 curr_spatial_node_key: &SpatialNodeKey, 274 ) -> bool { 275 let key = (*prev_spatial_node_key, *curr_spatial_node_key); 276 let spatial_nodes = &self.spatial_nodes; 277 278 *self.compare_cache 279 .entry(key) 280 .or_insert_with(|| { 281 let prev = &spatial_nodes[&prev_spatial_node_key]; 282 let curr = &spatial_nodes[&curr_spatial_node_key]; 283 curr == prev 284 }) 285 } 286 287 /// Ensure that the comparer won't GC any nodes for a given frame id 288 pub fn retain_for_frame(&mut self, frame_id: FrameId) { 289 self.referenced_frames.insert(frame_id); 290 } 291 } 292 293 /// A key for storing primitive comparison results during tile dependency tests. 294 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] 295 pub struct PrimitiveComparisonKey { 296 pub prev_index: PrimitiveDependencyIndex, 297 pub curr_index: PrimitiveDependencyIndex, 298 } 299 300 /// A helper struct to compare a primitive and all its sub-dependencies. 301 pub struct PrimitiveComparer<'a> { 302 prev_data: &'a [u8], 303 curr_data: &'a [u8], 304 prev_frame_id: FrameId, 305 curr_frame_id: FrameId, 306 resource_cache: &'a ResourceCache, 307 spatial_node_comparer: &'a mut SpatialNodeComparer, 308 opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>, 309 color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>, 310 } 311 312 impl<'a> PrimitiveComparer<'a> { 313 pub fn new( 314 prev: &'a CachedSurfaceDescriptor, 315 curr: &'a CachedSurfaceDescriptor, 316 resource_cache: &'a ResourceCache, 317 spatial_node_comparer: &'a mut SpatialNodeComparer, 318 opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>, 319 color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>, 320 ) -> Self { 321 PrimitiveComparer { 322 prev_data: &prev.dep_data, 323 curr_data: &curr.dep_data, 324 prev_frame_id: prev.last_updated_frame_id, 325 curr_frame_id: curr.last_updated_frame_id, 326 resource_cache, 327 spatial_node_comparer, 328 opacity_bindings, 329 color_bindings, 330 } 331 } 332 333 /// Check if two primitive descriptors are the same. 334 pub fn compare_prim( 335 &mut self, 336 prev_desc: &PrimitiveDescriptor, 337 curr_desc: &PrimitiveDescriptor, 338 ) -> PrimitiveCompareResult { 339 let resource_cache = self.resource_cache; 340 let spatial_node_comparer = &mut self.spatial_node_comparer; 341 let opacity_bindings = self.opacity_bindings; 342 let color_bindings = self.color_bindings; 343 344 // Check equality of the PrimitiveDescriptor 345 if prev_desc != curr_desc { 346 return PrimitiveCompareResult::Descriptor; 347 } 348 349 let mut prev_dep_data = &self.prev_data[prev_desc.dep_offset as usize ..]; 350 let mut curr_dep_data = &self.curr_data[curr_desc.dep_offset as usize ..]; 351 352 let mut prev_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID }; 353 let mut curr_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID }; 354 355 debug_assert_eq!(prev_desc.dep_count, curr_desc.dep_count); 356 357 for _ in 0 .. prev_desc.dep_count { 358 prev_dep_data = peek_from_slice(prev_dep_data, &mut prev_dep); 359 curr_dep_data = peek_from_slice(curr_dep_data, &mut curr_dep); 360 361 match (&prev_dep, &curr_dep) { 362 (PrimitiveDependency::Clip { clip: prev }, PrimitiveDependency::Clip { clip: curr }) => { 363 if prev != curr { 364 return PrimitiveCompareResult::Clip; 365 } 366 } 367 (PrimitiveDependency::SpatialNode { index: prev }, PrimitiveDependency::SpatialNode { index: curr }) => { 368 let prev_key = SpatialNodeKey { 369 spatial_node_index: *prev, 370 frame_id: self.prev_frame_id, 371 }; 372 let curr_key = SpatialNodeKey { 373 spatial_node_index: *curr, 374 frame_id: self.curr_frame_id, 375 }; 376 if !spatial_node_comparer.are_transforms_equivalent(&prev_key, &curr_key) { 377 return PrimitiveCompareResult::Transform; 378 } 379 } 380 (PrimitiveDependency::OpacityBinding { binding: prev }, PrimitiveDependency::OpacityBinding { binding: curr }) => { 381 if prev != curr { 382 return PrimitiveCompareResult::OpacityBinding; 383 } 384 385 if let OpacityBinding::Binding(id) = curr { 386 if opacity_bindings 387 .get(id) 388 .map_or(true, |info| info.changed) { 389 return PrimitiveCompareResult::OpacityBinding; 390 } 391 } 392 } 393 (PrimitiveDependency::ColorBinding { binding: prev }, PrimitiveDependency::ColorBinding { binding: curr }) => { 394 if prev != curr { 395 return PrimitiveCompareResult::ColorBinding; 396 } 397 398 if let ColorBinding::Binding(id) = curr { 399 if color_bindings 400 .get(id) 401 .map_or(true, |info| info.changed) { 402 return PrimitiveCompareResult::ColorBinding; 403 } 404 } 405 } 406 (PrimitiveDependency::Image { image: prev }, PrimitiveDependency::Image { image: curr }) => { 407 if prev != curr { 408 return PrimitiveCompareResult::Image; 409 } 410 411 if resource_cache.get_image_generation(curr.key) != curr.generation { 412 return PrimitiveCompareResult::Image; 413 } 414 } 415 _ => { 416 // There was a mismatch between types of dependencies, so something changed 417 return PrimitiveCompareResult::Descriptor; 418 } 419 } 420 } 421 422 PrimitiveCompareResult::Equal 423 } 424 }