mod.rs (150374B)
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 //! Tile cache types and descriptors 6 //! 7 //! This module contains the core tile caching infrastructure including: 8 //! - Tile identification and coordinate types 9 //! - Tile descriptors that track primitive dependencies 10 //! - Comparison results for invalidation tracking 11 12 // Existing tile cache slice builder (was previously tile_cache.rs) 13 pub mod slice_builder; 14 15 use api::{AlphaType, BorderRadius, ClipMode, ColorF, ColorDepth, DebugFlags, ImageKey, ImageRendering}; 16 use api::{PropertyBindingId, PrimitiveFlags, YuvFormat, YuvRangedColorSpace}; 17 use api::units::*; 18 use crate::clip::{ClipNodeId, ClipLeafId, ClipItemKind, ClipSpaceConversion, ClipChainInstance, ClipStore}; 19 use crate::composite::{CompositorKind, CompositeState, CompositorSurfaceKind, ExternalSurfaceDescriptor}; 20 use crate::composite::{ExternalSurfaceDependency, NativeSurfaceId, NativeTileId}; 21 use crate::composite::{CompositorClipIndex, CompositorTransformIndex}; 22 use crate::composite::{CompositeTileDescriptor, CompositeTile}; 23 use crate::gpu_types::ZBufferId; 24 use crate::internal_types::{FastHashMap, FrameId, Filter}; 25 use crate::invalidation::{InvalidationReason, DirtyRegion, PrimitiveCompareResult}; 26 use crate::invalidation::cached_surface::{CachedSurface, TileUpdateDirtyContext, TileUpdateDirtyState, PrimitiveDependencyInfo}; 27 use crate::invalidation::compare::{PrimitiveDependency, ImageDependency}; 28 use crate::invalidation::compare::{SpatialNodeComparer, PrimitiveComparisonKey}; 29 use crate::invalidation::compare::{OpacityBindingInfo, ColorBindingInfo}; 30 use crate::picture::{SurfaceTextureDescriptor, PictureCompositeMode, SurfaceIndex, clamp}; 31 use crate::picture::{get_relative_scale_offset, PicturePrimitive}; 32 use crate::picture::MAX_COMPOSITOR_SURFACES_SIZE; 33 use crate::prim_store::{PrimitiveInstance, PrimitiveInstanceKind, PrimitiveScratchBuffer, PictureIndex}; 34 use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveTemplateKind}; 35 use crate::print_tree::{PrintTreePrinter, PrintTree}; 36 use crate::{profiler, render_backend::DataStores}; 37 use crate::profiler::TransactionProfile; 38 use crate::renderer::GpuBufferBuilderF; 39 use crate::resource_cache::{ResourceCache, ImageRequest}; 40 use crate::scene_building::SliceFlags; 41 use crate::space::SpaceMapper; 42 use crate::spatial_tree::{SpatialNodeIndex, SpatialTree}; 43 use crate::surface::{SubpixelMode, SurfaceInfo}; 44 use crate::util::{ScaleOffset, MatrixHelpers, MaxRect}; 45 use crate::visibility::{FrameVisibilityContext, FrameVisibilityState, VisibilityState, PrimitiveVisibilityFlags}; 46 use euclid::approxeq::ApproxEq; 47 use euclid::Box2D; 48 use peek_poke::{PeekPoke, ensure_red_zone}; 49 use std::fmt::{Display, Error, Formatter}; 50 use std::{marker, mem}; 51 use std::sync::atomic::{AtomicUsize, Ordering}; 52 53 pub use self::slice_builder::{ 54 TileCacheBuilder, TileCacheConfig, 55 PictureCacheDebugInfo, SliceDebugInfo, DirtyTileDebugInfo, TileDebugInfo, 56 }; 57 58 pub use api::units::TileOffset; 59 pub use api::units::TileRange as TileRect; 60 61 /// The maximum number of compositor surfaces that are allowed per picture cache. This 62 /// is an arbitrary number that should be enough for common cases, but low enough to 63 /// prevent performance and memory usage drastically degrading in pathological cases. 64 pub const MAX_COMPOSITOR_SURFACES: usize = 4; 65 66 /// The size in device pixels of a normal cached tile. 67 pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize { 68 width: 1024, 69 height: 512, 70 _unit: marker::PhantomData, 71 }; 72 73 /// The size in device pixels of a tile for horizontal scroll bars 74 pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize { 75 width: 1024, 76 height: 32, 77 _unit: marker::PhantomData, 78 }; 79 80 /// The size in device pixels of a tile for vertical scroll bars 81 pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize { 82 width: 32, 83 height: 1024, 84 _unit: marker::PhantomData, 85 }; 86 87 /// The maximum size per axis of a surface, in DevicePixel coordinates. 88 /// Render tasks larger than this size are scaled down to fit, which may cause 89 /// some blurriness. 90 pub const MAX_SURFACE_SIZE: usize = 4096; 91 92 /// Used to get unique tile IDs, even when the tile cache is 93 /// destroyed between display lists / scenes. 94 static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0); 95 96 /// A unique identifier for a tile. These are stable across display lists and 97 /// scenes. 98 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)] 99 #[cfg_attr(feature = "capture", derive(Serialize))] 100 #[cfg_attr(feature = "replay", derive(Deserialize))] 101 pub struct TileId(pub usize); 102 103 impl TileId { 104 pub fn new() -> TileId { 105 TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed)) 106 } 107 } 108 109 // Internal function used by picture.rs for creating TileIds 110 #[doc(hidden)] 111 pub fn next_tile_id() -> usize { 112 NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed) 113 } 114 115 /// Uniquely identifies a tile within a picture cache slice 116 #[cfg_attr(feature = "capture", derive(Serialize))] 117 #[cfg_attr(feature = "replay", derive(Deserialize))] 118 #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] 119 pub struct TileKey { 120 // Tile index (x,y) 121 pub tile_offset: TileOffset, 122 // Sub-slice (z) 123 pub sub_slice_index: SubSliceIndex, 124 } 125 126 /// Defines which sub-slice (effectively a z-index) a primitive exists on within 127 /// a picture cache instance. 128 #[cfg_attr(feature = "capture", derive(Serialize))] 129 #[cfg_attr(feature = "replay", derive(Deserialize))] 130 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PeekPoke)] 131 pub struct SubSliceIndex(pub u8); 132 133 impl SubSliceIndex { 134 pub const DEFAULT: SubSliceIndex = SubSliceIndex(0); 135 136 pub fn new(index: usize) -> Self { 137 SubSliceIndex(index as u8) 138 } 139 140 /// Return true if this sub-slice is the primary sub-slice (for now, we assume 141 /// that only the primary sub-slice may be opaque and support subpixel AA, for example). 142 pub fn is_primary(&self) -> bool { 143 self.0 == 0 144 } 145 146 /// Get an array index for this sub-slice 147 pub fn as_usize(&self) -> usize { 148 self.0 as usize 149 } 150 } 151 152 /// The key that identifies a tile cache instance. For now, it's simple the index of 153 /// the slice as it was created during scene building. 154 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 155 #[cfg_attr(feature = "capture", derive(Serialize))] 156 #[cfg_attr(feature = "replay", derive(Deserialize))] 157 pub struct SliceId(usize); 158 159 impl SliceId { 160 pub fn new(index: usize) -> Self { 161 SliceId(index) 162 } 163 } 164 165 /// Information that is required to reuse or create a new tile cache. Created 166 /// during scene building and passed to the render backend / frame builder. 167 pub struct TileCacheParams { 168 // The current debug flags for the system. 169 pub debug_flags: DebugFlags, 170 // Index of the slice (also effectively the key of the tile cache, though we use SliceId where that matters) 171 pub slice: usize, 172 // Flags describing content of this cache (e.g. scrollbars) 173 pub slice_flags: SliceFlags, 174 // The anchoring spatial node / scroll root 175 pub spatial_node_index: SpatialNodeIndex, 176 // The space in which visibility/invalidation/clipping computations are done. 177 pub visibility_node_index: SpatialNodeIndex, 178 // Optional background color of this tilecache. If present, can be used as an optimization 179 // to enable opaque blending and/or subpixel AA in more places. 180 pub background_color: Option<ColorF>, 181 // Node in the clip-tree that defines where we exclude clips from child prims 182 pub shared_clip_node_id: ClipNodeId, 183 // Clip leaf that is used to build the clip-chain for this tile cache. 184 pub shared_clip_leaf_id: Option<ClipLeafId>, 185 // Virtual surface sizes are always square, so this represents both the width and height 186 pub virtual_surface_size: i32, 187 // The number of Image surfaces that are being requested for this tile cache. 188 // This is only a suggestion - the tile cache will clamp this as a reasonable number 189 // and only promote a limited number of surfaces. 190 pub image_surface_count: usize, 191 // The number of YuvImage surfaces that are being requested for this tile cache. 192 // This is only a suggestion - the tile cache will clamp this as a reasonable number 193 // and only promote a limited number of surfaces. 194 pub yuv_image_surface_count: usize, 195 } 196 197 /// The backing surface for this tile. 198 #[derive(Debug)] 199 pub enum TileSurface { 200 Texture { 201 /// Descriptor for the surface that this tile draws into. 202 descriptor: SurfaceTextureDescriptor, 203 }, 204 Color { 205 color: ColorF, 206 }, 207 } 208 209 impl TileSurface { 210 pub fn kind(&self) -> &'static str { 211 match *self { 212 TileSurface::Color { .. } => "Color", 213 TileSurface::Texture { .. } => "Texture", 214 } 215 } 216 } 217 218 /// Information about a cached tile. 219 pub struct Tile { 220 /// The grid position of this tile within the picture cache 221 pub tile_offset: TileOffset, 222 /// The current world rect of this tile. 223 pub world_tile_rect: WorldRect, 224 /// The device space dirty rect for this tile. 225 /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future, 226 /// expose these as multiple dirty rects, which will help in some cases. 227 pub device_dirty_rect: DeviceRect, 228 /// World space rect that contains valid pixels region of this tile. 229 pub world_valid_rect: WorldRect, 230 /// Device space rect that contains valid pixels region of this tile. 231 pub device_valid_rect: DeviceRect, 232 /// Handle to the backing surface for this tile. 233 pub surface: Option<TileSurface>, 234 /// If true, this tile intersects with the currently visible screen 235 /// rect, and will be drawn. 236 pub is_visible: bool, 237 /// The tile id is stable between display lists and / or frames, 238 /// if the tile is retained. Useful for debugging tile evictions. 239 pub id: TileId, 240 /// If true, the tile was determined to be opaque, which means blending 241 /// can be disabled when drawing it. 242 pub is_opaque: bool, 243 /// z-buffer id for this tile 244 pub z_id: ZBufferId, 245 /// Cached surface state (content tracking, invalidation, dependencies) 246 pub cached_surface: CachedSurface, 247 } 248 249 impl Tile { 250 /// Construct a new, invalid tile. 251 fn new(tile_offset: TileOffset) -> Self { 252 let id = TileId(crate::tile_cache::next_tile_id()); 253 254 Tile { 255 tile_offset, 256 world_tile_rect: WorldRect::zero(), 257 world_valid_rect: WorldRect::zero(), 258 device_valid_rect: DeviceRect::zero(), 259 device_dirty_rect: DeviceRect::zero(), 260 surface: None, 261 is_visible: false, 262 id, 263 is_opaque: false, 264 z_id: ZBufferId::invalid(), 265 cached_surface: CachedSurface::new(), 266 } 267 } 268 269 /// Print debug information about this tile to a tree printer. 270 fn print(&self, pt: &mut dyn PrintTreePrinter) { 271 pt.new_level(format!("Tile {:?}", self.id)); 272 pt.add_item(format!("local_rect: {:?}", self.cached_surface.local_rect)); 273 self.cached_surface.print(pt); 274 pt.end_level(); 275 } 276 277 /// Invalidate a tile based on change in content. This 278 /// must be called even if the tile is not currently 279 /// visible on screen. We might be able to improve this 280 /// later by changing how ComparableVec is used. 281 fn update_content_validity( 282 &mut self, 283 ctx: &TileUpdateDirtyContext, 284 state: &mut TileUpdateDirtyState, 285 frame_context: &FrameVisibilityContext, 286 ) { 287 self.cached_surface.update_content_validity( 288 ctx, 289 state, 290 frame_context, 291 ); 292 } 293 294 /// Invalidate this tile. If `invalidation_rect` is None, the entire 295 /// tile is invalidated. 296 pub fn invalidate( 297 &mut self, 298 invalidation_rect: Option<PictureRect>, 299 reason: InvalidationReason, 300 ) { 301 self.cached_surface.invalidate(invalidation_rect, reason); 302 } 303 304 /// Called during pre_update of a tile cache instance. Allows the 305 /// tile to setup state before primitive dependency calculations. 306 fn pre_update( 307 &mut self, 308 ctx: &TilePreUpdateContext, 309 ) { 310 self.cached_surface.local_rect = PictureRect::new( 311 PicturePoint::new( 312 self.tile_offset.x as f32 * ctx.tile_size.width, 313 self.tile_offset.y as f32 * ctx.tile_size.height, 314 ), 315 PicturePoint::new( 316 (self.tile_offset.x + 1) as f32 * ctx.tile_size.width, 317 (self.tile_offset.y + 1) as f32 * ctx.tile_size.height, 318 ), 319 ); 320 321 self.world_tile_rect = ctx.pic_to_world_mapper 322 .map(&self.cached_surface.local_rect) 323 .expect("bug: map local tile rect"); 324 325 // Check if this tile is currently on screen. 326 self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect); 327 328 // Delegate to CachedSurface for content tracking setup 329 self.cached_surface.pre_update( 330 ctx.background_color, 331 self.cached_surface.local_rect, 332 ctx.frame_id, 333 self.is_visible, 334 ); 335 } 336 337 /// Add dependencies for a given primitive to this tile. 338 fn add_prim_dependency( 339 &mut self, 340 info: &PrimitiveDependencyInfo, 341 ) { 342 // If this tile isn't currently visible, we don't want to update the dependencies 343 // for this tile, as an optimization, since it won't be drawn anyway. 344 if !self.is_visible { 345 return; 346 } 347 348 self.cached_surface.add_prim_dependency(info, self.cached_surface.local_rect); 349 } 350 351 /// Called during tile cache instance post_update. Allows invalidation and dirty 352 /// rect calculation after primitive dependencies have been updated. 353 fn update_dirty_and_valid_rects( 354 &mut self, 355 ctx: &TileUpdateDirtyContext, 356 state: &mut TileUpdateDirtyState, 357 frame_context: &FrameVisibilityContext, 358 ) { 359 // Ensure peek-poke constraint is met, that `dep_data` is large enough 360 ensure_red_zone::<PrimitiveDependency>(&mut self.cached_surface.current_descriptor.dep_data); 361 362 // Register the frame id of this tile with the spatial node comparer, to ensure 363 // that it doesn't GC any spatial nodes from the comparer that are referenced 364 // by this tile. Must be done before we early exit below, so that we retain 365 // spatial node info even for tiles that are currently not visible. 366 state.spatial_node_comparer.retain_for_frame(self.cached_surface.current_descriptor.last_updated_frame_id); 367 368 // If tile is not visible, just early out from here - we don't update dependencies 369 // so don't want to invalidate, merge, split etc. The tile won't need to be drawn 370 // (and thus updated / invalidated) until it is on screen again. 371 if !self.is_visible { 372 return; 373 } 374 375 // Calculate the overall valid rect for this tile. 376 self.cached_surface.current_descriptor.local_valid_rect = self.cached_surface.local_valid_rect; 377 378 // TODO(gw): In theory, the local tile rect should always have an 379 // intersection with the overall picture rect. In practice, 380 // due to some accuracy issues with how fract_offset (and 381 // fp accuracy) are used in the calling method, this isn't 382 // always true. In this case, it's safe to set the local 383 // valid rect to zero, which means it will be clipped out 384 // and not affect the scene. In future, we should fix the 385 // accuracy issue above, so that this assumption holds, but 386 // it shouldn't have any noticeable effect on performance 387 // or memory usage (textures should never get allocated). 388 self.cached_surface.current_descriptor.local_valid_rect = self.cached_surface.local_rect 389 .intersection(&ctx.local_rect) 390 .and_then(|r| r.intersection(&self.cached_surface.current_descriptor.local_valid_rect)) 391 .unwrap_or_else(PictureRect::zero); 392 393 // The device_valid_rect is referenced during `update_content_validity` so it 394 // must be updated here first. 395 self.world_valid_rect = ctx.pic_to_world_mapper 396 .map(&self.cached_surface.current_descriptor.local_valid_rect) 397 .expect("bug: map local valid rect"); 398 399 // The device rect is guaranteed to be aligned on a device pixel - the round 400 // is just to deal with float accuracy. However, the valid rect is not 401 // always aligned to a device pixel. To handle this, round out to get all 402 // required pixels, and intersect with the tile device rect. 403 let device_rect = (self.world_tile_rect * ctx.global_device_pixel_scale).round(); 404 self.device_valid_rect = (self.world_valid_rect * ctx.global_device_pixel_scale) 405 .round_out() 406 .intersection(&device_rect) 407 .unwrap_or_else(DeviceRect::zero); 408 409 // Invalidate the tile based on the content changing. 410 self.update_content_validity(ctx, state, frame_context); 411 } 412 413 /// Called during tile cache instance post_update. Allows invalidation and dirty 414 /// rect calculation after primitive dependencies have been updated. 415 fn post_update( 416 &mut self, 417 ctx: &TilePostUpdateContext, 418 state: &mut TilePostUpdateState, 419 frame_context: &FrameVisibilityContext, 420 ) { 421 // If tile is not visible, just early out from here - we don't update dependencies 422 // so don't want to invalidate, merge, split etc. The tile won't need to be drawn 423 // (and thus updated / invalidated) until it is on screen again. 424 if !self.is_visible { 425 return; 426 } 427 428 // If there are no primitives there is no need to draw or cache it. 429 // Bug 1719232 - The final device valid rect does not always describe a non-empty 430 // region. Cull the tile as a workaround. 431 if self.cached_surface.current_descriptor.prims.is_empty() || self.device_valid_rect.is_empty() { 432 // If there is a native compositor surface allocated for this (now empty) tile 433 // it must be freed here, otherwise the stale tile with previous contents will 434 // be composited. If the tile subsequently gets new primitives added to it, the 435 // surface will be re-allocated when it's added to the composite draw list. 436 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() { 437 if let Some(id) = id.take() { 438 state.resource_cache.destroy_compositor_tile(id); 439 } 440 } 441 442 self.is_visible = false; 443 return; 444 } 445 446 // Check if this tile can be considered opaque. Opacity state must be updated only 447 // after all early out checks have been performed. Otherwise, we might miss updating 448 // the native surface next time this tile becomes visible. 449 let clipped_rect = self.cached_surface.current_descriptor.local_valid_rect 450 .intersection(&ctx.local_clip_rect) 451 .unwrap_or_else(PictureRect::zero); 452 453 let has_opaque_bg_color = self.cached_surface.background_color.map_or(false, |c| c.a >= 1.0); 454 let has_opaque_backdrop = ctx.backdrop.map_or(false, |b| b.opaque_rect.contains_box(&clipped_rect)); 455 let mut is_opaque = has_opaque_bg_color || has_opaque_backdrop; 456 457 // If this tile intersects with any underlay surfaces, we need to consider it 458 // translucent, since it will contain an alpha cutout 459 for underlay in ctx.underlays { 460 if clipped_rect.intersects(&underlay.local_rect) { 461 is_opaque = false; 462 break; 463 } 464 } 465 466 // Set the correct z_id for this tile 467 self.z_id = ctx.z_id; 468 469 if is_opaque != self.is_opaque { 470 // If opacity changed, the native compositor surface and all tiles get invalidated. 471 // (this does nothing if not using native compositor mode). 472 // TODO(gw): This property probably changes very rarely, so it is OK to invalidate 473 // everything in this case. If it turns out that this isn't true, we could 474 // consider other options, such as per-tile opacity (natively supported 475 // on CoreAnimation, and supported if backed by non-virtual surfaces in 476 // DirectComposition). 477 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface { 478 if let Some(id) = id.take() { 479 state.resource_cache.destroy_compositor_tile(id); 480 } 481 } 482 483 // Invalidate the entire tile to force a redraw. 484 self.invalidate(None, InvalidationReason::SurfaceOpacityChanged); 485 self.is_opaque = is_opaque; 486 } 487 488 // Check if the selected composite mode supports dirty rect updates. For Draw composite 489 // mode, we can always update the content with smaller dirty rects, unless there is a 490 // driver bug to workaround. For native composite mode, we can only use dirty rects if 491 // the compositor supports partial surface updates. 492 let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind { 493 CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => { 494 (frame_context.config.gpu_supports_render_target_partial_update, true) 495 } 496 CompositorKind::Native { capabilities, .. } => { 497 (capabilities.max_update_rects > 0, false) 498 } 499 }; 500 501 // TODO(gw): Consider using smaller tiles and/or tile splits for 502 // native compositors that don't support dirty rects. 503 if supports_dirty_rects { 504 // Only allow splitting for normal content sized tiles 505 if ctx.current_tile_size == state.resource_cache.picture_textures.default_tile_size() { 506 let max_split_level = 3; 507 508 // Consider splitting / merging dirty regions 509 self.cached_surface.root.maybe_merge_or_split( 510 0, 511 &self.cached_surface.current_descriptor.prims, 512 max_split_level, 513 ); 514 } 515 } 516 517 // The dirty rect will be set correctly by now. If the underlying platform 518 // doesn't support partial updates, and this tile isn't valid, force the dirty 519 // rect to be the size of the entire tile. 520 if !self.cached_surface.is_valid && !supports_dirty_rects { 521 self.cached_surface.local_dirty_rect = self.cached_surface.local_rect; 522 } 523 524 // See if this tile is a simple color, in which case we can just draw 525 // it as a rect, and avoid allocating a texture surface and drawing it. 526 // TODO(gw): Initial native compositor interface doesn't support simple 527 // color tiles. We can definitely support this in DC, so this 528 // should be added as a follow up. 529 let is_simple_prim = 530 ctx.backdrop.map_or(false, |b| b.kind.is_some()) && 531 self.cached_surface.current_descriptor.prims.len() == 1 && 532 self.is_opaque && 533 supports_simple_prims; 534 535 // Set up the backing surface for this tile. 536 let surface = if is_simple_prim { 537 // If we determine the tile can be represented by a color, set the 538 // surface unconditionally (this will drop any previously used 539 // texture cache backing surface). 540 match ctx.backdrop.unwrap().kind { 541 Some(BackdropKind::Color { color }) => { 542 TileSurface::Color { 543 color, 544 } 545 } 546 None => { 547 // This should be prevented by the is_simple_prim check above. 548 unreachable!(); 549 } 550 } 551 } else { 552 // If this tile will be backed by a surface, we want to retain 553 // the texture handle from the previous frame, if possible. If 554 // the tile was previously a color, or not set, then just set 555 // up a new texture cache handle. 556 match self.surface.take() { 557 Some(TileSurface::Texture { descriptor }) => { 558 // Reuse the existing descriptor and vis mask 559 TileSurface::Texture { 560 descriptor, 561 } 562 } 563 Some(TileSurface::Color { .. }) | None => { 564 // This is the case where we are constructing a tile surface that 565 // involves drawing to a texture. Create the correct surface 566 // descriptor depending on the compositing mode that will read 567 // the output. 568 let descriptor = match state.composite_state.compositor_kind { 569 CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => { 570 // For a texture cache entry, create an invalid handle that 571 // will be allocated when update_picture_cache is called. 572 SurfaceTextureDescriptor::TextureCache { 573 handle: None, 574 } 575 } 576 CompositorKind::Native { .. } => { 577 // Create a native surface surface descriptor, but don't allocate 578 // a surface yet. The surface is allocated *after* occlusion 579 // culling occurs, so that only visible tiles allocate GPU memory. 580 SurfaceTextureDescriptor::Native { 581 id: None, 582 } 583 } 584 }; 585 586 TileSurface::Texture { 587 descriptor, 588 } 589 } 590 } 591 }; 592 593 // Store the current surface backing info for use during batching. 594 self.surface = Some(surface); 595 } 596 } 597 598 // TODO(gw): Tidy this up by: 599 // - Add an Other variant for things like opaque gradient backdrops 600 #[derive(Debug, Copy, Clone)] 601 pub enum BackdropKind { 602 Color { 603 color: ColorF, 604 }, 605 } 606 607 /// Stores information about the calculated opaque backdrop of this slice. 608 #[derive(Debug, Copy, Clone)] 609 pub struct BackdropInfo { 610 /// The picture space rectangle that is known to be opaque. This is used 611 /// to determine where subpixel AA can be used, and where alpha blending 612 /// can be disabled. 613 pub opaque_rect: PictureRect, 614 /// If the backdrop covers the entire slice with an opaque color, this 615 /// will be set and can be used as a clear color for the slice's tiles. 616 pub spanning_opaque_color: Option<ColorF>, 617 /// Kind of the backdrop 618 pub kind: Option<BackdropKind>, 619 /// The picture space rectangle of the backdrop, if kind is set. 620 pub backdrop_rect: PictureRect, 621 } 622 623 impl BackdropInfo { 624 fn empty() -> Self { 625 BackdropInfo { 626 opaque_rect: PictureRect::zero(), 627 spanning_opaque_color: None, 628 kind: None, 629 backdrop_rect: PictureRect::zero(), 630 } 631 } 632 } 633 634 /// Represents the native surfaces created for a picture cache, if using 635 /// a native compositor. An opaque and alpha surface is always created, 636 /// but tiles are added to a surface based on current opacity. If the 637 /// calculated opacity of a tile changes, the tile is invalidated and 638 /// attached to a different native surface. This means that we don't 639 /// need to invalidate the entire surface if only some tiles are changing 640 /// opacity. It also means we can take advantage of opaque tiles on cache 641 /// slices where only some of the tiles are opaque. There is an assumption 642 /// that creating a native surface is cheap, and only when a tile is added 643 /// to a surface is there a significant cost. This assumption holds true 644 /// for the current native compositor implementations on Windows and Mac. 645 pub struct NativeSurface { 646 /// Native surface for opaque tiles 647 pub opaque: NativeSurfaceId, 648 /// Native surface for alpha tiles 649 pub alpha: NativeSurfaceId, 650 } 651 652 /// Hash key for an external native compositor surface 653 #[derive(PartialEq, Eq, Hash)] 654 pub struct ExternalNativeSurfaceKey { 655 /// The YUV/RGB image keys that are used to draw this surface. 656 pub image_keys: [ImageKey; 3], 657 /// If this is not an 'external' compositor surface created via 658 /// Compositor::create_external_surface, this is set to the 659 /// current device size of the surface. 660 pub size: Option<DeviceIntSize>, 661 } 662 663 /// Information about a native compositor surface cached between frames. 664 pub struct ExternalNativeSurface { 665 /// If true, the surface was used this frame. Used for a simple form 666 /// of GC to remove old surfaces. 667 pub used_this_frame: bool, 668 /// The native compositor surface handle 669 pub native_surface_id: NativeSurfaceId, 670 /// List of image keys, and current image generations, that are drawn in this surface. 671 /// The image generations are used to check if the compositor surface is dirty and 672 /// needs to be updated. 673 pub image_dependencies: [ImageDependency; 3], 674 } 675 676 /// Wrapper struct around an external surface descriptor with a little more information 677 /// that the picture caching code needs. 678 pub struct CompositorSurface { 679 // External surface descriptor used by compositing logic 680 pub descriptor: ExternalSurfaceDescriptor, 681 // The compositor surface rect + any intersecting prims. Later prims that intersect 682 // with this must be added to the next sub-slice. 683 prohibited_rect: PictureRect, 684 // If the compositor surface content is opaque. 685 pub is_opaque: bool, 686 } 687 688 pub struct BackdropSurface { 689 pub id: NativeSurfaceId, 690 pub color: ColorF, 691 pub device_rect: DeviceRect, 692 } 693 694 /// In some cases, we need to know the dirty rect of all tiles in order 695 /// to correctly invalidate a primitive. 696 #[derive(Debug)] 697 pub struct DeferredDirtyTest { 698 /// The tile rect that the primitive being checked affects 699 pub tile_rect: TileRect, 700 /// The picture-cache local rect of the primitive being checked 701 pub prim_rect: PictureRect, 702 } 703 704 /// Represents a cache of tiles that make up a picture primitives. 705 pub struct TileCacheInstance { 706 // The current debug flags for the system. 707 pub debug_flags: DebugFlags, 708 /// Index of the tile cache / slice for this frame builder. It's determined 709 /// by the setup_picture_caching method during flattening, which splits the 710 /// picture tree into multiple slices. It's used as a simple input to the tile 711 /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed 712 /// between display lists - this seems very unlikely to occur on most pages, but 713 /// can be revisited if we ever notice that. 714 pub slice: usize, 715 /// Propagated information about the slice 716 pub slice_flags: SliceFlags, 717 /// The currently selected tile size to use for this cache 718 pub current_tile_size: DeviceIntSize, 719 /// The list of sub-slices in this tile cache 720 pub sub_slices: Vec<SubSlice>, 721 /// The positioning node for this tile cache. 722 pub spatial_node_index: SpatialNodeIndex, 723 /// The coordinate space to do visibility/clipping/invalidation in. 724 pub visibility_node_index: SpatialNodeIndex, 725 /// List of opacity bindings, with some extra information 726 /// about whether they changed since last frame. 727 opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>, 728 /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating. 729 old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>, 730 /// A helper to compare transforms between previous and current frame. 731 spatial_node_comparer: SpatialNodeComparer, 732 /// List of color bindings, with some extra information 733 /// about whether they changed since last frame. 734 color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>, 735 /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating. 736 old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>, 737 /// The current dirty region tracker for this picture. 738 pub dirty_region: DirtyRegion, 739 /// Current size of tiles in picture units. 740 tile_size: PictureSize, 741 /// Tile coords of the currently allocated grid. 742 tile_rect: TileRect, 743 /// Pre-calculated versions of the tile_rect above, used to speed up the 744 /// calculations in get_tile_coords_for_rect. 745 tile_bounds_p0: TileOffset, 746 tile_bounds_p1: TileOffset, 747 /// Local rect (unclipped) of the picture this cache covers. 748 pub local_rect: PictureRect, 749 /// The local clip rect, from the shared clips of this picture. 750 pub local_clip_rect: PictureRect, 751 /// Registered clip in CompositeState for this picture cache 752 pub compositor_clip: Option<CompositorClipIndex>, 753 /// The screen rect, transformed to local picture space. 754 pub screen_rect_in_pic_space: PictureRect, 755 /// The surface index that this tile cache will be drawn into. 756 surface_index: SurfaceIndex, 757 /// The background color from the renderer. If this is set opaque, we know it's 758 /// fine to clear the tiles to this and allow subpixel text on the first slice. 759 pub background_color: Option<ColorF>, 760 /// Information about the calculated backdrop content of this cache. 761 pub backdrop: BackdropInfo, 762 /// The allowed subpixel mode for this surface, which depends on the detected 763 /// opacity of the background. 764 pub subpixel_mode: SubpixelMode, 765 // Node in the clip-tree that defines where we exclude clips from child prims 766 pub shared_clip_node_id: ClipNodeId, 767 // Clip leaf that is used to build the clip-chain for this tile cache. 768 pub shared_clip_leaf_id: Option<ClipLeafId>, 769 /// The number of frames until this cache next evaluates what tile size to use. 770 /// If a picture rect size is regularly changing just around a size threshold, 771 /// we don't want to constantly invalidate and reallocate different tile size 772 /// configuration each frame. 773 frames_until_size_eval: usize, 774 /// For DirectComposition, virtual surfaces don't support negative coordinates. However, 775 /// picture cache tile coordinates can be negative. To handle this, we apply an offset 776 /// to each tile in DirectComposition. We want to change this as little as possible, 777 /// to avoid invalidating tiles. However, if we have a picture cache tile coordinate 778 /// which is outside the virtual surface bounds, we must change this to allow 779 /// correct remapping of the coordinates passed to BeginDraw in DC. 780 pub virtual_offset: DeviceIntPoint, 781 /// keep around the hash map used as compare_cache to avoid reallocating it each 782 /// frame. 783 compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>, 784 /// The currently considered tile size override. Used to check if we should 785 /// re-evaluate tile size, even if the frame timer hasn't expired. 786 tile_size_override: Option<DeviceIntSize>, 787 /// A cache of compositor surfaces that are retained between frames 788 pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>, 789 /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting 790 frame_id: FrameId, 791 /// Registered transform in CompositeState for this picture cache 792 pub transform_index: CompositorTransformIndex, 793 /// Current transform mapping local picture space to compositor surface raster space 794 local_to_raster: ScaleOffset, 795 /// Current transform mapping compositor surface raster space to final device space 796 raster_to_device: ScaleOffset, 797 /// If true, we need to invalidate all tiles during `post_update` 798 invalidate_all_tiles: bool, 799 /// The current raster scale for tiles in this cache 800 pub current_raster_scale: f32, 801 /// Depth of off-screen surfaces that are currently pushed during dependency updates 802 current_surface_traversal_depth: usize, 803 /// A list of extra dirty invalidation tests that can only be checked once we 804 /// know the dirty rect of all tiles 805 deferred_dirty_tests: Vec<DeferredDirtyTest>, 806 /// Is there a backdrop associated with this cache 807 pub found_prims_after_backdrop: bool, 808 pub backdrop_surface: Option<BackdropSurface>, 809 /// List of underlay compositor surfaces that exist in this picture cache 810 pub underlays: Vec<ExternalSurfaceDescriptor>, 811 /// "Region" (actually a spanning rect) containing all overlay promoted surfaces 812 pub overlay_region: PictureRect, 813 /// The number YuvImage prims in this cache, provided in our TileCacheParams. 814 pub yuv_images_count: usize, 815 /// The remaining number of YuvImage prims we will see this frame. We prioritize 816 /// promoting these before promoting any Image prims. 817 pub yuv_images_remaining: usize, 818 } 819 820 impl TileCacheInstance { 821 pub fn new(params: TileCacheParams) -> Self { 822 // Determine how many sub-slices we need. Clamp to an arbitrary limit to ensure 823 // we don't create a huge number of OS compositor tiles and sub-slices. 824 let sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1; 825 826 let mut sub_slices = Vec::with_capacity(sub_slice_count); 827 for _ in 0 .. sub_slice_count { 828 sub_slices.push(SubSlice::new()); 829 } 830 831 TileCacheInstance { 832 debug_flags: params.debug_flags, 833 slice: params.slice, 834 slice_flags: params.slice_flags, 835 spatial_node_index: params.spatial_node_index, 836 visibility_node_index: params.visibility_node_index, 837 sub_slices, 838 opacity_bindings: FastHashMap::default(), 839 old_opacity_bindings: FastHashMap::default(), 840 spatial_node_comparer: SpatialNodeComparer::new(), 841 color_bindings: FastHashMap::default(), 842 old_color_bindings: FastHashMap::default(), 843 dirty_region: DirtyRegion::new(params.visibility_node_index, params.spatial_node_index), 844 tile_size: PictureSize::zero(), 845 tile_rect: TileRect::zero(), 846 tile_bounds_p0: TileOffset::zero(), 847 tile_bounds_p1: TileOffset::zero(), 848 local_rect: PictureRect::zero(), 849 local_clip_rect: PictureRect::zero(), 850 compositor_clip: None, 851 screen_rect_in_pic_space: PictureRect::zero(), 852 surface_index: SurfaceIndex(0), 853 background_color: params.background_color, 854 backdrop: BackdropInfo::empty(), 855 subpixel_mode: SubpixelMode::Allow, 856 shared_clip_node_id: params.shared_clip_node_id, 857 shared_clip_leaf_id: params.shared_clip_leaf_id, 858 current_tile_size: DeviceIntSize::zero(), 859 frames_until_size_eval: 0, 860 // Default to centering the virtual offset in the middle of the DC virtual surface 861 virtual_offset: DeviceIntPoint::new( 862 params.virtual_surface_size / 2, 863 params.virtual_surface_size / 2, 864 ), 865 compare_cache: FastHashMap::default(), 866 tile_size_override: None, 867 external_native_surface_cache: FastHashMap::default(), 868 frame_id: FrameId::INVALID, 869 transform_index: CompositorTransformIndex::INVALID, 870 raster_to_device: ScaleOffset::identity(), 871 local_to_raster: ScaleOffset::identity(), 872 invalidate_all_tiles: true, 873 current_raster_scale: 1.0, 874 current_surface_traversal_depth: 0, 875 deferred_dirty_tests: Vec::new(), 876 found_prims_after_backdrop: false, 877 backdrop_surface: None, 878 underlays: Vec::new(), 879 overlay_region: PictureRect::zero(), 880 yuv_images_count: params.yuv_image_surface_count, 881 yuv_images_remaining: 0, 882 } 883 } 884 885 /// Return the total number of tiles allocated by this tile cache 886 pub fn tile_count(&self) -> usize { 887 self.tile_rect.area() as usize * self.sub_slices.len() 888 } 889 890 /// Trims memory held by the tile cache, such as native surfaces. 891 pub fn memory_pressure(&mut self, resource_cache: &mut ResourceCache) { 892 for sub_slice in &mut self.sub_slices { 893 for tile in sub_slice.tiles.values_mut() { 894 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { 895 // Reseting the id to None with take() ensures that a new 896 // tile will be allocated during the next frame build. 897 if let Some(id) = id.take() { 898 resource_cache.destroy_compositor_tile(id); 899 } 900 } 901 } 902 if let Some(native_surface) = sub_slice.native_surface.take() { 903 resource_cache.destroy_compositor_surface(native_surface.opaque); 904 resource_cache.destroy_compositor_surface(native_surface.alpha); 905 } 906 } 907 } 908 909 /// Reset this tile cache with the updated parameters from a new scene 910 /// that has arrived. This allows the tile cache to be retained across 911 /// new scenes. 912 pub fn prepare_for_new_scene( 913 &mut self, 914 params: TileCacheParams, 915 resource_cache: &mut ResourceCache, 916 ) { 917 // We should only receive updated state for matching slice key 918 assert_eq!(self.slice, params.slice); 919 920 // Determine how many sub-slices we need, based on how many compositor surface prims are 921 // in the supplied primitive list. 922 let required_sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1; 923 924 if self.sub_slices.len() != required_sub_slice_count { 925 self.tile_rect = TileRect::zero(); 926 927 if self.sub_slices.len() > required_sub_slice_count { 928 let old_sub_slices = self.sub_slices.split_off(required_sub_slice_count); 929 930 for mut sub_slice in old_sub_slices { 931 for tile in sub_slice.tiles.values_mut() { 932 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { 933 if let Some(id) = id.take() { 934 resource_cache.destroy_compositor_tile(id); 935 } 936 } 937 } 938 939 if let Some(native_surface) = sub_slice.native_surface { 940 resource_cache.destroy_compositor_surface(native_surface.opaque); 941 resource_cache.destroy_compositor_surface(native_surface.alpha); 942 } 943 } 944 } else { 945 while self.sub_slices.len() < required_sub_slice_count { 946 self.sub_slices.push(SubSlice::new()); 947 } 948 } 949 } 950 951 // Store the parameters from the scene builder for this slice. Other 952 // params in the tile cache are retained and reused, or are always 953 // updated during pre/post_update. 954 self.slice_flags = params.slice_flags; 955 self.spatial_node_index = params.spatial_node_index; 956 self.background_color = params.background_color; 957 self.shared_clip_leaf_id = params.shared_clip_leaf_id; 958 self.shared_clip_node_id = params.shared_clip_node_id; 959 960 // Since the slice flags may have changed, ensure we re-evaluate the 961 // appropriate tile size for this cache next update. 962 self.frames_until_size_eval = 0; 963 964 // Update the number of YuvImage prims we have in the scene. 965 self.yuv_images_count = params.yuv_image_surface_count; 966 } 967 968 /// Destroy any manually managed resources before this picture cache is 969 /// destroyed, such as native compositor surfaces. 970 pub fn destroy( 971 self, 972 resource_cache: &mut ResourceCache, 973 ) { 974 for sub_slice in self.sub_slices { 975 if let Some(native_surface) = sub_slice.native_surface { 976 resource_cache.destroy_compositor_surface(native_surface.opaque); 977 resource_cache.destroy_compositor_surface(native_surface.alpha); 978 } 979 } 980 981 for (_, external_surface) in self.external_native_surface_cache { 982 resource_cache.destroy_compositor_surface(external_surface.native_surface_id) 983 } 984 985 if let Some(backdrop_surface) = &self.backdrop_surface { 986 resource_cache.destroy_compositor_surface(backdrop_surface.id); 987 } 988 } 989 990 /// Get the tile coordinates for a given rectangle. 991 fn get_tile_coords_for_rect( 992 &self, 993 rect: &PictureRect, 994 ) -> (TileOffset, TileOffset) { 995 // Get the tile coordinates in the picture space. 996 let mut p0 = TileOffset::new( 997 (rect.min.x / self.tile_size.width).floor() as i32, 998 (rect.min.y / self.tile_size.height).floor() as i32, 999 ); 1000 1001 let mut p1 = TileOffset::new( 1002 (rect.max.x / self.tile_size.width).ceil() as i32, 1003 (rect.max.y / self.tile_size.height).ceil() as i32, 1004 ); 1005 1006 // Clamp the tile coordinates here to avoid looping over irrelevant tiles later on. 1007 p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x); 1008 p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y); 1009 p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x); 1010 p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y); 1011 1012 (p0, p1) 1013 } 1014 1015 /// Update transforms, opacity, color bindings and tile rects. 1016 pub fn pre_update( 1017 &mut self, 1018 surface_index: SurfaceIndex, 1019 frame_context: &FrameVisibilityContext, 1020 frame_state: &mut FrameVisibilityState, 1021 ) -> WorldRect { 1022 let surface = &frame_state.surfaces[surface_index.0]; 1023 let pic_rect = surface.unclipped_local_rect; 1024 1025 self.surface_index = surface_index; 1026 self.local_rect = pic_rect; 1027 self.local_clip_rect = PictureRect::max_rect(); 1028 self.deferred_dirty_tests.clear(); 1029 self.underlays.clear(); 1030 self.overlay_region = PictureRect::zero(); 1031 self.yuv_images_remaining = self.yuv_images_count; 1032 1033 for sub_slice in &mut self.sub_slices { 1034 sub_slice.reset(); 1035 } 1036 1037 // Reset the opaque rect + subpixel mode, as they are calculated 1038 // during the prim dependency checks. 1039 self.backdrop = BackdropInfo::empty(); 1040 1041 // Calculate the screen rect in picture space, for later comparison against 1042 // backdrops, and prims potentially covering backdrops. 1043 let pic_to_world_mapper = SpaceMapper::new_with_target( 1044 frame_context.root_spatial_node_index, 1045 self.spatial_node_index, 1046 frame_context.global_screen_world_rect, 1047 frame_context.spatial_tree, 1048 ); 1049 self.screen_rect_in_pic_space = pic_to_world_mapper 1050 .unmap(&frame_context.global_screen_world_rect) 1051 .expect("unable to unmap screen rect"); 1052 1053 let pic_to_vis_mapper = SpaceMapper::new_with_target( 1054 // TODO: use the raster node instead of the root node. 1055 frame_context.root_spatial_node_index, 1056 self.spatial_node_index, 1057 surface.culling_rect, 1058 frame_context.spatial_tree, 1059 ); 1060 1061 // If there is a valid set of shared clips, build a clip chain instance for this, 1062 // which will provide a local clip rect. This is useful for establishing things 1063 // like whether the backdrop rect supplied by Gecko can be considered opaque. 1064 if let Some(shared_clip_leaf_id) = self.shared_clip_leaf_id { 1065 let map_local_to_picture = SpaceMapper::new( 1066 self.spatial_node_index, 1067 pic_rect, 1068 ); 1069 1070 frame_state.clip_store.set_active_clips( 1071 self.spatial_node_index, 1072 map_local_to_picture.ref_spatial_node_index, 1073 surface.visibility_spatial_node_index, 1074 shared_clip_leaf_id, 1075 frame_context.spatial_tree, 1076 &mut frame_state.data_stores.clip, 1077 &frame_state.clip_tree, 1078 ); 1079 1080 let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance( 1081 pic_rect.cast_unit(), 1082 &map_local_to_picture, 1083 &pic_to_vis_mapper, 1084 frame_context.spatial_tree, 1085 &mut frame_state.frame_gpu_data.f32, 1086 frame_state.resource_cache, 1087 frame_context.global_device_pixel_scale, 1088 &surface.culling_rect, 1089 &mut frame_state.data_stores.clip, 1090 frame_state.rg_builder, 1091 true, 1092 ); 1093 1094 // Ensure that if the entire picture cache is clipped out, the local 1095 // clip rect is zero. This makes sure we don't register any occluders 1096 // that are actually off-screen. 1097 self.local_clip_rect = PictureRect::zero(); 1098 self.compositor_clip = None; 1099 1100 if let Some(clip_chain) = clip_chain_instance { 1101 self.local_clip_rect = clip_chain.pic_coverage_rect; 1102 self.compositor_clip = None; 1103 1104 if clip_chain.needs_mask { 1105 for i in 0 .. clip_chain.clips_range.count { 1106 let clip_instance = frame_state 1107 .clip_store 1108 .get_instance_from_range(&clip_chain.clips_range, i); 1109 let clip_node = &frame_state.data_stores.clip[clip_instance.handle]; 1110 1111 match clip_node.item.kind { 1112 ClipItemKind::RoundedRectangle { rect, radius, mode } => { 1113 assert_eq!(mode, ClipMode::Clip); 1114 1115 // Map the clip in to device space. We know from the shared 1116 // clip creation logic it's in root coord system, so only a 1117 // 2d axis-aligned transform can apply. For example, in the 1118 // case of a pinch-zoom effect. 1119 let map = ClipSpaceConversion::new( 1120 frame_context.root_spatial_node_index, 1121 clip_node.item.spatial_node_index, 1122 frame_context.root_spatial_node_index, 1123 frame_context.spatial_tree, 1124 ); 1125 1126 let (rect, radius) = match map { 1127 ClipSpaceConversion::Local => { 1128 (rect.cast_unit(), radius) 1129 } 1130 ClipSpaceConversion::ScaleOffset(scale_offset) => { 1131 ( 1132 scale_offset.map_rect(&rect), 1133 BorderRadius { 1134 top_left: scale_offset.map_size(&radius.top_left), 1135 top_right: scale_offset.map_size(&radius.top_right), 1136 bottom_left: scale_offset.map_size(&radius.bottom_left), 1137 bottom_right: scale_offset.map_size(&radius.bottom_right), 1138 }, 1139 ) 1140 } 1141 ClipSpaceConversion::Transform(..) => { 1142 unreachable!(); 1143 } 1144 }; 1145 1146 self.compositor_clip = Some(frame_state.composite_state.register_clip( 1147 rect, 1148 radius, 1149 )); 1150 1151 break; 1152 } 1153 _ => { 1154 // The logic to check for shared clips excludes other mask 1155 // clip types (box-shadow, image-mask) and ensures that the 1156 // clip is in the root coord system (so rect clips can't 1157 // produce a mask). 1158 } 1159 } 1160 } 1161 } 1162 } 1163 } 1164 1165 // Advance the current frame ID counter for this picture cache (must be done 1166 // after any retained prev state is taken above). 1167 self.frame_id.advance(); 1168 1169 // Notify the spatial node comparer that a new frame has started, and the 1170 // current reference spatial node for this tile cache. 1171 self.spatial_node_comparer.next_frame(self.spatial_node_index); 1172 1173 // At the start of the frame, step through each current compositor surface 1174 // and mark it as unused. Later, this is used to free old compositor surfaces. 1175 // TODO(gw): In future, we might make this more sophisticated - for example, 1176 // retaining them for >1 frame if unused, or retaining them in some 1177 // kind of pool to reduce future allocations. 1178 for external_native_surface in self.external_native_surface_cache.values_mut() { 1179 external_native_surface.used_this_frame = false; 1180 } 1181 1182 // Only evaluate what tile size to use fairly infrequently, so that we don't end 1183 // up constantly invalidating and reallocating tiles if the picture rect size is 1184 // changing near a threshold value. 1185 if self.frames_until_size_eval == 0 || 1186 self.tile_size_override != frame_context.config.tile_size_override { 1187 1188 // Work out what size tile is appropriate for this picture cache. 1189 let desired_tile_size = match frame_context.config.tile_size_override { 1190 Some(tile_size_override) => { 1191 tile_size_override 1192 } 1193 None => { 1194 if self.slice_flags.contains(SliceFlags::IS_SCROLLBAR) { 1195 if pic_rect.width() <= pic_rect.height() { 1196 TILE_SIZE_SCROLLBAR_VERTICAL 1197 } else { 1198 TILE_SIZE_SCROLLBAR_HORIZONTAL 1199 } 1200 } else { 1201 frame_state.resource_cache.picture_textures.default_tile_size() 1202 } 1203 } 1204 }; 1205 1206 // If the desired tile size has changed, then invalidate and drop any 1207 // existing tiles. 1208 if desired_tile_size != self.current_tile_size { 1209 for sub_slice in &mut self.sub_slices { 1210 // Destroy any native surfaces on the tiles that will be dropped due 1211 // to resizing. 1212 if let Some(native_surface) = sub_slice.native_surface.take() { 1213 frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); 1214 frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); 1215 } 1216 sub_slice.tiles.clear(); 1217 } 1218 self.tile_rect = TileRect::zero(); 1219 self.current_tile_size = desired_tile_size; 1220 } 1221 1222 // Reset counter until next evaluating the desired tile size. This is an 1223 // arbitrary value. 1224 self.frames_until_size_eval = 120; 1225 self.tile_size_override = frame_context.config.tile_size_override; 1226 } 1227 1228 // Get the complete scale-offset from local space to device space 1229 let local_to_device = get_relative_scale_offset( 1230 self.spatial_node_index, 1231 frame_context.root_spatial_node_index, 1232 frame_context.spatial_tree, 1233 ); 1234 1235 // Get the compositor transform, which depends on pinch-zoom mode 1236 let mut raster_to_device = local_to_device; 1237 1238 if frame_context.config.low_quality_pinch_zoom { 1239 raster_to_device.scale.x /= self.current_raster_scale; 1240 raster_to_device.scale.y /= self.current_raster_scale; 1241 } else { 1242 raster_to_device.scale.x = 1.0; 1243 raster_to_device.scale.y = 1.0; 1244 } 1245 1246 // Use that compositor transform to calculate a relative local to surface 1247 let local_to_raster = local_to_device.then(&raster_to_device.inverse()); 1248 1249 const EPSILON: f32 = 0.001; 1250 let compositor_translation_changed = 1251 !raster_to_device.offset.x.approx_eq_eps(&self.raster_to_device.offset.x, &EPSILON) || 1252 !raster_to_device.offset.y.approx_eq_eps(&self.raster_to_device.offset.y, &EPSILON); 1253 let compositor_scale_changed = 1254 !raster_to_device.scale.x.approx_eq_eps(&self.raster_to_device.scale.x, &EPSILON) || 1255 !raster_to_device.scale.y.approx_eq_eps(&self.raster_to_device.scale.y, &EPSILON); 1256 let surface_scale_changed = 1257 !local_to_raster.scale.x.approx_eq_eps(&self.local_to_raster.scale.x, &EPSILON) || 1258 !local_to_raster.scale.y.approx_eq_eps(&self.local_to_raster.scale.y, &EPSILON); 1259 1260 if compositor_translation_changed || 1261 compositor_scale_changed || 1262 surface_scale_changed || 1263 frame_context.config.force_invalidation { 1264 frame_state.composite_state.dirty_rects_are_valid = false; 1265 } 1266 1267 self.raster_to_device = raster_to_device; 1268 self.local_to_raster = local_to_raster; 1269 self.invalidate_all_tiles = surface_scale_changed || frame_context.config.force_invalidation; 1270 1271 // Do a hacky diff of opacity binding values from the last frame. This is 1272 // used later on during tile invalidation tests. 1273 let current_properties = frame_context.scene_properties.float_properties(); 1274 mem::swap(&mut self.opacity_bindings, &mut self.old_opacity_bindings); 1275 1276 self.opacity_bindings.clear(); 1277 for (id, value) in current_properties { 1278 let changed = match self.old_opacity_bindings.get(id) { 1279 Some(old_property) => !old_property.value.approx_eq(value), 1280 None => true, 1281 }; 1282 self.opacity_bindings.insert(*id, OpacityBindingInfo { 1283 value: *value, 1284 changed, 1285 }); 1286 } 1287 1288 // Do a hacky diff of color binding values from the last frame. This is 1289 // used later on during tile invalidation tests. 1290 let current_properties = frame_context.scene_properties.color_properties(); 1291 mem::swap(&mut self.color_bindings, &mut self.old_color_bindings); 1292 1293 self.color_bindings.clear(); 1294 for (id, value) in current_properties { 1295 let changed = match self.old_color_bindings.get(id) { 1296 Some(old_property) => old_property.value != (*value).into(), 1297 None => true, 1298 }; 1299 self.color_bindings.insert(*id, ColorBindingInfo { 1300 value: (*value).into(), 1301 changed, 1302 }); 1303 } 1304 1305 let world_tile_size = WorldSize::new( 1306 self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0, 1307 self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0, 1308 ); 1309 1310 self.tile_size = PictureSize::new( 1311 world_tile_size.width / self.local_to_raster.scale.x, 1312 world_tile_size.height / self.local_to_raster.scale.y, 1313 ); 1314 1315 // Inflate the needed rect a bit, so that we retain tiles that we have drawn 1316 // but have just recently gone off-screen. This means that we avoid re-drawing 1317 // tiles if the user is scrolling up and down small amounts, at the cost of 1318 // a bit of extra texture memory. 1319 let desired_rect_in_pic_space = self.screen_rect_in_pic_space 1320 .inflate(0.0, 1.0 * self.tile_size.height); 1321 1322 let needed_rect_in_pic_space = desired_rect_in_pic_space 1323 .intersection(&pic_rect) 1324 .unwrap_or_else(Box2D::zero); 1325 1326 let p0 = needed_rect_in_pic_space.min; 1327 let p1 = needed_rect_in_pic_space.max; 1328 1329 let x0 = (p0.x / self.tile_size.width).floor() as i32; 1330 let x1 = (p1.x / self.tile_size.width).ceil() as i32; 1331 1332 let y0 = (p0.y / self.tile_size.height).floor() as i32; 1333 let y1 = (p1.y / self.tile_size.height).ceil() as i32; 1334 1335 let new_tile_rect = TileRect { 1336 min: TileOffset::new(x0, y0), 1337 max: TileOffset::new(x1, y1), 1338 }; 1339 1340 // Determine whether the current bounds of the tile grid will exceed the 1341 // bounds of the DC virtual surface, taking into account the current 1342 // virtual offset. If so, we need to invalidate all tiles, and set up 1343 // a new virtual offset, centered around the current tile grid. 1344 1345 let virtual_surface_size = frame_context.config.compositor_kind.get_virtual_surface_size(); 1346 // We only need to invalidate in this case if the underlying platform 1347 // uses virtual surfaces. 1348 if virtual_surface_size > 0 { 1349 // Get the extremities of the tile grid after virtual offset is applied 1350 let tx0 = self.virtual_offset.x + x0 * self.current_tile_size.width; 1351 let ty0 = self.virtual_offset.y + y0 * self.current_tile_size.height; 1352 let tx1 = self.virtual_offset.x + (x1+1) * self.current_tile_size.width; 1353 let ty1 = self.virtual_offset.y + (y1+1) * self.current_tile_size.height; 1354 1355 let need_new_virtual_offset = tx0 < 0 || 1356 ty0 < 0 || 1357 tx1 >= virtual_surface_size || 1358 ty1 >= virtual_surface_size; 1359 1360 if need_new_virtual_offset { 1361 // Calculate a new virtual offset, centered around the middle of the 1362 // current tile grid. This means we won't need to invalidate and get 1363 // a new offset for a long time! 1364 self.virtual_offset = DeviceIntPoint::new( 1365 (virtual_surface_size/2) - ((x0 + x1) / 2) * self.current_tile_size.width, 1366 (virtual_surface_size/2) - ((y0 + y1) / 2) * self.current_tile_size.height, 1367 ); 1368 1369 // Invalidate all native tile surfaces. They will be re-allocated next time 1370 // they are scheduled to be rasterized. 1371 for sub_slice in &mut self.sub_slices { 1372 for tile in sub_slice.tiles.values_mut() { 1373 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { 1374 if let Some(id) = id.take() { 1375 frame_state.resource_cache.destroy_compositor_tile(id); 1376 tile.surface = None; 1377 // Invalidate the entire tile to force a redraw. 1378 // TODO(gw): Add a new invalidation reason for virtual offset changing 1379 tile.invalidate(None, InvalidationReason::CompositorKindChanged); 1380 } 1381 } 1382 } 1383 1384 // Destroy the native virtual surfaces. They will be re-allocated next time a tile 1385 // that references them is scheduled to draw. 1386 if let Some(native_surface) = sub_slice.native_surface.take() { 1387 frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); 1388 frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); 1389 } 1390 } 1391 } 1392 } 1393 1394 // Rebuild the tile grid if the picture cache rect has changed. 1395 if new_tile_rect != self.tile_rect { 1396 for sub_slice in &mut self.sub_slices { 1397 let mut old_tiles = sub_slice.resize(new_tile_rect); 1398 1399 // When old tiles that remain after the loop, dirty rects are not valid. 1400 if !old_tiles.is_empty() { 1401 frame_state.composite_state.dirty_rects_are_valid = false; 1402 } 1403 1404 // Any old tiles that remain after the loop above are going to be dropped. For 1405 // simple composite mode, the texture cache handle will expire and be collected 1406 // by the texture cache. For native compositor mode, we need to explicitly 1407 // invoke a callback to the client to destroy that surface. 1408 if let CompositorKind::Native { .. } = frame_state.composite_state.compositor_kind { 1409 for tile in old_tiles.values_mut() { 1410 // Only destroy native surfaces that have been allocated. It's 1411 // possible for display port tiles to be created that never 1412 // come on screen, and thus never get a native surface allocated. 1413 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { 1414 if let Some(id) = id.take() { 1415 frame_state.resource_cache.destroy_compositor_tile(id); 1416 } 1417 } 1418 } 1419 } 1420 } 1421 } 1422 1423 // This is duplicated information from tile_rect, but cached here to avoid 1424 // redundant calculations during get_tile_coords_for_rect 1425 self.tile_bounds_p0 = TileOffset::new(x0, y0); 1426 self.tile_bounds_p1 = TileOffset::new(x1, y1); 1427 self.tile_rect = new_tile_rect; 1428 1429 let mut world_culling_rect = WorldRect::zero(); 1430 1431 let mut ctx = TilePreUpdateContext { 1432 pic_to_world_mapper, 1433 background_color: self.background_color, 1434 global_screen_world_rect: frame_context.global_screen_world_rect, 1435 tile_size: self.tile_size, 1436 frame_id: self.frame_id, 1437 }; 1438 1439 // Pre-update each tile 1440 for sub_slice in &mut self.sub_slices { 1441 for tile in sub_slice.tiles.values_mut() { 1442 tile.pre_update(&ctx); 1443 1444 // Only include the tiles that are currently in view into the world culling 1445 // rect. This is a very important optimization for a couple of reasons: 1446 // (1) Primitives that intersect with tiles in the grid that are not currently 1447 // visible can be skipped from primitive preparation, clip chain building 1448 // and tile dependency updates. 1449 // (2) When we need to allocate an off-screen surface for a child picture (for 1450 // example a CSS filter) we clip the size of the GPU surface to the world 1451 // culling rect below (to ensure we draw enough of it to be sampled by any 1452 // tiles that reference it). Making the world culling rect only affected 1453 // by visible tiles (rather than the entire virtual tile display port) can 1454 // result in allocating _much_ smaller GPU surfaces for cases where the 1455 // true off-screen surface size is very large. 1456 if tile.is_visible { 1457 world_culling_rect = world_culling_rect.union(&tile.world_tile_rect); 1458 } 1459 } 1460 1461 // The background color can only be applied to the first sub-slice. 1462 ctx.background_color = None; 1463 } 1464 1465 // If compositor mode is changed, need to drop all incompatible tiles. 1466 match frame_context.config.compositor_kind { 1467 CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => { 1468 for sub_slice in &mut self.sub_slices { 1469 for tile in sub_slice.tiles.values_mut() { 1470 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { 1471 if let Some(id) = id.take() { 1472 frame_state.resource_cache.destroy_compositor_tile(id); 1473 } 1474 tile.surface = None; 1475 // Invalidate the entire tile to force a redraw. 1476 tile.invalidate(None, InvalidationReason::CompositorKindChanged); 1477 } 1478 } 1479 1480 if let Some(native_surface) = sub_slice.native_surface.take() { 1481 frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); 1482 frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); 1483 } 1484 } 1485 1486 for (_, external_surface) in self.external_native_surface_cache.drain() { 1487 frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id) 1488 } 1489 } 1490 CompositorKind::Native { .. } => { 1491 // This could hit even when compositor mode is not changed, 1492 // then we need to check if there are incompatible tiles. 1493 for sub_slice in &mut self.sub_slices { 1494 for tile in sub_slice.tiles.values_mut() { 1495 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface { 1496 tile.surface = None; 1497 // Invalidate the entire tile to force a redraw. 1498 tile.invalidate(None, InvalidationReason::CompositorKindChanged); 1499 } 1500 } 1501 } 1502 } 1503 } 1504 1505 world_culling_rect 1506 } 1507 1508 fn can_promote_to_surface( 1509 &mut self, 1510 prim_clip_chain: &ClipChainInstance, 1511 prim_spatial_node_index: SpatialNodeIndex, 1512 is_root_tile_cache: bool, 1513 sub_slice_index: usize, 1514 surface_kind: CompositorSurfaceKind, 1515 pic_coverage_rect: PictureRect, 1516 frame_context: &FrameVisibilityContext, 1517 data_stores: &DataStores, 1518 clip_store: &ClipStore, 1519 composite_state: &CompositeState, 1520 force: bool, 1521 ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> { 1522 use SurfacePromotionFailure::*; 1523 1524 // Each strategy has different restrictions on whether we can promote 1525 match surface_kind { 1526 CompositorSurfaceKind::Overlay => { 1527 // For now, only support a small (arbitrary) number of compositor surfaces. 1528 // Non-opaque compositor surfaces require sub-slices, as they are drawn 1529 // as overlays. 1530 if sub_slice_index == self.sub_slices.len() - 1 { 1531 return Err(OverlaySurfaceLimit); 1532 } 1533 1534 // If a complex clip is being applied to this primitive, it can't be 1535 // promoted directly to a compositor surface. 1536 if prim_clip_chain.needs_mask { 1537 let mut is_supported_rounded_rect = false; 1538 if let CompositorKind::Layer { .. } = composite_state.compositor_kind { 1539 if prim_clip_chain.clips_range.count == 1 && self.compositor_clip.is_none() { 1540 let clip_instance = clip_store.get_instance_from_range(&prim_clip_chain.clips_range, 0); 1541 let clip_node = &data_stores.clip[clip_instance.handle]; 1542 1543 if let ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip, rect, .. } = clip_node.item.kind { 1544 let max_corner_width = radius.top_left.width 1545 .max(radius.bottom_left.width) 1546 .max(radius.top_right.width) 1547 .max(radius.bottom_right.width); 1548 let max_corner_height = radius.top_left.height 1549 .max(radius.bottom_left.height) 1550 .max(radius.top_right.height) 1551 .max(radius.bottom_right.height); 1552 1553 if max_corner_width <= 0.5 * rect.size().width && 1554 max_corner_height <= 0.5 * rect.size().height { 1555 is_supported_rounded_rect = true; 1556 } 1557 } 1558 } 1559 } 1560 1561 if !is_supported_rounded_rect { 1562 return Err(OverlayNeedsMask); 1563 } 1564 } 1565 } 1566 CompositorSurfaceKind::Underlay => { 1567 // If a mask is needed, there are some restrictions. 1568 if prim_clip_chain.needs_mask { 1569 // Need an opaque region behind this prim. The opaque region doesn't 1570 // need to span the entire visible region of the TileCacheInstance, 1571 // which would set self.backdrop.kind, but that also qualifies. 1572 if !self.backdrop.opaque_rect.contains_box(&pic_coverage_rect) { 1573 let result = Err(UnderlayAlphaBackdrop); 1574 // If we aren't forcing, give up and return Err. 1575 if !force { 1576 return result; 1577 } 1578 1579 // Log this but don't return an error. 1580 self.report_promotion_failure(result, pic_coverage_rect, true); 1581 } 1582 1583 // Only one masked underlay allowed. 1584 if !self.underlays.is_empty() { 1585 return Err(UnderlaySurfaceLimit); 1586 } 1587 } 1588 1589 // Underlays can't appear on top of overlays, because they can't punch 1590 // through the existing overlay. 1591 if self.overlay_region.intersects(&pic_coverage_rect) { 1592 let result = Err(UnderlayIntersectsOverlay); 1593 // If we aren't forcing, give up and return Err. 1594 if !force { 1595 return result; 1596 } 1597 1598 // Log this but don't return an error. 1599 self.report_promotion_failure(result, pic_coverage_rect, true); 1600 } 1601 1602 // Underlay cutouts are difficult to align with compositor surfaces when 1603 // compositing during low-quality zoom, and the required invalidation 1604 // whilst zooming would prevent low-quality zoom from working efficiently. 1605 if frame_context.config.low_quality_pinch_zoom && 1606 frame_context.spatial_tree.get_spatial_node(prim_spatial_node_index).is_ancestor_or_self_zooming 1607 { 1608 return Err(UnderlayLowQualityZoom); 1609 } 1610 } 1611 CompositorSurfaceKind::Blit => unreachable!(), 1612 } 1613 1614 // If not on the root picture cache, it has some kind of 1615 // complex effect (such as a filter, mix-blend-mode or 3d transform). 1616 if !is_root_tile_cache { 1617 return Err(NotRootTileCache); 1618 } 1619 1620 let mapper : SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target( 1621 frame_context.root_spatial_node_index, 1622 prim_spatial_node_index, 1623 frame_context.global_screen_world_rect, 1624 &frame_context.spatial_tree); 1625 let transform = mapper.get_transform(); 1626 if !transform.is_2d_scale_translation() { 1627 let result = Err(ComplexTransform); 1628 // Unfortunately, ComplexTransform absolutely prevents proper 1629 // functioning of surface promotion. Treating this as a warning 1630 // instead of an error will cause a crash in get_relative_scale_offset. 1631 return result; 1632 } 1633 1634 if self.slice_flags.contains(SliceFlags::IS_ATOMIC) { 1635 return Err(SliceAtomic); 1636 } 1637 1638 Ok(surface_kind) 1639 } 1640 1641 fn setup_compositor_surfaces_yuv( 1642 &mut self, 1643 sub_slice_index: usize, 1644 prim_info: &mut PrimitiveDependencyInfo, 1645 flags: PrimitiveFlags, 1646 local_prim_rect: LayoutRect, 1647 prim_clip_chain: &ClipChainInstance, 1648 prim_spatial_node_index: SpatialNodeIndex, 1649 pic_coverage_rect: PictureRect, 1650 frame_context: &FrameVisibilityContext, 1651 data_stores: &DataStores, 1652 clip_store: &ClipStore, 1653 image_dependencies: &[ImageDependency;3], 1654 api_keys: &[ImageKey; 3], 1655 resource_cache: &mut ResourceCache, 1656 composite_state: &mut CompositeState, 1657 gpu_buffer: &mut GpuBufferBuilderF, 1658 image_rendering: ImageRendering, 1659 color_depth: ColorDepth, 1660 color_space: YuvRangedColorSpace, 1661 format: YuvFormat, 1662 surface_kind: CompositorSurfaceKind, 1663 ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> { 1664 for &key in api_keys { 1665 if key != ImageKey::DUMMY { 1666 // TODO: See comment in setup_compositor_surfaces_rgb. 1667 resource_cache.request_image(ImageRequest { 1668 key, 1669 rendering: image_rendering, 1670 tile: None, 1671 }, 1672 gpu_buffer, 1673 ); 1674 } 1675 } 1676 1677 self.setup_compositor_surfaces_impl( 1678 sub_slice_index, 1679 prim_info, 1680 flags, 1681 local_prim_rect, 1682 prim_clip_chain, 1683 prim_spatial_node_index, 1684 pic_coverage_rect, 1685 frame_context, 1686 data_stores, 1687 clip_store, 1688 ExternalSurfaceDependency::Yuv { 1689 image_dependencies: *image_dependencies, 1690 color_space, 1691 format, 1692 channel_bit_depth: color_depth.bit_depth(), 1693 }, 1694 api_keys, 1695 resource_cache, 1696 composite_state, 1697 image_rendering, 1698 true, 1699 surface_kind, 1700 ) 1701 } 1702 1703 fn setup_compositor_surfaces_rgb( 1704 &mut self, 1705 sub_slice_index: usize, 1706 prim_info: &mut PrimitiveDependencyInfo, 1707 flags: PrimitiveFlags, 1708 local_prim_rect: LayoutRect, 1709 prim_clip_chain: &ClipChainInstance, 1710 prim_spatial_node_index: SpatialNodeIndex, 1711 pic_coverage_rect: PictureRect, 1712 frame_context: &FrameVisibilityContext, 1713 data_stores: &DataStores, 1714 clip_store: &ClipStore, 1715 image_dependency: ImageDependency, 1716 api_key: ImageKey, 1717 resource_cache: &mut ResourceCache, 1718 composite_state: &mut CompositeState, 1719 gpu_buffer: &mut GpuBufferBuilderF, 1720 image_rendering: ImageRendering, 1721 is_opaque: bool, 1722 surface_kind: CompositorSurfaceKind, 1723 ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> { 1724 let mut api_keys = [ImageKey::DUMMY; 3]; 1725 api_keys[0] = api_key; 1726 1727 // TODO: The picture compositing code requires images promoted 1728 // into their own picture cache slices to be requested every 1729 // frame even if they are not visible. However the image updates 1730 // are only reached on the prepare pass for visible primitives. 1731 // So we make sure to trigger an image request when promoting 1732 // the image here. 1733 resource_cache.request_image(ImageRequest { 1734 key: api_key, 1735 rendering: image_rendering, 1736 tile: None, 1737 }, 1738 gpu_buffer, 1739 ); 1740 1741 self.setup_compositor_surfaces_impl( 1742 sub_slice_index, 1743 prim_info, 1744 flags, 1745 local_prim_rect, 1746 prim_clip_chain, 1747 prim_spatial_node_index, 1748 pic_coverage_rect, 1749 frame_context, 1750 data_stores, 1751 clip_store, 1752 ExternalSurfaceDependency::Rgb { 1753 image_dependency, 1754 }, 1755 &api_keys, 1756 resource_cache, 1757 composite_state, 1758 image_rendering, 1759 is_opaque, 1760 surface_kind, 1761 ) 1762 } 1763 1764 // returns false if composition is not available for this surface, 1765 // and the non-compositor path should be used to draw it instead. 1766 fn setup_compositor_surfaces_impl( 1767 &mut self, 1768 sub_slice_index: usize, 1769 prim_info: &mut PrimitiveDependencyInfo, 1770 flags: PrimitiveFlags, 1771 local_prim_rect: LayoutRect, 1772 prim_clip_chain: &ClipChainInstance, 1773 prim_spatial_node_index: SpatialNodeIndex, 1774 pic_coverage_rect: PictureRect, 1775 frame_context: &FrameVisibilityContext, 1776 data_stores: &DataStores, 1777 clip_store: &ClipStore, 1778 dependency: ExternalSurfaceDependency, 1779 api_keys: &[ImageKey; 3], 1780 resource_cache: &mut ResourceCache, 1781 composite_state: &mut CompositeState, 1782 image_rendering: ImageRendering, 1783 is_opaque: bool, 1784 surface_kind: CompositorSurfaceKind, 1785 ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> { 1786 use SurfacePromotionFailure::*; 1787 1788 let map_local_to_picture = SpaceMapper::new_with_target( 1789 self.spatial_node_index, 1790 prim_spatial_node_index, 1791 self.local_rect, 1792 frame_context.spatial_tree, 1793 ); 1794 1795 // Map the primitive local rect into picture space. 1796 let prim_rect = match map_local_to_picture.map(&local_prim_rect) { 1797 Some(rect) => rect, 1798 None => return Ok(surface_kind), 1799 }; 1800 1801 // If the rect is invalid, no need to create dependencies. 1802 if prim_rect.is_empty() { 1803 return Ok(surface_kind); 1804 } 1805 1806 let pic_to_world_mapper = SpaceMapper::new_with_target( 1807 frame_context.root_spatial_node_index, 1808 self.spatial_node_index, 1809 frame_context.global_screen_world_rect, 1810 frame_context.spatial_tree, 1811 ); 1812 1813 let world_clip_rect = pic_to_world_mapper 1814 .map(&prim_info.prim_clip_box) 1815 .expect("bug: unable to map clip to world space"); 1816 1817 let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect); 1818 if !is_visible { 1819 return Ok(surface_kind); 1820 } 1821 1822 let prim_offset = ScaleOffset::from_offset(local_prim_rect.min.to_vector().cast_unit()); 1823 1824 let local_prim_to_device = get_relative_scale_offset( 1825 prim_spatial_node_index, 1826 frame_context.root_spatial_node_index, 1827 frame_context.spatial_tree, 1828 ); 1829 1830 let normalized_prim_to_device = prim_offset.then(&local_prim_to_device); 1831 1832 let local_to_raster = ScaleOffset::identity(); 1833 let raster_to_device = normalized_prim_to_device; 1834 1835 // If this primitive is an external image, and supports being used 1836 // directly by a native compositor, then lookup the external image id 1837 // so we can pass that through. 1838 let mut external_image_id = if flags.contains(PrimitiveFlags::SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE) 1839 && image_rendering == ImageRendering::Auto { 1840 resource_cache.get_image_properties(api_keys[0]) 1841 .and_then(|properties| properties.external_image) 1842 .and_then(|image| Some(image.id)) 1843 } else { 1844 None 1845 }; 1846 1847 match composite_state.compositor_kind { 1848 CompositorKind::Native { capabilities, .. } => { 1849 if external_image_id.is_some() && 1850 !capabilities.supports_external_compositor_surface_negative_scaling && 1851 (raster_to_device.scale.x < 0.0 || raster_to_device.scale.y < 0.0) { 1852 external_image_id = None; 1853 } 1854 } 1855 CompositorKind::Layer { .. } | CompositorKind::Draw { .. } => {} 1856 } 1857 1858 let compositor_transform_index = composite_state.register_transform( 1859 local_to_raster, 1860 raster_to_device, 1861 ); 1862 1863 let surface_size = composite_state.get_surface_rect( 1864 &local_prim_rect, 1865 &local_prim_rect, 1866 compositor_transform_index, 1867 ).size(); 1868 1869 let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round(); 1870 1871 1872 let mut compositor_clip_index = None; 1873 1874 if surface_kind == CompositorSurfaceKind::Overlay && 1875 prim_clip_chain.needs_mask { 1876 assert!(prim_clip_chain.clips_range.count == 1); 1877 assert!(self.compositor_clip.is_none()); 1878 1879 let clip_instance = clip_store.get_instance_from_range(&prim_clip_chain.clips_range, 0); 1880 let clip_node = &data_stores.clip[clip_instance.handle]; 1881 if let ClipItemKind::RoundedRectangle { radius, mode: ClipMode::Clip, rect, .. } = clip_node.item.kind { 1882 // Map the clip in to device space. We know from the shared 1883 // clip creation logic it's in root coord system, so only a 1884 // 2d axis-aligned transform can apply. For example, in the 1885 // case of a pinch-zoom effect. 1886 let map = ClipSpaceConversion::new( 1887 frame_context.root_spatial_node_index, 1888 clip_node.item.spatial_node_index, 1889 frame_context.root_spatial_node_index, 1890 frame_context.spatial_tree, 1891 ); 1892 1893 let (rect, radius) = match map { 1894 ClipSpaceConversion::Local => { 1895 (rect.cast_unit(), radius) 1896 } 1897 ClipSpaceConversion::ScaleOffset(scale_offset) => { 1898 ( 1899 scale_offset.map_rect(&rect), 1900 BorderRadius { 1901 top_left: scale_offset.map_size(&radius.top_left), 1902 top_right: scale_offset.map_size(&radius.top_right), 1903 bottom_left: scale_offset.map_size(&radius.bottom_left), 1904 bottom_right: scale_offset.map_size(&radius.bottom_right), 1905 }, 1906 ) 1907 } 1908 ClipSpaceConversion::Transform(..) => { 1909 unreachable!(); 1910 } 1911 }; 1912 1913 compositor_clip_index = Some(composite_state.register_clip( 1914 rect, 1915 radius, 1916 )); 1917 } else { 1918 unreachable!(); 1919 } 1920 } 1921 1922 if surface_size.width >= MAX_COMPOSITOR_SURFACES_SIZE || 1923 surface_size.height >= MAX_COMPOSITOR_SURFACES_SIZE { 1924 return Err(SizeTooLarge); 1925 } 1926 1927 // When using native compositing, we need to find an existing native surface 1928 // handle to use, or allocate a new one. For existing native surfaces, we can 1929 // also determine whether this needs to be updated, depending on whether the 1930 // image generation(s) of the planes have changed since last composite. 1931 let (native_surface_id, update_params) = match composite_state.compositor_kind { 1932 CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => { 1933 (None, None) 1934 } 1935 CompositorKind::Native { .. } => { 1936 let native_surface_size = surface_size.to_i32(); 1937 1938 let key = ExternalNativeSurfaceKey { 1939 image_keys: *api_keys, 1940 size: if external_image_id.is_some() { None } else { Some(native_surface_size) }, 1941 }; 1942 1943 let native_surface = self.external_native_surface_cache 1944 .entry(key) 1945 .or_insert_with(|| { 1946 // No existing surface, so allocate a new compositor surface. 1947 let native_surface_id = match external_image_id { 1948 Some(_external_image) => { 1949 // If we have a suitable external image, then create an external 1950 // surface to attach to. 1951 resource_cache.create_compositor_external_surface(is_opaque) 1952 } 1953 None => { 1954 // Otherwise create a normal compositor surface and a single 1955 // compositor tile that covers the entire surface. 1956 let native_surface_id = 1957 resource_cache.create_compositor_surface( 1958 DeviceIntPoint::zero(), 1959 native_surface_size, 1960 is_opaque, 1961 ); 1962 1963 let tile_id = NativeTileId { 1964 surface_id: native_surface_id, 1965 x: 0, 1966 y: 0, 1967 }; 1968 resource_cache.create_compositor_tile(tile_id); 1969 1970 native_surface_id 1971 } 1972 }; 1973 1974 ExternalNativeSurface { 1975 used_this_frame: true, 1976 native_surface_id, 1977 image_dependencies: [ImageDependency::INVALID; 3], 1978 } 1979 }); 1980 1981 // Mark that the surface is referenced this frame so that the 1982 // backing native surface handle isn't freed. 1983 native_surface.used_this_frame = true; 1984 1985 let update_params = match external_image_id { 1986 Some(external_image) => { 1987 // If this is an external image surface, then there's no update 1988 // to be done. Just attach the current external image to the surface 1989 // and we're done. 1990 resource_cache.attach_compositor_external_image( 1991 native_surface.native_surface_id, 1992 external_image, 1993 ); 1994 None 1995 } 1996 None => { 1997 // If the image dependencies match, there is no need to update 1998 // the backing native surface. 1999 match dependency { 2000 ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => { 2001 if image_dependencies == native_surface.image_dependencies { 2002 None 2003 } else { 2004 Some(native_surface_size) 2005 } 2006 }, 2007 ExternalSurfaceDependency::Rgb{ image_dependency, .. } => { 2008 if image_dependency == native_surface.image_dependencies[0] { 2009 None 2010 } else { 2011 Some(native_surface_size) 2012 } 2013 }, 2014 } 2015 } 2016 }; 2017 2018 (Some(native_surface.native_surface_id), update_params) 2019 } 2020 }; 2021 2022 let descriptor = ExternalSurfaceDescriptor { 2023 local_surface_size: local_prim_rect.size(), 2024 local_rect: prim_rect, 2025 local_clip_rect: prim_info.prim_clip_box, 2026 dependency, 2027 image_rendering, 2028 clip_rect, 2029 transform_index: compositor_transform_index, 2030 compositor_clip_index: compositor_clip_index, 2031 z_id: ZBufferId::invalid(), 2032 native_surface_id, 2033 update_params, 2034 external_image_id, 2035 }; 2036 2037 // If the surface is opaque, we can draw it an an underlay (which avoids 2038 // additional sub-slice surfaces, and supports clip masks) 2039 match surface_kind { 2040 CompositorSurfaceKind::Underlay => { 2041 self.underlays.push(descriptor); 2042 } 2043 CompositorSurfaceKind::Overlay => { 2044 // For compositor surfaces, if we didn't find an earlier sub-slice to add to, 2045 // we know we can append to the current slice. 2046 assert!(sub_slice_index < self.sub_slices.len() - 1); 2047 let sub_slice = &mut self.sub_slices[sub_slice_index]; 2048 2049 // Each compositor surface allocates a unique z-id 2050 sub_slice.compositor_surfaces.push(CompositorSurface { 2051 prohibited_rect: pic_coverage_rect, 2052 is_opaque, 2053 descriptor, 2054 }); 2055 2056 // Add the pic_coverage_rect to the overlay region. This prevents 2057 // future promoted surfaces from becoming underlays if they would 2058 // intersect with the overlay region. 2059 self.overlay_region = self.overlay_region.union(&pic_coverage_rect); 2060 } 2061 CompositorSurfaceKind::Blit => unreachable!(), 2062 } 2063 2064 Ok(surface_kind) 2065 } 2066 2067 /// Push an estimated rect for an off-screen surface during dependency updates. This is 2068 /// a workaround / hack that allows the picture cache code to know when it should be 2069 /// processing primitive dependencies as a single atomic unit. In future, we aim to remove 2070 /// this hack by having the primitive dependencies stored _within_ each owning picture. 2071 /// This is part of the work required to support child picture caching anyway! 2072 pub fn push_surface( 2073 &mut self, 2074 estimated_local_rect: LayoutRect, 2075 surface_spatial_node_index: SpatialNodeIndex, 2076 spatial_tree: &SpatialTree, 2077 ) { 2078 // Only need to evaluate sub-slice regions if we have compositor surfaces present 2079 if self.current_surface_traversal_depth == 0 && self.sub_slices.len() > 1 { 2080 let map_local_to_picture = SpaceMapper::new_with_target( 2081 self.spatial_node_index, 2082 surface_spatial_node_index, 2083 self.local_rect, 2084 spatial_tree, 2085 ); 2086 2087 if let Some(pic_rect) = map_local_to_picture.map(&estimated_local_rect) { 2088 // Find the first sub-slice we can add this primitive to (we want to add 2089 // prims to the primary surface if possible, so they get subpixel AA). 2090 for sub_slice in &mut self.sub_slices { 2091 let mut intersects_prohibited_region = false; 2092 2093 for surface in &mut sub_slice.compositor_surfaces { 2094 if pic_rect.intersects(&surface.prohibited_rect) { 2095 surface.prohibited_rect = surface.prohibited_rect.union(&pic_rect); 2096 2097 intersects_prohibited_region = true; 2098 } 2099 } 2100 2101 if !intersects_prohibited_region { 2102 break; 2103 } 2104 } 2105 } 2106 } 2107 2108 self.current_surface_traversal_depth += 1; 2109 } 2110 2111 /// Pop an off-screen surface off the stack during dependency updates 2112 pub fn pop_surface(&mut self) { 2113 self.current_surface_traversal_depth -= 1; 2114 } 2115 2116 fn report_promotion_failure(&self, 2117 result: Result<CompositorSurfaceKind, SurfacePromotionFailure>, 2118 rect: PictureRect, 2119 ignored: bool) { 2120 if !self.debug_flags.contains(DebugFlags::SURFACE_PROMOTION_LOGGING) || result.is_ok() { 2121 return; 2122 } 2123 2124 // Report this as a warning. 2125 // TODO: Find a way to expose this to web authors. 2126 let outcome = if ignored { "failure ignored" } else { "failed" }; 2127 warn!("Surface promotion of prim at {:?} {outcome} with: {}.", rect, result.unwrap_err()); 2128 } 2129 2130 /// Update the dependencies for each tile for a given primitive instance. 2131 pub fn update_prim_dependencies( 2132 &mut self, 2133 prim_instance: &mut PrimitiveInstance, 2134 prim_spatial_node_index: SpatialNodeIndex, 2135 local_prim_rect: LayoutRect, 2136 frame_context: &FrameVisibilityContext, 2137 data_stores: &DataStores, 2138 clip_store: &ClipStore, 2139 pictures: &[PicturePrimitive], 2140 resource_cache: &mut ResourceCache, 2141 color_bindings: &ColorBindingStorage, 2142 surface_stack: &[(PictureIndex, SurfaceIndex)], 2143 composite_state: &mut CompositeState, 2144 gpu_buffer: &mut GpuBufferBuilderF, 2145 scratch: &mut PrimitiveScratchBuffer, 2146 is_root_tile_cache: bool, 2147 surfaces: &mut [SurfaceInfo], 2148 profile: &mut TransactionProfile, 2149 ) -> VisibilityState { 2150 use SurfacePromotionFailure::*; 2151 2152 // This primitive exists on the last element on the current surface stack. 2153 profile_scope!("update_prim_dependencies"); 2154 let prim_surface_index = surface_stack.last().unwrap().1; 2155 let prim_clip_chain = &prim_instance.vis.clip_chain; 2156 2157 // If the primitive is directly drawn onto this picture cache surface, then 2158 // the pic_coverage_rect is in the same space. If not, we need to map it from 2159 // the intermediate picture space into the picture cache space. 2160 let on_picture_surface = prim_surface_index == self.surface_index; 2161 let pic_coverage_rect = if on_picture_surface { 2162 prim_clip_chain.pic_coverage_rect 2163 } else { 2164 // We want to get the rect in the tile cache picture space that this primitive 2165 // occupies, in order to enable correct invalidation regions. Each surface 2166 // that exists in the chain between this primitive and the tile cache surface 2167 // may have an arbitrary inflation factor (for example, in the case of a series 2168 // of nested blur elements). To account for this, step through the current 2169 // surface stack, mapping the primitive rect into each picture space, including 2170 // the inflation factor from each intermediate surface. 2171 let mut current_pic_coverage_rect = prim_clip_chain.pic_coverage_rect; 2172 let mut current_spatial_node_index = surfaces[prim_surface_index.0] 2173 .surface_spatial_node_index; 2174 2175 for (pic_index, surface_index) in surface_stack.iter().rev() { 2176 let surface = &surfaces[surface_index.0]; 2177 let pic = &pictures[pic_index.0]; 2178 2179 let map_local_to_parent = SpaceMapper::new_with_target( 2180 surface.surface_spatial_node_index, 2181 current_spatial_node_index, 2182 surface.unclipped_local_rect, 2183 frame_context.spatial_tree, 2184 ); 2185 2186 // Map the rect into the parent surface, and inflate if this surface requires 2187 // it. If the rect can't be mapping (e.g. due to an invalid transform) then 2188 // just bail out from the dependencies and cull this primitive. 2189 current_pic_coverage_rect = match map_local_to_parent.map(¤t_pic_coverage_rect) { 2190 Some(rect) => { 2191 // TODO(gw): The casts here are a hack. We have some interface inconsistencies 2192 // between layout/picture rects which don't really work with the 2193 // current unit system, since sometimes the local rect of a picture 2194 // is a LayoutRect, and sometimes it's a PictureRect. Consider how 2195 // we can improve this? 2196 pic.composite_mode.as_ref().unwrap().get_coverage( 2197 surface, 2198 Some(rect.cast_unit()), 2199 ).cast_unit() 2200 } 2201 None => { 2202 return VisibilityState::Culled; 2203 } 2204 }; 2205 2206 current_spatial_node_index = surface.surface_spatial_node_index; 2207 } 2208 2209 current_pic_coverage_rect 2210 }; 2211 2212 // Get the tile coordinates in the picture space. 2213 let (p0, p1) = self.get_tile_coords_for_rect(&pic_coverage_rect); 2214 2215 // If the primitive is outside the tiling rects, it's known to not 2216 // be visible. 2217 if p0.x == p1.x || p0.y == p1.y { 2218 return VisibilityState::Culled; 2219 } 2220 2221 // Build the list of resources that this primitive has dependencies on. 2222 let mut prim_info = PrimitiveDependencyInfo::new( 2223 prim_instance.uid(), 2224 pic_coverage_rect, 2225 ); 2226 2227 let mut sub_slice_index = self.sub_slices.len() - 1; 2228 2229 // Only need to evaluate sub-slice regions if we have compositor surfaces present 2230 if sub_slice_index > 0 { 2231 // Find the first sub-slice we can add this primitive to (we want to add 2232 // prims to the primary surface if possible, so they get subpixel AA). 2233 for (i, sub_slice) in self.sub_slices.iter_mut().enumerate() { 2234 let mut intersects_prohibited_region = false; 2235 2236 for surface in &mut sub_slice.compositor_surfaces { 2237 if pic_coverage_rect.intersects(&surface.prohibited_rect) { 2238 surface.prohibited_rect = surface.prohibited_rect.union(&pic_coverage_rect); 2239 2240 intersects_prohibited_region = true; 2241 } 2242 } 2243 2244 if !intersects_prohibited_region { 2245 sub_slice_index = i; 2246 break; 2247 } 2248 } 2249 } 2250 2251 // Include the prim spatial node, if differs relative to cache root. 2252 if prim_spatial_node_index != self.spatial_node_index { 2253 prim_info.spatial_nodes.push(prim_spatial_node_index); 2254 } 2255 2256 // If there was a clip chain, add any clip dependencies to the list for this tile. 2257 let clip_instances = &clip_store 2258 .clip_node_instances[prim_clip_chain.clips_range.to_range()]; 2259 for clip_instance in clip_instances { 2260 let clip = &data_stores.clip[clip_instance.handle]; 2261 2262 prim_info.clips.push(clip_instance.handle.uid()); 2263 2264 // If the clip has the same spatial node, the relative transform 2265 // will always be the same, so there's no need to depend on it. 2266 if clip.item.spatial_node_index != self.spatial_node_index 2267 && !prim_info.spatial_nodes.contains(&clip.item.spatial_node_index) { 2268 prim_info.spatial_nodes.push(clip.item.spatial_node_index); 2269 } 2270 } 2271 2272 // Certain primitives may select themselves to be a backdrop candidate, which is 2273 // then applied below. 2274 let mut backdrop_candidate = None; 2275 2276 // For pictures, we don't (yet) know the valid clip rect, so we can't correctly 2277 // use it to calculate the local bounding rect for the tiles. If we include them 2278 // then we may calculate a bounding rect that is too large, since it won't include 2279 // the clip bounds of the picture. Excluding them from the bounding rect here 2280 // fixes any correctness issues (the clips themselves are considered when we 2281 // consider the bounds of the primitives that are *children* of the picture), 2282 // however it does potentially result in some un-necessary invalidations of a 2283 // tile (in cases where the picture local rect affects the tile, but the clip 2284 // rect eventually means it doesn't affect that tile). 2285 // TODO(gw): Get picture clips earlier (during the initial picture traversal 2286 // pass) so that we can calculate these correctly. 2287 match prim_instance.kind { 2288 PrimitiveInstanceKind::Picture { pic_index,.. } => { 2289 // Pictures can depend on animated opacity bindings. 2290 let pic = &pictures[pic_index.0]; 2291 if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.composite_mode { 2292 prim_info.opacity_bindings.push(binding.into()); 2293 } 2294 } 2295 PrimitiveInstanceKind::Rectangle { data_handle, color_binding_index, .. } => { 2296 // Rectangles can only form a backdrop candidate if they are known opaque. 2297 // TODO(gw): We could resolve the opacity binding here, but the common 2298 // case for background rects is that they don't have animated opacity. 2299 let PrimitiveTemplateKind::Rectangle { color, .. } = data_stores.prim[data_handle].kind; 2300 let color = frame_context.scene_properties.resolve_color(&color); 2301 if color.a >= 1.0 { 2302 backdrop_candidate = Some(BackdropInfo { 2303 opaque_rect: pic_coverage_rect, 2304 spanning_opaque_color: None, 2305 kind: Some(BackdropKind::Color { color }), 2306 backdrop_rect: pic_coverage_rect, 2307 }); 2308 } 2309 2310 if color_binding_index != ColorBindingIndex::INVALID { 2311 prim_info.color_binding = Some(color_bindings[color_binding_index].into()); 2312 } 2313 } 2314 PrimitiveInstanceKind::Image { data_handle, ref mut compositor_surface_kind, .. } => { 2315 let image_key = &data_stores.image[data_handle]; 2316 let image_data = &image_key.kind; 2317 2318 // For now, assume that for compositor surface purposes, any RGBA image may be 2319 // translucent. See the comment in `add_prim` in this source file for more 2320 // details. We'll leave the `is_opaque` code branches here, but disabled, as 2321 // in future we will want to support this case correctly. 2322 let mut is_opaque = false; 2323 2324 if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) { 2325 // For an image to be a possible opaque backdrop, it must: 2326 // - Have a valid, opaque image descriptor 2327 // - Not use tiling (since they can fail to draw) 2328 // - Not having any spacing / padding 2329 // - Have opaque alpha in the instance (flattened) color 2330 if image_properties.descriptor.is_opaque() && 2331 image_properties.tiling.is_none() && 2332 image_data.tile_spacing == LayoutSize::zero() && 2333 image_data.color.a >= 1.0 { 2334 backdrop_candidate = Some(BackdropInfo { 2335 opaque_rect: pic_coverage_rect, 2336 spanning_opaque_color: None, 2337 kind: None, 2338 backdrop_rect: PictureRect::zero(), 2339 }); 2340 } 2341 2342 is_opaque = image_properties.descriptor.is_opaque(); 2343 } 2344 2345 let mut promotion_result: Result<CompositorSurfaceKind, SurfacePromotionFailure> = Ok(CompositorSurfaceKind::Blit); 2346 if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { 2347 // Only consider promoting Images if all of our YuvImages have been 2348 // processed (whether they were promoted or not). 2349 if self.yuv_images_remaining > 0 { 2350 promotion_result = Err(ImageWaitingOnYuvImage); 2351 } else { 2352 promotion_result = self.can_promote_to_surface(prim_clip_chain, 2353 prim_spatial_node_index, 2354 is_root_tile_cache, 2355 sub_slice_index, 2356 CompositorSurfaceKind::Overlay, 2357 pic_coverage_rect, 2358 frame_context, 2359 data_stores, 2360 clip_store, 2361 composite_state, 2362 false); 2363 } 2364 2365 // Native OS compositors (DC and CA, at least) support premultiplied alpha 2366 // only. If we have an image that's not pre-multiplied alpha, we can't promote it. 2367 if image_data.alpha_type == AlphaType::Alpha { 2368 promotion_result = Err(NotPremultipliedAlpha); 2369 } 2370 2371 if let Ok(kind) = promotion_result { 2372 promotion_result = self.setup_compositor_surfaces_rgb( 2373 sub_slice_index, 2374 &mut prim_info, 2375 image_key.common.flags, 2376 local_prim_rect, 2377 prim_clip_chain, 2378 prim_spatial_node_index, 2379 pic_coverage_rect, 2380 frame_context, 2381 data_stores, 2382 clip_store, 2383 ImageDependency { 2384 key: image_data.key, 2385 generation: resource_cache.get_image_generation(image_data.key), 2386 }, 2387 image_data.key, 2388 resource_cache, 2389 composite_state, 2390 gpu_buffer, 2391 image_data.image_rendering, 2392 is_opaque, 2393 kind, 2394 ); 2395 } 2396 } 2397 2398 if let Ok(kind) = promotion_result { 2399 *compositor_surface_kind = kind; 2400 2401 if kind == CompositorSurfaceKind::Overlay { 2402 profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS); 2403 return VisibilityState::Culled; 2404 } 2405 2406 assert!(kind == CompositorSurfaceKind::Blit, "Image prims should either be overlays or blits."); 2407 } else { 2408 // In Err case, we handle as a blit, and proceed. 2409 self.report_promotion_failure(promotion_result, pic_coverage_rect, false); 2410 *compositor_surface_kind = CompositorSurfaceKind::Blit; 2411 } 2412 2413 if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { 2414 profile.inc(profiler::COMPOSITOR_SURFACE_BLITS); 2415 } 2416 2417 prim_info.images.push(ImageDependency { 2418 key: image_data.key, 2419 generation: resource_cache.get_image_generation(image_data.key), 2420 }); 2421 } 2422 PrimitiveInstanceKind::YuvImage { data_handle, ref mut compositor_surface_kind, .. } => { 2423 let prim_data = &data_stores.yuv_image[data_handle]; 2424 2425 let mut promotion_result: Result<CompositorSurfaceKind, SurfacePromotionFailure> = Ok(CompositorSurfaceKind::Blit); 2426 if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { 2427 // Note if this is one of the YuvImages we were considering for 2428 // surface promotion. We only care for primitives that were added 2429 // to us, indicated by is_root_tile_cache. Those are the only ones 2430 // that were added to the TileCacheParams that configured the 2431 // current scene. 2432 if is_root_tile_cache { 2433 self.yuv_images_remaining -= 1; 2434 } 2435 2436 // Should we force the promotion of this surface? We'll force it if promotion 2437 // is necessary for correct color display. 2438 let force = prim_data.kind.color_depth.bit_depth() > 8; 2439 2440 let promotion_attempts = 2441 [CompositorSurfaceKind::Overlay, CompositorSurfaceKind::Underlay]; 2442 2443 for kind in promotion_attempts { 2444 // Since this might be an attempt after an earlier error, clear the flag 2445 // so that we are allowed to report another error. 2446 promotion_result = self.can_promote_to_surface( 2447 prim_clip_chain, 2448 prim_spatial_node_index, 2449 is_root_tile_cache, 2450 sub_slice_index, 2451 kind, 2452 pic_coverage_rect, 2453 frame_context, 2454 data_stores, 2455 clip_store, 2456 composite_state, 2457 force); 2458 if promotion_result.is_ok() { 2459 break; 2460 } 2461 2462 // We couldn't promote, but did we give up because the slice is marked 2463 // atomic? If that was the reason, and the YuvImage is wide color, 2464 // failing to promote will flatten the colors and look terrible. Let's 2465 // ignore the atomic slice restriction in such a case. 2466 if let Err(SliceAtomic) = promotion_result { 2467 if prim_data.kind. color_depth != ColorDepth::Color8 { 2468 // Let's promote with the attempted kind. 2469 promotion_result = Ok(kind); 2470 break; 2471 } 2472 } 2473 } 2474 2475 // TODO(gw): When we support RGBA images for external surfaces, we also 2476 // need to check if opaque (YUV images are implicitly opaque). 2477 2478 // If this primitive is being promoted to a surface, construct an external 2479 // surface descriptor for use later during batching and compositing. We only 2480 // add the image keys for this primitive as a dependency if this is _not_ 2481 // a promoted surface, since we don't want the tiles to invalidate when the 2482 // video content changes, if it's a compositor surface! 2483 if let Ok(kind) = promotion_result { 2484 // Build dependency for each YUV plane, with current image generation for 2485 // later detection of when the composited surface has changed. 2486 let mut image_dependencies = [ImageDependency::INVALID; 3]; 2487 for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) { 2488 *dep = ImageDependency { 2489 key, 2490 generation: resource_cache.get_image_generation(key), 2491 } 2492 } 2493 2494 promotion_result = self.setup_compositor_surfaces_yuv( 2495 sub_slice_index, 2496 &mut prim_info, 2497 prim_data.common.flags, 2498 local_prim_rect, 2499 prim_clip_chain, 2500 prim_spatial_node_index, 2501 pic_coverage_rect, 2502 frame_context, 2503 data_stores, 2504 clip_store, 2505 &image_dependencies, 2506 &prim_data.kind.yuv_key, 2507 resource_cache, 2508 composite_state, 2509 gpu_buffer, 2510 prim_data.kind.image_rendering, 2511 prim_data.kind.color_depth, 2512 prim_data.kind.color_space.with_range(prim_data.kind.color_range), 2513 prim_data.kind.format, 2514 kind, 2515 ); 2516 } 2517 } 2518 2519 // Store on the YUV primitive instance whether this is a promoted surface. 2520 // This is used by the batching code to determine whether to draw the 2521 // image to the content tiles, or just a transparent z-write. 2522 if let Ok(kind) = promotion_result { 2523 *compositor_surface_kind = kind; 2524 if kind == CompositorSurfaceKind::Overlay { 2525 profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS); 2526 return VisibilityState::Culled; 2527 } 2528 2529 profile.inc(profiler::COMPOSITOR_SURFACE_UNDERLAYS); 2530 } else { 2531 // In Err case, we handle as a blit, and proceed. 2532 self.report_promotion_failure(promotion_result, pic_coverage_rect, false); 2533 *compositor_surface_kind = CompositorSurfaceKind::Blit; 2534 if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { 2535 profile.inc(profiler::COMPOSITOR_SURFACE_BLITS); 2536 } 2537 } 2538 2539 if *compositor_surface_kind == CompositorSurfaceKind::Blit { 2540 prim_info.images.extend( 2541 prim_data.kind.yuv_key.iter().map(|key| { 2542 ImageDependency { 2543 key: *key, 2544 generation: resource_cache.get_image_generation(*key), 2545 } 2546 }) 2547 ); 2548 } 2549 } 2550 PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { 2551 let border_data = &data_stores.image_border[data_handle].kind; 2552 prim_info.images.push(ImageDependency { 2553 key: border_data.request.key, 2554 generation: resource_cache.get_image_generation(border_data.request.key), 2555 }); 2556 } 2557 PrimitiveInstanceKind::LinearGradient { data_handle, .. } 2558 | PrimitiveInstanceKind::CachedLinearGradient { data_handle, .. } => { 2559 let gradient_data = &data_stores.linear_grad[data_handle]; 2560 if gradient_data.stops_opacity.is_opaque 2561 && gradient_data.tile_spacing == LayoutSize::zero() 2562 { 2563 backdrop_candidate = Some(BackdropInfo { 2564 opaque_rect: pic_coverage_rect, 2565 spanning_opaque_color: None, 2566 kind: None, 2567 backdrop_rect: PictureRect::zero(), 2568 }); 2569 } 2570 } 2571 PrimitiveInstanceKind::ConicGradient { data_handle, .. } => { 2572 let gradient_data = &data_stores.conic_grad[data_handle]; 2573 if gradient_data.stops_opacity.is_opaque 2574 && gradient_data.tile_spacing == LayoutSize::zero() 2575 { 2576 backdrop_candidate = Some(BackdropInfo { 2577 opaque_rect: pic_coverage_rect, 2578 spanning_opaque_color: None, 2579 kind: None, 2580 backdrop_rect: PictureRect::zero(), 2581 }); 2582 } 2583 } 2584 PrimitiveInstanceKind::RadialGradient { data_handle, .. } => { 2585 let gradient_data = &data_stores.radial_grad[data_handle]; 2586 if gradient_data.stops_opacity.is_opaque 2587 && gradient_data.tile_spacing == LayoutSize::zero() 2588 { 2589 backdrop_candidate = Some(BackdropInfo { 2590 opaque_rect: pic_coverage_rect, 2591 spanning_opaque_color: None, 2592 kind: None, 2593 backdrop_rect: PictureRect::zero(), 2594 }); 2595 } 2596 } 2597 PrimitiveInstanceKind::BackdropCapture { .. } => {} 2598 PrimitiveInstanceKind::BackdropRender { pic_index, .. } => { 2599 // If the area that the backdrop covers in the space of the surface it draws on 2600 // is empty, skip any sub-graph processing. This is not just a performance win, 2601 // it also ensures that we don't do a deferred dirty test that invalidates a tile 2602 // even if the tile isn't actually dirty, which can cause panics later in the 2603 // WR pipeline. 2604 if !pic_coverage_rect.is_empty() { 2605 // Mark that we need the sub-graph this render depends on so that 2606 // we don't skip it during the prepare pass 2607 scratch.required_sub_graphs.insert(pic_index); 2608 2609 // If this is a sub-graph, register the bounds on any affected tiles 2610 // so we know how much to expand the content tile by. 2611 let sub_slice = &mut self.sub_slices[sub_slice_index]; 2612 2613 let mut surface_info = Vec::new(); 2614 for (pic_index, surface_index) in surface_stack.iter().rev() { 2615 let pic = &pictures[pic_index.0]; 2616 surface_info.push((pic.composite_mode.as_ref().unwrap().clone(), *surface_index)); 2617 } 2618 2619 for y in p0.y .. p1.y { 2620 for x in p0.x .. p1.x { 2621 let key = TileOffset::new(x, y); 2622 let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile"); 2623 tile.cached_surface.sub_graphs.push((pic_coverage_rect, surface_info.clone())); 2624 } 2625 } 2626 2627 // For backdrop-filter, we need to check if any of the dirty rects 2628 // in tiles that are affected by the filter primitive are dirty. 2629 self.deferred_dirty_tests.push(DeferredDirtyTest { 2630 tile_rect: TileRect::new(p0, p1), 2631 prim_rect: pic_coverage_rect, 2632 }); 2633 } 2634 } 2635 PrimitiveInstanceKind::LineDecoration { .. } | 2636 PrimitiveInstanceKind::NormalBorder { .. } | 2637 PrimitiveInstanceKind::BoxShadow { .. } | 2638 PrimitiveInstanceKind::TextRun { .. } => { 2639 // These don't contribute dependencies 2640 } 2641 }; 2642 2643 // Calculate the screen rect in local space. When we calculate backdrops, we 2644 // care only that they cover the visible rect (based off the local clip), and 2645 // don't have any overlapping prims in the visible rect. 2646 let visible_local_clip_rect = self.local_clip_rect.intersection(&self.screen_rect_in_pic_space).unwrap_or_default(); 2647 if pic_coverage_rect.intersects(&visible_local_clip_rect) { 2648 self.found_prims_after_backdrop = true; 2649 } 2650 2651 // If this primitive considers itself a backdrop candidate, apply further 2652 // checks to see if it matches all conditions to be a backdrop. 2653 let mut vis_flags = PrimitiveVisibilityFlags::empty(); 2654 let sub_slice = &mut self.sub_slices[sub_slice_index]; 2655 if let Some(mut backdrop_candidate) = backdrop_candidate { 2656 // Update whether the surface that this primitive exists on 2657 // can be considered opaque. Any backdrop kind other than 2658 // a clear primitive (e.g. color, gradient, image) can be 2659 // considered. 2660 match backdrop_candidate.kind { 2661 Some(BackdropKind::Color { .. }) | None => { 2662 let surface = &mut surfaces[prim_surface_index.0]; 2663 2664 let is_same_coord_system = frame_context.spatial_tree.is_matching_coord_system( 2665 prim_spatial_node_index, 2666 surface.surface_spatial_node_index, 2667 ); 2668 2669 // To be an opaque backdrop, it must: 2670 // - Be the same coordinate system (axis-aligned) 2671 // - Have no clip mask 2672 // - Have a rect that covers the surface local rect 2673 if is_same_coord_system && 2674 !prim_clip_chain.needs_mask && 2675 prim_clip_chain.pic_coverage_rect.contains_box(&surface.unclipped_local_rect) 2676 { 2677 // Note that we use `prim_clip_chain.pic_clip_rect` here rather 2678 // than `backdrop_candidate.opaque_rect`. The former is in the 2679 // local space of the surface, the latter is in the local space 2680 // of the top level tile-cache. 2681 surface.is_opaque = true; 2682 } 2683 } 2684 } 2685 2686 // Check a number of conditions to see if we can consider this 2687 // primitive as an opaque backdrop rect. Several of these are conservative 2688 // checks and could be relaxed in future. However, these checks 2689 // are quick and capture the common cases of background rects and images. 2690 // Specifically, we currently require: 2691 // - The primitive is on the main picture cache surface. 2692 // - Same coord system as picture cache (ensures rects are axis-aligned). 2693 // - No clip masks exist. 2694 let same_coord_system = frame_context.spatial_tree.is_matching_coord_system( 2695 prim_spatial_node_index, 2696 self.spatial_node_index, 2697 ); 2698 2699 let is_suitable_backdrop = same_coord_system && on_picture_surface; 2700 2701 if sub_slice_index == 0 && 2702 is_suitable_backdrop && 2703 sub_slice.compositor_surfaces.is_empty() { 2704 2705 // If the backdrop candidate has a clip-mask, try to extract an opaque inner 2706 // rect that is safe to use for subpixel rendering 2707 if prim_clip_chain.needs_mask { 2708 backdrop_candidate.opaque_rect = clip_store 2709 .get_inner_rect_for_clip_chain( 2710 prim_clip_chain, 2711 &data_stores.clip, 2712 frame_context.spatial_tree, 2713 ) 2714 .unwrap_or(PictureRect::zero()); 2715 } 2716 2717 // We set the backdrop opaque_rect here, indicating the coverage area, which 2718 // is useful for calculate_subpixel_mode. We will only set the backdrop kind 2719 // if it covers the visible rect. 2720 if backdrop_candidate.opaque_rect.contains_box(&self.backdrop.opaque_rect) { 2721 self.backdrop.opaque_rect = backdrop_candidate.opaque_rect; 2722 } 2723 2724 if let Some(kind) = backdrop_candidate.kind { 2725 if backdrop_candidate.opaque_rect.contains_box(&visible_local_clip_rect) { 2726 self.found_prims_after_backdrop = false; 2727 self.backdrop.kind = Some(kind); 2728 self.backdrop.backdrop_rect = backdrop_candidate.opaque_rect; 2729 2730 // If we have a color backdrop that spans the entire local rect, mark 2731 // the visibility flags of the primitive so it is skipped during batching 2732 // (and also clears any previous primitives). Additionally, update our 2733 // background color to match the backdrop color, which will ensure that 2734 // our tiles are cleared to this color. 2735 let BackdropKind::Color { color } = kind; 2736 if backdrop_candidate.opaque_rect.contains_box(&self.local_rect) { 2737 vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP; 2738 self.backdrop.spanning_opaque_color = Some(color); 2739 } 2740 } 2741 } 2742 } 2743 } 2744 2745 // Record any new spatial nodes in the used list. 2746 for spatial_node_index in &prim_info.spatial_nodes { 2747 self.spatial_node_comparer.register_used_transform( 2748 *spatial_node_index, 2749 self.frame_id, 2750 frame_context.spatial_tree, 2751 ); 2752 } 2753 2754 // Normalize the tile coordinates before adding to tile dependencies. 2755 // For each affected tile, mark any of the primitive dependencies. 2756 for y in p0.y .. p1.y { 2757 for x in p0.x .. p1.x { 2758 // TODO(gw): Convert to 2d array temporarily to avoid hash lookups per-tile? 2759 let key = TileOffset::new(x, y); 2760 let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile"); 2761 2762 tile.add_prim_dependency(&prim_info); 2763 } 2764 } 2765 2766 VisibilityState::Visible { 2767 vis_flags, 2768 sub_slice_index: SubSliceIndex::new(sub_slice_index), 2769 } 2770 } 2771 2772 /// Print debug information about this picture cache to a tree printer. 2773 pub fn print(&self) { 2774 // TODO(gw): This initial implementation is very basic - just printing 2775 // the picture cache state to stdout. In future, we can 2776 // make this dump each frame to a file, and produce a report 2777 // stating which frames had invalidations. This will allow 2778 // diff'ing the invalidation states in a visual tool. 2779 let mut pt = PrintTree::new("Picture Cache"); 2780 2781 pt.new_level(format!("Slice {:?}", self.slice)); 2782 2783 pt.add_item(format!("background_color: {:?}", self.background_color)); 2784 2785 for (sub_slice_index, sub_slice) in self.sub_slices.iter().enumerate() { 2786 pt.new_level(format!("SubSlice {:?}", sub_slice_index)); 2787 2788 for y in self.tile_bounds_p0.y .. self.tile_bounds_p1.y { 2789 for x in self.tile_bounds_p0.x .. self.tile_bounds_p1.x { 2790 let key = TileOffset::new(x, y); 2791 let tile = &sub_slice.tiles[&key]; 2792 tile.print(&mut pt); 2793 } 2794 } 2795 2796 pt.end_level(); 2797 } 2798 2799 pt.end_level(); 2800 } 2801 2802 fn calculate_subpixel_mode(&self) -> SubpixelMode { 2803 // We can only consider the full opaque cases if there's no underlays 2804 if self.underlays.is_empty() { 2805 let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0); 2806 2807 // If the overall tile cache is known opaque, subpixel AA is allowed everywhere 2808 if has_opaque_bg_color { 2809 return SubpixelMode::Allow; 2810 } 2811 2812 // If the opaque backdrop rect covers the entire tile cache surface, 2813 // we can allow subpixel AA anywhere, skipping the per-text-run tests 2814 // later on during primitive preparation. 2815 if self.backdrop.opaque_rect.contains_box(&self.local_rect) { 2816 return SubpixelMode::Allow; 2817 } 2818 } 2819 2820 // If we didn't find any valid opaque backdrop, no subpixel AA allowed 2821 if self.backdrop.opaque_rect.is_empty() { 2822 return SubpixelMode::Deny; 2823 } 2824 2825 // Calculate a prohibited rect where we won't allow subpixel AA. 2826 // TODO(gw): This is conservative - it will disallow subpixel AA if there 2827 // are two underlay surfaces with text placed in between them. That's 2828 // probably unlikely to be an issue in practice, but maybe we should support 2829 // an array of prohibted rects? 2830 let prohibited_rect = self 2831 .underlays 2832 .iter() 2833 .fold( 2834 PictureRect::zero(), 2835 |acc, underlay| { 2836 acc.union(&underlay.local_rect) 2837 } 2838 ); 2839 2840 // If none of the simple cases above match, we need test where we can support subpixel AA. 2841 // TODO(gw): In future, it may make sense to have > 1 inclusion rect, 2842 // but this handles the common cases. 2843 // TODO(gw): If a text run gets animated such that it's moving in a way that is 2844 // sometimes intersecting with the video rect, this can result in subpixel 2845 // AA flicking on/off for that text run. It's probably very rare, but 2846 // something we should handle in future. 2847 SubpixelMode::Conditional { 2848 allowed_rect: self.backdrop.opaque_rect, 2849 prohibited_rect, 2850 } 2851 } 2852 2853 /// Apply any updates after prim dependency updates. This applies 2854 /// any late tile invalidations, and sets up the dirty rect and 2855 /// set of tile blits. 2856 pub fn post_update( 2857 &mut self, 2858 frame_context: &FrameVisibilityContext, 2859 composite_state: &mut CompositeState, 2860 resource_cache: &mut ResourceCache, 2861 ) { 2862 assert!(self.current_surface_traversal_depth == 0); 2863 2864 // TODO: Switch from the root node ot raster space. 2865 let visibility_node = frame_context.spatial_tree.root_reference_frame_index(); 2866 2867 self.dirty_region.reset(visibility_node, self.spatial_node_index); 2868 self.subpixel_mode = self.calculate_subpixel_mode(); 2869 2870 self.transform_index = composite_state.register_transform( 2871 self.local_to_raster, 2872 // TODO(gw): Once we support scaling of picture cache tiles during compositing, 2873 // that transform gets plugged in here! 2874 self.raster_to_device, 2875 ); 2876 2877 let map_pic_to_world = SpaceMapper::new_with_target( 2878 frame_context.root_spatial_node_index, 2879 self.spatial_node_index, 2880 frame_context.global_screen_world_rect, 2881 frame_context.spatial_tree, 2882 ); 2883 2884 // A simple GC of the native external surface cache, to remove and free any 2885 // surfaces that were not referenced during the update_prim_dependencies pass. 2886 self.external_native_surface_cache.retain(|_, surface| { 2887 if !surface.used_this_frame { 2888 // If we removed an external surface, we need to mark the dirty rects as 2889 // invalid so a full composite occurs on the next frame. 2890 composite_state.dirty_rects_are_valid = false; 2891 2892 resource_cache.destroy_compositor_surface(surface.native_surface_id); 2893 } 2894 2895 surface.used_this_frame 2896 }); 2897 2898 let pic_to_world_mapper = SpaceMapper::new_with_target( 2899 frame_context.root_spatial_node_index, 2900 self.spatial_node_index, 2901 frame_context.global_screen_world_rect, 2902 frame_context.spatial_tree, 2903 ); 2904 2905 let ctx = TileUpdateDirtyContext { 2906 pic_to_world_mapper, 2907 global_device_pixel_scale: frame_context.global_device_pixel_scale, 2908 opacity_bindings: &self.opacity_bindings, 2909 color_bindings: &self.color_bindings, 2910 local_rect: self.local_rect, 2911 invalidate_all: self.invalidate_all_tiles, 2912 }; 2913 2914 let mut state = TileUpdateDirtyState { 2915 resource_cache, 2916 composite_state, 2917 compare_cache: &mut self.compare_cache, 2918 spatial_node_comparer: &mut self.spatial_node_comparer, 2919 }; 2920 2921 // Step through each tile and invalidate if the dependencies have changed. Determine 2922 // the current opacity setting and whether it's changed. 2923 for sub_slice in &mut self.sub_slices { 2924 for tile in sub_slice.tiles.values_mut() { 2925 tile.update_dirty_and_valid_rects(&ctx, &mut state, frame_context); 2926 } 2927 } 2928 2929 // Process any deferred dirty checks 2930 for sub_slice in &mut self.sub_slices { 2931 for dirty_test in self.deferred_dirty_tests.drain(..) { 2932 // Calculate the total dirty rect from all tiles that this primitive affects 2933 let mut total_dirty_rect = PictureRect::zero(); 2934 2935 for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y { 2936 for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x { 2937 let key = TileOffset::new(x, y); 2938 let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile"); 2939 total_dirty_rect = total_dirty_rect.union(&tile.cached_surface.local_dirty_rect); 2940 } 2941 } 2942 2943 // If that dirty rect intersects with the local rect of the primitive 2944 // being checked, invalidate that region in all of the affected tiles. 2945 // TODO(gw): This is somewhat conservative, we could be more clever 2946 // here and avoid invalidating every tile when this changes. 2947 // We could also store the dirty rect only when the prim 2948 // is encountered, so that we don't invalidate if something 2949 // *after* the query in the rendering order affects invalidation. 2950 if total_dirty_rect.intersects(&dirty_test.prim_rect) { 2951 for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y { 2952 for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x { 2953 let key = TileOffset::new(x, y); 2954 let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile"); 2955 tile.invalidate( 2956 Some(dirty_test.prim_rect), 2957 InvalidationReason::SurfaceContentChanged, 2958 ); 2959 } 2960 } 2961 } 2962 } 2963 } 2964 2965 let mut ctx = TilePostUpdateContext { 2966 local_clip_rect: self.local_clip_rect, 2967 backdrop: None, 2968 current_tile_size: self.current_tile_size, 2969 z_id: ZBufferId::invalid(), 2970 underlays: &self.underlays, 2971 }; 2972 2973 let mut state = TilePostUpdateState { 2974 resource_cache, 2975 composite_state, 2976 }; 2977 2978 for (i, sub_slice) in self.sub_slices.iter_mut().enumerate().rev() { 2979 // The backdrop is only relevant for the first sub-slice 2980 if i == 0 { 2981 ctx.backdrop = Some(self.backdrop); 2982 } 2983 2984 for compositor_surface in sub_slice.compositor_surfaces.iter_mut().rev() { 2985 compositor_surface.descriptor.z_id = state.composite_state.z_generator.next(); 2986 } 2987 2988 ctx.z_id = state.composite_state.z_generator.next(); 2989 2990 for tile in sub_slice.tiles.values_mut() { 2991 tile.post_update(&ctx, &mut state, frame_context); 2992 } 2993 } 2994 2995 // Assign z-order for each underlay 2996 for underlay in self.underlays.iter_mut().rev() { 2997 underlay.z_id = state.composite_state.z_generator.next(); 2998 } 2999 3000 // Register any opaque external compositor surfaces as potential occluders. This 3001 // is especially useful when viewing video in full-screen mode, as it is 3002 // able to occlude every background tile (avoiding allocation, rasterizion 3003 // and compositing). 3004 3005 // Register any underlays as occluders where possible 3006 for underlay in &self.underlays { 3007 if let Some(world_surface_rect) = underlay.get_occluder_rect( 3008 &self.local_clip_rect, 3009 &map_pic_to_world, 3010 ) { 3011 composite_state.register_occluder( 3012 underlay.z_id, 3013 world_surface_rect, 3014 self.compositor_clip, 3015 ); 3016 } 3017 } 3018 3019 for sub_slice in &self.sub_slices { 3020 for compositor_surface in &sub_slice.compositor_surfaces { 3021 if compositor_surface.is_opaque { 3022 if let Some(world_surface_rect) = compositor_surface.descriptor.get_occluder_rect( 3023 &self.local_clip_rect, 3024 &map_pic_to_world, 3025 ) { 3026 composite_state.register_occluder( 3027 compositor_surface.descriptor.z_id, 3028 world_surface_rect, 3029 self.compositor_clip, 3030 ); 3031 } 3032 } 3033 } 3034 } 3035 3036 // Register the opaque region of this tile cache as an occluder, which 3037 // is used later in the frame to occlude other tiles. 3038 if !self.backdrop.opaque_rect.is_empty() { 3039 let z_id_backdrop = composite_state.z_generator.next(); 3040 3041 let backdrop_rect = self.backdrop.opaque_rect 3042 .intersection(&self.local_rect) 3043 .and_then(|r| { 3044 r.intersection(&self.local_clip_rect) 3045 }); 3046 3047 if let Some(backdrop_rect) = backdrop_rect { 3048 let world_backdrop_rect = map_pic_to_world 3049 .map(&backdrop_rect) 3050 .expect("bug: unable to map backdrop to world space"); 3051 3052 // Since we register the entire backdrop rect, use the opaque z-id for the 3053 // picture cache slice. 3054 composite_state.register_occluder( 3055 z_id_backdrop, 3056 world_backdrop_rect, 3057 self.compositor_clip, 3058 ); 3059 } 3060 } 3061 } 3062 } 3063 3064 3065 /// A SubSlice represents a potentially overlapping set of tiles within a picture cache. Most 3066 /// picture cache instances will have only a single sub-slice. The exception to this is when 3067 /// a picture cache has compositor surfaces, in which case sub slices are used to interleave 3068 /// content under or order the compositor surface(s). 3069 pub struct SubSlice { 3070 /// Hash of tiles present in this picture. 3071 pub tiles: FastHashMap<TileOffset, Box<Tile>>, 3072 /// The allocated compositor surfaces for this picture cache. May be None if 3073 /// not using native compositor, or if the surface was destroyed and needs 3074 /// to be reallocated next time this surface contains valid tiles. 3075 pub native_surface: Option<NativeSurface>, 3076 /// List of compositor surfaces that have been promoted from primitives 3077 /// in this tile cache. 3078 pub compositor_surfaces: Vec<CompositorSurface>, 3079 /// List of visible tiles to be composited for this subslice 3080 pub composite_tiles: Vec<CompositeTile>, 3081 /// Compositor descriptors of visible, opaque tiles (used by composite_state.push_surface) 3082 pub opaque_tile_descriptors: Vec<CompositeTileDescriptor>, 3083 /// Compositor descriptors of visible, alpha tiles (used by composite_state.push_surface) 3084 pub alpha_tile_descriptors: Vec<CompositeTileDescriptor>, 3085 } 3086 3087 impl SubSlice { 3088 /// Construct a new sub-slice 3089 fn new() -> Self { 3090 SubSlice { 3091 tiles: FastHashMap::default(), 3092 native_surface: None, 3093 compositor_surfaces: Vec::new(), 3094 composite_tiles: Vec::new(), 3095 opaque_tile_descriptors: Vec::new(), 3096 alpha_tile_descriptors: Vec::new(), 3097 } 3098 } 3099 3100 /// Reset the list of compositor surfaces that follow this sub-slice. 3101 /// Built per-frame, since APZ may change whether an image is suitable to be a compositor surface. 3102 fn reset(&mut self) { 3103 self.compositor_surfaces.clear(); 3104 self.composite_tiles.clear(); 3105 self.opaque_tile_descriptors.clear(); 3106 self.alpha_tile_descriptors.clear(); 3107 } 3108 3109 /// Resize the tile grid to match a new tile bounds 3110 fn resize(&mut self, new_tile_rect: TileRect) -> FastHashMap<TileOffset, Box<Tile>> { 3111 let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default()); 3112 self.tiles.reserve(new_tile_rect.area() as usize); 3113 3114 for y in new_tile_rect.min.y .. new_tile_rect.max.y { 3115 for x in new_tile_rect.min.x .. new_tile_rect.max.x { 3116 let key = TileOffset::new(x, y); 3117 let tile = old_tiles 3118 .remove(&key) 3119 .unwrap_or_else(|| { 3120 Box::new(Tile::new(key)) 3121 }); 3122 self.tiles.insert(key, tile); 3123 } 3124 } 3125 3126 old_tiles 3127 } 3128 } 3129 3130 #[derive(Clone, Copy, Debug)] 3131 enum SurfacePromotionFailure { 3132 ImageWaitingOnYuvImage, 3133 NotPremultipliedAlpha, 3134 OverlaySurfaceLimit, 3135 OverlayNeedsMask, 3136 UnderlayAlphaBackdrop, 3137 UnderlaySurfaceLimit, 3138 UnderlayIntersectsOverlay, 3139 UnderlayLowQualityZoom, 3140 NotRootTileCache, 3141 ComplexTransform, 3142 SliceAtomic, 3143 SizeTooLarge, 3144 } 3145 3146 impl Display for SurfacePromotionFailure { 3147 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 3148 write!( 3149 f, 3150 "{}", 3151 match *self { 3152 SurfacePromotionFailure::ImageWaitingOnYuvImage => "Image prim waiting for all YuvImage prims to be considered for promotion", 3153 SurfacePromotionFailure::NotPremultipliedAlpha => "does not use premultiplied alpha", 3154 SurfacePromotionFailure::OverlaySurfaceLimit => "hit the overlay surface limit", 3155 SurfacePromotionFailure::OverlayNeedsMask => "overlay not allowed for prim with mask", 3156 SurfacePromotionFailure::UnderlayAlphaBackdrop => "underlay requires an opaque backdrop", 3157 SurfacePromotionFailure::UnderlaySurfaceLimit => "hit the underlay surface limit", 3158 SurfacePromotionFailure::UnderlayIntersectsOverlay => "underlay intersects already-promoted overlay", 3159 SurfacePromotionFailure::UnderlayLowQualityZoom => "underlay not allowed during low-quality pinch zoom", 3160 SurfacePromotionFailure::NotRootTileCache => "is not on a root tile cache", 3161 SurfacePromotionFailure::ComplexTransform => "has a complex transform", 3162 SurfacePromotionFailure::SliceAtomic => "slice is atomic", 3163 SurfacePromotionFailure::SizeTooLarge => "surface is too large for compositor", 3164 }.to_owned() 3165 ) 3166 } 3167 } 3168 3169 // Immutable context passed to picture cache tiles during pre_update 3170 struct TilePreUpdateContext { 3171 /// Maps from picture cache coords -> world space coords. 3172 pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>, 3173 3174 /// The optional background color of the picture cache instance 3175 background_color: Option<ColorF>, 3176 3177 /// The visible part of the screen in world coords. 3178 global_screen_world_rect: WorldRect, 3179 3180 /// Current size of tiles in picture units. 3181 tile_size: PictureSize, 3182 3183 /// The current frame id for this picture cache 3184 frame_id: FrameId, 3185 } 3186 3187 // Immutable context passed to picture cache tiles during post_update 3188 struct TilePostUpdateContext<'a> { 3189 /// The local clip rect (in picture space) of the entire picture cache 3190 local_clip_rect: PictureRect, 3191 3192 /// The calculated backdrop information for this cache instance. 3193 backdrop: Option<BackdropInfo>, 3194 3195 /// Current size in device pixels of tiles for this cache 3196 current_tile_size: DeviceIntSize, 3197 3198 /// Pre-allocated z-id to assign to tiles during post_update. 3199 z_id: ZBufferId, 3200 3201 /// The list of compositor underlays for this picture cache 3202 underlays: &'a [ExternalSurfaceDescriptor], 3203 } 3204 3205 // Mutable state passed to picture cache tiles during post_update 3206 struct TilePostUpdateState<'a> { 3207 /// Allow access to the texture cache for requesting tiles 3208 resource_cache: &'a mut ResourceCache, 3209 3210 /// Current configuration and setup for compositing all the picture cache tiles in renderer. 3211 composite_state: &'a mut CompositeState, 3212 }