Skip to main content

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(&current_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}