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