tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

slice_builder.rs (29572B)


      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 use api::{BorderRadius, ClipId, ClipMode, ColorF, DebugFlags, PrimitiveFlags, QualitySettings, RasterSpace};
      6 use api::units::*;
      7 use crate::clip::{ClipItemKeyKind, ClipNodeId, ClipTreeBuilder};
      8 use crate::frame_builder::FrameBuilderConfig;
      9 use crate::internal_types::FastHashMap;
     10 use crate::picture::{PrimitiveList, PictureCompositeMode, PicturePrimitive, Picture3DContext, PictureFlags};
     11 use crate::tile_cache::{SliceId, TileCacheParams};
     12 use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PictureIndex};
     13 use crate::scene_building::SliceFlags;
     14 use crate::scene_builder_thread::Interners;
     15 use crate::spatial_tree::{SpatialNodeIndex, SceneSpatialTree};
     16 use crate::util::VecHelper;
     17 use std::mem;
     18 
     19 /*
     20 Types and functionality related to picture caching. In future, we'll
     21 move more and more of the existing functionality out of picture.rs
     22 and into here.
     23 */
     24 
     25 // If the page would create too many slices (an arbitrary definition where
     26 // it's assumed the GPU memory + compositing overhead would be too high)
     27 // then create a single picture cache for the remaining content. This at
     28 // least means that we can cache small content changes efficiently when
     29 // scrolling isn't occurring. Scrolling regions will be handled reasonably
     30 // efficiently by the dirty rect tracking (since it's likely that if the
     31 // page has so many slices there isn't a single major scroll region).
     32 const MAX_CACHE_SLICES: usize = 16;
     33 
     34 struct SliceDescriptor {
     35    prim_list: PrimitiveList,
     36    scroll_root: SpatialNodeIndex,
     37 }
     38 
     39 enum SliceKind {
     40    Default {
     41        secondary_slices: Vec<SliceDescriptor>,
     42    },
     43    Atomic {
     44        prim_list: PrimitiveList,
     45    },
     46 }
     47 
     48 impl SliceKind {
     49    fn default() -> Self {
     50        SliceKind::Default {
     51            secondary_slices: Vec::new(),
     52        }
     53    }
     54 }
     55 
     56 struct PrimarySlice {
     57    /// Whether this slice is atomic or has secondary slice(s)
     58    kind: SliceKind,
     59    /// Optional background color of this slice
     60    background_color: Option<ColorF>,
     61    /// Optional root clip for the iframe
     62    iframe_clip: Option<ClipId>,
     63    /// Information about how to draw and composite this slice
     64    slice_flags: SliceFlags,
     65 }
     66 
     67 impl PrimarySlice {
     68    fn new(
     69        slice_flags: SliceFlags,
     70        iframe_clip: Option<ClipId>,
     71        background_color: Option<ColorF>,
     72    ) -> Self {
     73        PrimarySlice {
     74            kind: SliceKind::default(),
     75            background_color,
     76            iframe_clip,
     77            slice_flags,
     78        }
     79    }
     80 
     81    fn has_too_many_slices(&self) -> bool {
     82        match self.kind {
     83            SliceKind::Atomic { .. } => false,
     84            SliceKind::Default { ref secondary_slices } => secondary_slices.len() > MAX_CACHE_SLICES,
     85        }
     86    }
     87 
     88    fn merge(&mut self) {
     89        self.slice_flags |= SliceFlags::IS_ATOMIC;
     90 
     91        let old = mem::replace(
     92            &mut self.kind,
     93            SliceKind::Default { secondary_slices: Vec::new() },
     94        );
     95 
     96        self.kind = match old {
     97            SliceKind::Default { mut secondary_slices } => {
     98                let mut prim_list = PrimitiveList::empty();
     99 
    100                for descriptor in secondary_slices.drain(..) {
    101                    prim_list.merge(descriptor.prim_list);
    102                }
    103 
    104                SliceKind::Atomic {
    105                    prim_list,
    106                }
    107            }
    108            atomic => atomic,
    109        }
    110    }
    111 }
    112 
    113 /// Used during scene building to construct the list of pending tile caches.
    114 pub struct TileCacheBuilder {
    115    /// List of tile caches that have been created so far (last in the list is currently active).
    116    primary_slices: Vec<PrimarySlice>,
    117    /// Cache the previous scroll root search for a spatial node, since they are often the same.
    118    prev_scroll_root_cache: (SpatialNodeIndex, SpatialNodeIndex),
    119    /// Handle to the root reference frame
    120    root_spatial_node_index: SpatialNodeIndex,
    121    /// Debug flags to provide to our TileCacheInstances.
    122    debug_flags: DebugFlags,
    123 }
    124 
    125 /// The output of a tile cache builder, containing all details needed to construct the
    126 /// tile cache(s) for the next scene, and retain tiles from the previous frame when sent
    127 /// send to the frame builder.
    128 pub struct TileCacheConfig {
    129    /// Mapping of slice id to the parameters needed to construct this tile cache.
    130    pub tile_caches: FastHashMap<SliceId, TileCacheParams>,
    131    /// Number of picture cache slices that were created (for profiler)
    132    pub picture_cache_slice_count: usize,
    133 }
    134 
    135 impl TileCacheConfig {
    136    pub fn new(picture_cache_slice_count: usize) -> Self {
    137        TileCacheConfig {
    138            tile_caches: FastHashMap::default(),
    139            picture_cache_slice_count,
    140        }
    141    }
    142 }
    143 
    144 impl TileCacheBuilder {
    145    /// Construct a new tile cache builder.
    146    pub fn new(
    147        root_spatial_node_index: SpatialNodeIndex,
    148        background_color: Option<ColorF>,
    149        debug_flags: DebugFlags,
    150    ) -> Self {
    151        TileCacheBuilder {
    152            primary_slices: vec![PrimarySlice::new(SliceFlags::empty(), None, background_color)],
    153            prev_scroll_root_cache: (SpatialNodeIndex::INVALID, SpatialNodeIndex::INVALID),
    154            root_spatial_node_index,
    155            debug_flags,
    156        }
    157    }
    158 
    159    pub fn make_current_slice_atomic(&mut self) {
    160        self.primary_slices
    161            .last_mut()
    162            .unwrap()
    163            .merge();
    164    }
    165 
    166    /// Returns true if the current slice has no primitives added yet
    167    pub fn is_current_slice_empty(&self) -> bool {
    168        match self.primary_slices.last() {
    169            Some(slice) => {
    170                match slice.kind {
    171                    SliceKind::Default { ref secondary_slices } => {
    172                        secondary_slices.is_empty()
    173                    }
    174                    SliceKind::Atomic { ref prim_list } => {
    175                        prim_list.is_empty()
    176                    }
    177                }
    178            }
    179            None => {
    180                true
    181            }
    182        }
    183    }
    184 
    185    /// Set a barrier that forces a new tile cache next time a prim is added.
    186    pub fn add_tile_cache_barrier(
    187        &mut self,
    188        slice_flags: SliceFlags,
    189        iframe_clip: Option<ClipId>,
    190    ) {
    191        let new_slice = PrimarySlice::new(
    192            slice_flags,
    193            iframe_clip,
    194            None,
    195        );
    196 
    197        self.primary_slices.push(new_slice);
    198    }
    199 
    200    /// Create a new tile cache for an existing prim_list
    201    fn build_tile_cache(
    202        &mut self,
    203        prim_list: PrimitiveList,
    204        spatial_tree: &SceneSpatialTree,
    205    ) -> Option<SliceDescriptor> {
    206        if prim_list.is_empty() {
    207            return None;
    208        }
    209 
    210        // Iterate the clusters and determine which is the most commonly occurring
    211        // scroll root. This is a reasonable heuristic to decide which spatial node
    212        // should be considered the scroll root of this tile cache, in order to
    213        // minimize the invalidations that occur due to scrolling. It's often the
    214        // case that a blend container will have only a single scroll root.
    215        let mut scroll_root_occurrences = FastHashMap::default();
    216 
    217        for cluster in &prim_list.clusters {
    218            // If we encounter a cluster which has an unknown spatial node,
    219            // we don't include that in the set of spatial nodes that we
    220            // are trying to find scroll roots for. Later on, in finalize_picture,
    221            // the cluster spatial node will be updated to the selected scroll root.
    222            if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN {
    223                continue;
    224            }
    225 
    226            let scroll_root = find_scroll_root(
    227                cluster.spatial_node_index,
    228                &mut self.prev_scroll_root_cache,
    229                spatial_tree,
    230                true,
    231            );
    232 
    233            *scroll_root_occurrences.entry(scroll_root).or_insert(0) += 1;
    234        }
    235 
    236        // We can't just select the most commonly occurring scroll root in this
    237        // primitive list. If that is a nested scroll root, there may be
    238        // primitives in the list that are outside that scroll root, which
    239        // can cause panics when calculating relative transforms. To ensure
    240        // this doesn't happen, only retain scroll root candidates that are
    241        // also ancestors of every other scroll root candidate.
    242        let scroll_roots: Vec<SpatialNodeIndex> = scroll_root_occurrences
    243            .keys()
    244            .cloned()
    245            .collect();
    246 
    247        scroll_root_occurrences.retain(|parent_spatial_node_index, _| {
    248            scroll_roots.iter().all(|child_spatial_node_index| {
    249                parent_spatial_node_index == child_spatial_node_index ||
    250                spatial_tree.is_ancestor(
    251                    *parent_spatial_node_index,
    252                    *child_spatial_node_index,
    253                )
    254            })
    255        });
    256 
    257        // Select the scroll root by finding the most commonly occurring one
    258        let scroll_root = scroll_root_occurrences
    259            .iter()
    260            .max_by_key(|entry | entry.1)
    261            .map(|(spatial_node_index, _)| *spatial_node_index)
    262            .unwrap_or(self.root_spatial_node_index);
    263 
    264        Some(SliceDescriptor {
    265            scroll_root,
    266            prim_list,
    267        })
    268    }
    269 
    270    /// Add a primitive, either to the current tile cache, or a new one, depending on various conditions.
    271    pub fn add_prim(
    272        &mut self,
    273        prim_instance: PrimitiveInstance,
    274        prim_rect: LayoutRect,
    275        spatial_node_index: SpatialNodeIndex,
    276        prim_flags: PrimitiveFlags,
    277        spatial_tree: &SceneSpatialTree,
    278        interners: &Interners,
    279        quality_settings: &QualitySettings,
    280        prim_instances: &mut Vec<PrimitiveInstance>,
    281        clip_tree_builder: &ClipTreeBuilder,
    282    ) {
    283        let primary_slice = self.primary_slices.last_mut().unwrap();
    284 
    285        match primary_slice.kind {
    286            SliceKind::Atomic { ref mut prim_list } => {
    287                prim_list.add_prim(
    288                    prim_instance,
    289                    prim_rect,
    290                    spatial_node_index,
    291                    prim_flags,
    292                    prim_instances,
    293                    clip_tree_builder,
    294                );
    295            }
    296            SliceKind::Default { ref mut secondary_slices } => {
    297                assert_ne!(spatial_node_index, SpatialNodeIndex::UNKNOWN);
    298 
    299                // Check if we want to create a new slice based on the current / next scroll root
    300                let scroll_root = find_scroll_root(
    301                    spatial_node_index,
    302                    &mut self.prev_scroll_root_cache,
    303                    spatial_tree,
    304                    // Allow sticky frames as scroll roots, unless our quality settings prefer
    305                    // subpixel AA over performance.
    306                    !quality_settings.force_subpixel_aa_where_possible,
    307                );
    308 
    309                let current_scroll_root = secondary_slices
    310                    .last()
    311                    .map(|p| p.scroll_root);
    312 
    313                let mut want_new_tile_cache = secondary_slices.is_empty();
    314 
    315                if let Some(current_scroll_root) = current_scroll_root {
    316                    want_new_tile_cache |= match (current_scroll_root, scroll_root) {
    317                        (_, _) if current_scroll_root == self.root_spatial_node_index && scroll_root == self.root_spatial_node_index => {
    318                            // Both current slice and this cluster are fixed position, no need to cut
    319                            false
    320                        }
    321                        (_, _) if current_scroll_root == self.root_spatial_node_index => {
    322                            // A real scroll root is being established, so create a cache slice
    323                            true
    324                        }
    325                        (_, _) if scroll_root == self.root_spatial_node_index => {
    326                            // If quality settings force subpixel AA over performance, skip creating
    327                            // a slice for the fixed position element(s) here.
    328                            if quality_settings.force_subpixel_aa_where_possible {
    329                                false
    330                            } else {
    331                                // A fixed position slice is encountered within a scroll root. Only create
    332                                // a slice in this case if all the clips referenced by this cluster are also
    333                                // fixed position. There's no real point in creating slices for these cases,
    334                                // since we'll have to rasterize them as the scrolling clip moves anyway. It
    335                                // also allows us to retain subpixel AA in these cases. For these types of
    336                                // slices, the intra-slice dirty rect handling typically works quite well
    337                                // (a common case is parallax scrolling effects).
    338                                let mut create_slice = true;
    339 
    340                                let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
    341                                let mut current_node_id = leaf.node_id;
    342 
    343                                while current_node_id != ClipNodeId::NONE {
    344                                    let node = clip_tree_builder.get_node(current_node_id);
    345 
    346                                    let clip_node_data = &interners.clip[node.handle];
    347 
    348                                    let spatial_root = find_scroll_root(
    349                                        clip_node_data.key.spatial_node_index,
    350                                        &mut self.prev_scroll_root_cache,
    351                                        spatial_tree,
    352                                        true,
    353                                    );
    354 
    355                                    if spatial_root != self.root_spatial_node_index {
    356                                        create_slice = false;
    357                                        break;
    358                                    }
    359 
    360                                    current_node_id = node.parent;
    361                                }
    362 
    363                                create_slice
    364                            }
    365                        }
    366                        (curr_scroll_root, scroll_root) => {
    367                            // Two scrolling roots - only need a new slice if they differ
    368                            curr_scroll_root != scroll_root
    369                        }
    370                    };
    371                }
    372 
    373                if want_new_tile_cache {
    374                    secondary_slices.push(SliceDescriptor {
    375                        prim_list: PrimitiveList::empty(),
    376                        scroll_root,
    377                    });
    378                }
    379 
    380                secondary_slices
    381                    .last_mut()
    382                    .unwrap()
    383                    .prim_list
    384                    .add_prim(
    385                        prim_instance,
    386                        prim_rect,
    387                        spatial_node_index,
    388                        prim_flags,
    389                        prim_instances,
    390                        clip_tree_builder,
    391                    );
    392            }
    393        }
    394    }
    395 
    396    /// Consume this object and build the list of tile cache primitives
    397    pub fn build(
    398        mut self,
    399        config: &FrameBuilderConfig,
    400        prim_store: &mut PrimitiveStore,
    401        spatial_tree: &SceneSpatialTree,
    402        prim_instances: &[PrimitiveInstance],
    403        clip_tree_builder: &mut ClipTreeBuilder,
    404        interners: &Interners,
    405    ) -> (TileCacheConfig, Vec<PictureIndex>) {
    406        let mut result = TileCacheConfig::new(self.primary_slices.len());
    407        let mut tile_cache_pictures = Vec::new();
    408        let primary_slices = std::mem::replace(&mut self.primary_slices, Vec::new());
    409 
    410        // TODO: At the moment, culling, clipping and invalidation are always
    411        // done in the root coordinate space. The plan is to move to doing it
    412        // (always or mostly) in raster space.
    413        let visibility_node = spatial_tree.root_reference_frame_index();
    414 
    415        for mut primary_slice in primary_slices {
    416 
    417            if primary_slice.has_too_many_slices() {
    418                primary_slice.merge();
    419            }
    420 
    421            match primary_slice.kind {
    422                SliceKind::Atomic { prim_list } => {
    423                    if let Some(descriptor) = self.build_tile_cache(
    424                        prim_list,
    425                        spatial_tree,
    426                    ) {
    427                        create_tile_cache(
    428                            self.debug_flags,
    429                            primary_slice.slice_flags,
    430                            descriptor.scroll_root,
    431                            visibility_node,
    432                            primary_slice.iframe_clip,
    433                            descriptor.prim_list,
    434                            primary_slice.background_color,
    435                            prim_store,
    436                            prim_instances,
    437                            config,
    438                            &mut result.tile_caches,
    439                            &mut tile_cache_pictures,
    440                            clip_tree_builder,
    441                            interners,
    442                            spatial_tree,
    443                        );
    444                    }
    445                }
    446                SliceKind::Default { secondary_slices } => {
    447                    for descriptor in secondary_slices {
    448                        create_tile_cache(
    449                            self.debug_flags,
    450                            primary_slice.slice_flags,
    451                            descriptor.scroll_root,
    452                            visibility_node,
    453                            primary_slice.iframe_clip,
    454                            descriptor.prim_list,
    455                            primary_slice.background_color,
    456                            prim_store,
    457                            prim_instances,
    458                            config,
    459                            &mut result.tile_caches,
    460                            &mut tile_cache_pictures,
    461                            clip_tree_builder,
    462                            interners,
    463                            spatial_tree,
    464                        );
    465                    }
    466                }
    467            }
    468        }
    469 
    470        (result, tile_cache_pictures)
    471    }
    472 }
    473 
    474 /// Find the scroll root for a given spatial node
    475 fn find_scroll_root(
    476    spatial_node_index: SpatialNodeIndex,
    477    prev_scroll_root_cache: &mut (SpatialNodeIndex, SpatialNodeIndex),
    478    spatial_tree: &SceneSpatialTree,
    479    allow_sticky_frames: bool,
    480 ) -> SpatialNodeIndex {
    481    if prev_scroll_root_cache.0 == spatial_node_index {
    482        return prev_scroll_root_cache.1;
    483    }
    484 
    485    let scroll_root = spatial_tree.find_scroll_root(spatial_node_index, allow_sticky_frames);
    486    *prev_scroll_root_cache = (spatial_node_index, scroll_root);
    487 
    488    scroll_root
    489 }
    490 
    491 /// Given a PrimitiveList and scroll root, construct a tile cache primitive instance
    492 /// that wraps the primitive list.
    493 fn create_tile_cache(
    494    debug_flags: DebugFlags,
    495    slice_flags: SliceFlags,
    496    scroll_root: SpatialNodeIndex,
    497    visibility_node: SpatialNodeIndex,
    498    iframe_clip: Option<ClipId>,
    499    prim_list: PrimitiveList,
    500    background_color: Option<ColorF>,
    501    prim_store: &mut PrimitiveStore,
    502    prim_instances: &[PrimitiveInstance],
    503    frame_builder_config: &FrameBuilderConfig,
    504    tile_caches: &mut FastHashMap<SliceId, TileCacheParams>,
    505    tile_cache_pictures: &mut Vec<PictureIndex>,
    506    clip_tree_builder: &mut ClipTreeBuilder,
    507    interners: &Interners,
    508    spatial_tree: &SceneSpatialTree,
    509 ) {
    510    // Accumulate any clip instances from the iframe_clip into the shared clips
    511    // that will be applied by this tile cache during compositing.
    512    let mut additional_clips = Vec::new();
    513 
    514    if let Some(clip_id) = iframe_clip {
    515        additional_clips.push(clip_id);
    516    }
    517 
    518    // Find the best shared clip node that we can apply while compositing tiles,
    519    // rather than applying to each item individually.
    520 
    521    // Step 1: Walk the primitive list, and find the LCA of the clip-tree that
    522    //         matches all primitives. This gives us our "best-case" shared
    523    //         clip node that moves as many clips as possible to compositing.
    524    let mut shared_clip_node_id = None;
    525 
    526    for cluster in &prim_list.clusters {
    527        for prim_instance in &prim_instances[cluster.prim_range()] {
    528            let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
    529 
    530            // TODO(gw): Need to cache last clip-node id here?
    531            shared_clip_node_id = match shared_clip_node_id {
    532                Some(current) => {
    533                    Some(clip_tree_builder.find_lowest_common_ancestor(current, leaf.node_id))
    534                }
    535                None => {
    536                    Some(leaf.node_id)
    537                }
    538            }
    539        }
    540    }
    541 
    542    // Step 2: Now we need to walk up the shared clip node hierarchy, and remove clips
    543    //         that we can't handle during compositing, such as:
    544    //         (a) Non axis-aligned clips
    545    //         (b) Box-shadow or image-mask clips
    546    //         (c) Rounded rect clips.
    547    //
    548    //         A follow up patch to this series will relax the condition on (c) to
    549    //         allow tile caches to apply a single rounded-rect clip during compositing.
    550    let mut shared_clip_node_id = shared_clip_node_id.unwrap_or(ClipNodeId::NONE);
    551    let mut current_node_id = shared_clip_node_id;
    552    let mut rounded_rect_count = 0;
    553 
    554    // Walk up the hierarchy to the root of the clip-tree
    555    while current_node_id != ClipNodeId::NONE {
    556        let node = clip_tree_builder.get_node(current_node_id);
    557        let clip_node_data = &interners.clip[node.handle];
    558 
    559        // Check if this clip is in the root coord system (i.e. is axis-aligned with tile-cache)
    560        let is_rcs = spatial_tree.is_root_coord_system(clip_node_data.key.spatial_node_index);
    561 
    562        let node_valid = if is_rcs {
    563            match clip_node_data.key.kind {
    564                ClipItemKeyKind::BoxShadow(..) |
    565                ClipItemKeyKind::ImageMask(..) |
    566                ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
    567                ClipItemKeyKind::RoundedRectangle(_, _, ClipMode::ClipOut) => {
    568                    // Has a box-shadow / image-mask, we can't handle this as a shared clip
    569                    false
    570                }
    571                ClipItemKeyKind::RoundedRectangle(rect, radius, ClipMode::Clip) => {
    572                    // The shader and CoreAnimation rely on certain constraints such
    573                    // as uniform radii to be able to apply the clip during compositing.
    574                    if BorderRadius::from(radius).can_use_fast_path_in(&rect.into()) {
    575                        rounded_rect_count += 1;
    576 
    577                        true
    578                    } else {
    579                        false
    580                    }
    581                }
    582                ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => {
    583                    // We can apply multiple (via combining) axis-aligned rectangle
    584                    // clips to the shared compositing clip.
    585                    true
    586                }
    587            }
    588        } else {
    589            // Has a complex transform, we can't handle this as a shared clip
    590            false
    591        };
    592 
    593        if node_valid {
    594            // This node was found to be one we can apply during compositing.
    595            if rounded_rect_count > 1 {
    596                // However, we plan to only support one rounded-rect clip. If
    597                // we have found > 1 rounded rect, drop children from the shared
    598                // clip, and continue looking up the chain.
    599                shared_clip_node_id = current_node_id;
    600                rounded_rect_count = 1;
    601            }
    602        } else {
    603            // Node was invalid, due to transform / clip type. Drop this clip
    604            // and reset the rounded rect count to 0, since we drop children
    605            // from here too.
    606            shared_clip_node_id = node.parent;
    607            rounded_rect_count = 0;
    608        }
    609 
    610        current_node_id = node.parent;
    611    }
    612 
    613    let shared_clip_leaf_id = Some(clip_tree_builder.build_for_tile_cache(
    614        shared_clip_node_id,
    615        &additional_clips,
    616    ));
    617 
    618    // Build a clip-chain for the tile cache, that contains any of the shared clips
    619    // we will apply when drawing the tiles. In all cases provided by Gecko, these
    620    // are rectangle clips with a scale/offset transform only, and get handled as
    621    // a simple local clip rect in the vertex shader. However, this should in theory
    622    // also work with any complex clips, such as rounded rects and image masks, by
    623    // producing a clip mask that is applied to the picture cache tiles.
    624 
    625    let slice = tile_cache_pictures.len();
    626 
    627    let background_color = if slice == 0 {
    628        background_color
    629    } else {
    630        None
    631    };
    632 
    633    let slice_id = SliceId::new(slice);
    634 
    635    // Store some information about the picture cache slice. This is used when we swap the
    636    // new scene into the frame builder to either reuse existing slices, or create new ones.
    637    tile_caches.insert(slice_id, TileCacheParams {
    638        debug_flags,
    639        slice,
    640        slice_flags,
    641        spatial_node_index: scroll_root,
    642        visibility_node_index: visibility_node,
    643        background_color,
    644        shared_clip_node_id,
    645        shared_clip_leaf_id,
    646        virtual_surface_size: frame_builder_config.compositor_kind.get_virtual_surface_size(),
    647        image_surface_count: prim_list.image_surface_count,
    648        yuv_image_surface_count: prim_list.yuv_image_surface_count,
    649    });
    650 
    651    let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image(
    652        Some(PictureCompositeMode::TileCache { slice_id }),
    653        Picture3DContext::Out,
    654        PrimitiveFlags::IS_BACKFACE_VISIBLE,
    655        prim_list,
    656        scroll_root,
    657        RasterSpace::Screen,
    658        PictureFlags::empty(),
    659        None,
    660    ));
    661 
    662    tile_cache_pictures.push(PictureIndex(pic_index));
    663 }
    664 
    665 /// Debug information about a set of picture cache slices, exposed via RenderResults
    666 #[derive(Debug)]
    667 #[cfg_attr(feature = "capture", derive(Serialize))]
    668 #[cfg_attr(feature = "replay", derive(Deserialize))]
    669 pub struct PictureCacheDebugInfo {
    670    pub slices: FastHashMap<usize, SliceDebugInfo>,
    671 }
    672 
    673 impl PictureCacheDebugInfo {
    674    pub fn new() -> Self {
    675        PictureCacheDebugInfo {
    676            slices: FastHashMap::default(),
    677        }
    678    }
    679 
    680    /// Convenience method to retrieve a given slice. Deliberately panics
    681    /// if the slice isn't present.
    682    pub fn slice(&self, slice: usize) -> &SliceDebugInfo {
    683        &self.slices[&slice]
    684    }
    685 }
    686 
    687 impl Default for PictureCacheDebugInfo {
    688    fn default() -> PictureCacheDebugInfo {
    689        PictureCacheDebugInfo::new()
    690    }
    691 }
    692 
    693 /// Debug information about a set of picture cache tiles, exposed via RenderResults
    694 #[derive(Debug)]
    695 #[cfg_attr(feature = "capture", derive(Serialize))]
    696 #[cfg_attr(feature = "replay", derive(Deserialize))]
    697 pub struct SliceDebugInfo {
    698    pub tiles: FastHashMap<TileOffset, TileDebugInfo>,
    699 }
    700 
    701 impl SliceDebugInfo {
    702    pub fn new() -> Self {
    703        SliceDebugInfo {
    704            tiles: FastHashMap::default(),
    705        }
    706    }
    707 
    708    /// Convenience method to retrieve a given tile. Deliberately panics
    709    /// if the tile isn't present.
    710    pub fn tile(&self, x: i32, y: i32) -> &TileDebugInfo {
    711        &self.tiles[&TileOffset::new(x, y)]
    712    }
    713 }
    714 
    715 /// Debug information about a tile that was dirty and was rasterized
    716 #[derive(Debug, PartialEq)]
    717 #[cfg_attr(feature = "capture", derive(Serialize))]
    718 #[cfg_attr(feature = "replay", derive(Deserialize))]
    719 pub struct DirtyTileDebugInfo {
    720    pub local_valid_rect: PictureRect,
    721    pub local_dirty_rect: PictureRect,
    722 }
    723 
    724 /// Debug information about the state of a tile
    725 #[derive(Debug, PartialEq)]
    726 #[cfg_attr(feature = "capture", derive(Serialize))]
    727 #[cfg_attr(feature = "replay", derive(Deserialize))]
    728 pub enum TileDebugInfo {
    729    /// Tile was occluded by a tile in front of it
    730    Occluded,
    731    /// Tile was culled (not visible in current display port)
    732    Culled,
    733    /// Tile was valid (no rasterization was done) and visible
    734    Valid,
    735    /// Tile was dirty, and was updated
    736    Dirty(DirtyTileDebugInfo),
    737 }
    738 
    739 impl TileDebugInfo {
    740    pub fn is_occluded(&self) -> bool {
    741        match self {
    742            TileDebugInfo::Occluded => true,
    743            TileDebugInfo::Culled |
    744            TileDebugInfo::Valid |
    745            TileDebugInfo::Dirty(..) => false,
    746        }
    747    }
    748 
    749    pub fn is_valid(&self) -> bool {
    750        match self {
    751            TileDebugInfo::Valid => true,
    752            TileDebugInfo::Culled |
    753            TileDebugInfo::Occluded |
    754            TileDebugInfo::Dirty(..) => false,
    755        }
    756    }
    757 
    758    pub fn is_culled(&self) -> bool {
    759        match self {
    760            TileDebugInfo::Culled => true,
    761            TileDebugInfo::Valid |
    762            TileDebugInfo::Occluded |
    763            TileDebugInfo::Dirty(..) => false,
    764        }
    765    }
    766 
    767    pub fn as_dirty(&self) -> &DirtyTileDebugInfo {
    768        match self {
    769            TileDebugInfo::Occluded |
    770            TileDebugInfo::Culled |
    771            TileDebugInfo::Valid => {
    772                panic!("not a dirty tile!");
    773            }
    774            TileDebugInfo::Dirty(ref info) => {
    775                info
    776            }
    777        }
    778    }
    779 }