Skip to main content

webrender/invalidation/
cached_surface.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
5use api::ColorF;
6use api::PropertyBindingId;
7use api::units::*;
8use smallvec::SmallVec;
9use crate::composite::CompositeState;
10use crate::internal_types::{FastHashMap, FrameId};
11use crate::invalidation::compare::ImageDependency;
12use crate::invalidation::compare::{ColorBinding, OpacityBinding, OpacityBindingInfo, PrimitiveComparisonKey};
13use crate::invalidation::compare::{PrimitiveComparer, PrimitiveDependency, ColorBindingInfo};
14use crate::invalidation::{InvalidationReason, PrimitiveCompareResult, quadtree::TileNode};
15use crate::invalidation::vert_buffer::{CornersCache, VertRange};
16use crate::intern::ItemUid;
17use crate::picture::{PictureCompositeMode, SurfaceIndex, clampf};
18use crate::print_tree::PrintTreePrinter;
19use crate::resource_cache::ResourceCache;
20use crate::space::SpaceMapper;
21use crate::visibility::FrameVisibilityContext;
22use peek_poke::poke_into_vec;
23use std::mem;
24
25pub struct CachedSurface {
26    pub current_descriptor: CachedSurfaceDescriptor,
27    pub prev_descriptor: CachedSurfaceDescriptor,
28    pub is_valid: bool,
29    pub local_valid_rect: PictureBox2D,
30    pub local_dirty_rect: PictureRect,
31    pub local_rect: PictureRect,
32    pub root: TileNode,
33    pub background_color: Option<ColorF>,
34    pub invalidation_reason: Option<InvalidationReason>,
35    pub sub_graphs: Vec<(PictureRect, Vec<(PictureCompositeMode, SurfaceIndex)>)>,
36}
37
38impl CachedSurface {
39    pub fn new() -> Self {
40        CachedSurface {
41            current_descriptor: CachedSurfaceDescriptor::new(),
42            prev_descriptor: CachedSurfaceDescriptor::new(),
43            is_valid: false,
44            local_valid_rect: PictureBox2D::zero(),
45            local_dirty_rect: PictureRect::zero(),
46            local_rect: PictureRect::zero(),
47            root: TileNode::new_leaf(Vec::new()),
48            background_color: None,
49            invalidation_reason: None,
50            sub_graphs: Vec::new(),
51        }
52    }
53
54    pub fn print(&self, pt: &mut dyn PrintTreePrinter) {
55        pt.add_item(format!("background_color: {:?}", self.background_color));
56        pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
57        self.current_descriptor.print(pt);
58    }
59
60    /// Setup state before primitive dependency calculation.
61    pub fn pre_update(
62        &mut self,
63        background_color: Option<ColorF>,
64        local_tile_rect: PictureRect,
65        frame_id: FrameId,
66        is_visible: bool,
67    ) {
68        // TODO(gw): This is a hack / fix for Box2D::union in euclid not working with
69        //           zero sized rect accumulation. Once that lands, we'll revert this
70        //           to be zero.
71        self.local_valid_rect = PictureBox2D::new(
72            PicturePoint::new( 1.0e32,  1.0e32),
73            PicturePoint::new(-1.0e32, -1.0e32),
74        );
75        self.invalidation_reason  = None;
76        self.sub_graphs.clear();
77
78        // If the tile isn't visible, early exit, skipping the normal set up to
79        // validate dependencies. Instead, we will only compare the current tile
80        // dependencies the next time it comes into view.
81        if !is_visible {
82            return;
83        }
84
85        if background_color != self.background_color {
86            self.invalidate(None, InvalidationReason::BackgroundColor);
87            self.background_color = background_color;
88        }
89
90        // Clear any dependencies so that when we rebuild them we
91        // can compare if the tile has the same content.
92        mem::swap(
93            &mut self.current_descriptor,
94            &mut self.prev_descriptor,
95        );
96        self.current_descriptor.clear();
97        self.root.clear(local_tile_rect);
98
99        self.current_descriptor.last_updated_frame_id = frame_id;
100    }
101
102    pub fn add_prim_dependency(
103        &mut self,
104        info: &PrimitiveDependencyInfo,
105        corners_cache: &CornersCache,
106        prim_clamp_to_tile: bool,
107        local_raster_rect: &RasterRect,
108        local_tile_rect: PictureRect,
109    ) {
110        // Incorporate the bounding rect of the primitive in the local valid rect
111        // for this tile. This is used to minimize the size of the scissor rect
112        // during rasterization and the draw rect during composition of partial tiles.
113        self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box);
114
115        // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port,
116        //           which can cause invalidations when a new display list with changed
117        //           display port is received. To work around this, clamp the prim clip rect
118        //           to the tile boundaries - if the clip hasn't affected the tile, then the
119        //           changed clip can't affect the content of the primitive on this tile.
120        //           In future, we could consider supplying the display port clip from Gecko
121        //           in a different way (e.g. as a scroll frame clip) which still provides
122        //           the desired clip for checkerboarding, but doesn't require this extra
123        //           work below.
124
125        // TODO(gw): This is a hot part of the code - we could probably optimize further by:
126        //           - Using min/max instead of clamps below (if we guarantee the rects are well formed)
127
128        let pmin = local_tile_rect.min;
129        let pmax = local_tile_rect.max;
130
131        let prim_clip_box = PictureBox2D::new(
132            PicturePoint::new(
133                clampf(info.prim_clip_box.min.x, pmin.x, pmax.x),
134                clampf(info.prim_clip_box.min.y, pmin.y, pmax.y),
135            ),
136            PicturePoint::new(
137                clampf(info.prim_clip_box.max.x, pmin.x, pmax.x),
138                clampf(info.prim_clip_box.max.y, pmin.y, pmax.y),
139            ),
140        );
141
142        // Push raster-space corners into this tile's vert_data, clamping if requested.
143        let vert_data = &mut self.current_descriptor.vert_data;
144        let (prim_corners, coverage_corners) = if prim_clamp_to_tile {
145            (
146                corners_cache.push_verts_clamped(info.prim_scratch, local_raster_rect, vert_data),
147                corners_cache.push_verts_clamped(info.cov_scratch, local_raster_rect, vert_data),
148            )
149        } else {
150            (
151                corners_cache.push_verts(info.prim_scratch, vert_data),
152                corners_cache.push_verts(info.cov_scratch, vert_data),
153            )
154        };
155
156        // Update the tile descriptor, used for tile comparison during scene swaps.
157        let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
158
159        // Encode the deps for this primitive in the `dep_data` byte buffer.
160        let dep_offset = self.current_descriptor.dep_data.len() as u32;
161        let mut dep_count = 0;
162
163        for &(clip_uid, clip_scratch) in info.clips.iter() {
164            dep_count += 1;
165            poke_into_vec(
166                &PrimitiveDependency::Clip { prim_uid: clip_uid, vert_range: corners_cache.push_verts(clip_scratch, vert_data) },
167                &mut self.current_descriptor.dep_data,
168            );
169        }
170
171        for image in &info.images {
172            dep_count += 1;
173            poke_into_vec(
174                &PrimitiveDependency::Image {
175                    image: *image,
176                },
177                &mut self.current_descriptor.dep_data,
178            );
179        }
180
181        for binding in &info.opacity_bindings {
182            dep_count += 1;
183            poke_into_vec(
184                &PrimitiveDependency::OpacityBinding {
185                    binding: *binding,
186                },
187                &mut self.current_descriptor.dep_data,
188            );
189        }
190
191        if let Some(ref binding) = info.color_binding {
192            dep_count += 1;
193            poke_into_vec(
194                &PrimitiveDependency::ColorBinding {
195                    binding: *binding,
196                },
197                &mut self.current_descriptor.dep_data,
198            );
199        }
200
201        self.current_descriptor.prims.push(PrimitiveDescriptor {
202            prim_clip_box,
203            dep_offset,
204            dep_count,
205            prim_uid: info.prim_uid,
206            prim_corners,
207            coverage_corners,
208        });
209
210        // Add this primitive to the dirty rect quadtree.
211        self.root.add_prim(prim_index, &info.prim_clip_box);
212    }
213
214    /// Check if the content of the previous and current tile descriptors match
215    fn update_dirty_rects(
216        &mut self,
217        ctx: &TileUpdateDirtyContext,
218        state: &mut TileUpdateDirtyState,
219        invalidation_reason: &mut Option<InvalidationReason>,
220        frame_context: &FrameVisibilityContext,
221    ) -> PictureRect {
222        let mut prim_comparer = PrimitiveComparer::new(
223            &self.prev_descriptor,
224            &self.current_descriptor,
225            state.resource_cache,
226            ctx.opacity_bindings,
227            ctx.color_bindings,
228        );
229
230        let mut dirty_rect = PictureBox2D::zero();
231        self.root.update_dirty_rects(
232            &self.prev_descriptor.prims,
233            &self.current_descriptor.prims,
234            &mut prim_comparer,
235            &mut dirty_rect,
236            state.compare_cache,
237            invalidation_reason,
238            frame_context,
239        );
240
241        dirty_rect
242    }
243
244    /// Invalidate a tile based on change in content. This
245    /// must be called even if the tile is not currently
246    /// visible on screen. We might be able to improve this
247    /// later by changing how ComparableVec is used.
248    pub fn update_content_validity(
249        &mut self,
250        ctx: &TileUpdateDirtyContext,
251        state: &mut TileUpdateDirtyState,
252        frame_context: &FrameVisibilityContext,
253    ) {
254        // Check if the contents of the primitives, clips, and
255        // other dependencies are the same.
256        state.compare_cache.clear();
257        let mut invalidation_reason = None;
258        let dirty_rect = self.update_dirty_rects(
259            ctx,
260            state,
261            &mut invalidation_reason,
262            frame_context,
263        );
264
265        if !dirty_rect.is_empty() {
266            self.invalidate(
267                Some(dirty_rect),
268                invalidation_reason.expect("bug: no invalidation_reason")
269            );
270        }
271        if ctx.invalidate_all {
272            self.invalidate(None, InvalidationReason::ScaleChanged);
273        }
274        // TODO(gw): We can avoid invalidating the whole tile in some cases here,
275        //           but it should be a fairly rare invalidation case.
276        if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
277            self.invalidate(None, InvalidationReason::ValidRectChanged);
278            state.composite_state.dirty_rects_are_valid = false;
279        }
280    }
281
282    /// Invalidate this tile. If `invalidation_rect` is None, the entire
283    /// tile is invalidated.
284    pub fn invalidate(
285        &mut self,
286        invalidation_rect: Option<PictureRect>,
287        reason: InvalidationReason,
288    ) {
289        self.is_valid = false;
290
291        match invalidation_rect {
292            Some(rect) => {
293                self.local_dirty_rect = self.local_dirty_rect.union(&rect);
294            }
295            None => {
296                self.local_dirty_rect = self.local_rect;
297            }
298        }
299
300        if self.invalidation_reason.is_none() {
301            self.invalidation_reason = Some(reason);
302        }
303    }
304}
305
306// Immutable context passed to picture cache tiles during update_dirty_and_valid_rects
307pub struct TileUpdateDirtyContext<'a> {
308    /// Maps from picture cache coords -> world space coords.
309    pub pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
310
311    /// Global scale factor from world -> device pixels.
312    pub global_device_pixel_scale: DevicePixelScale,
313
314    /// Information about opacity bindings from the picture cache.
315    pub opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
316
317    /// Information about color bindings from the picture cache.
318    pub color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
319
320    /// The local rect of the overall picture cache
321    pub local_rect: PictureRect,
322
323    /// If true, the scale factor of the root transform for this picture
324    /// cache changed, so we need to invalidate the tile and re-render.
325    pub invalidate_all: bool,
326}
327
328// Mutable state passed to picture cache tiles during update_dirty_and_valid_rects
329pub struct TileUpdateDirtyState<'a> {
330    /// Allow access to the texture cache for requesting tiles
331    pub resource_cache: &'a mut ResourceCache,
332
333    /// Current configuration and setup for compositing all the picture cache tiles in renderer.
334    pub composite_state: &'a mut CompositeState,
335
336    /// A cache of comparison results to avoid re-computation during invalidation.
337    pub compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
338}
339
340/// Information about the dependencies of a single primitive instance.
341/// Built once per primitive (outside the tile loop); passed to each tile's
342/// add_prim_dependency, which does the per-tile clamping and vert-data push.
343pub struct PrimitiveDependencyInfo {
344    /// The (conservative) clipped area in picture space this primitive occupies.
345    /// Used for local_valid_rect accumulation and quadtree binning.
346    pub prim_clip_box: PictureBox2D,
347    /// Image keys this primitive depends on.
348    pub images: SmallVec<[ImageDependency; 8]>,
349    /// Opacity bindings this primitive depends on.
350    pub opacity_bindings: SmallVec<[OpacityBinding; 4]>,
351    /// Color binding this primitive depends on.
352    pub color_binding: Option<ColorBinding>,
353    /// Intern uid for this primitive instance. Stable across frames and across
354    /// content-side scroll events: scene building normalises each primitive's
355    /// prim_rect by the accumulated external_scroll_offset before interning,
356    /// so the key (and therefore this uid) does not change when the scroll
357    /// position changes. If external_scroll_offset is ever removed, this
358    /// stability guarantee would need to be preserved by another mechanism.
359    pub prim_uid: ItemUid,
360    /// Scratch range for the primitive's rect corners in raster space (unquantized).
361    /// Quantized into per-tile vert_data inside add_prim_dependency.
362    pub prim_scratch: VertRange,
363    /// Scratch range for the coverage rect corners (prim ∩ clip) in raster space.
364    /// Tracked separately from prim_scratch because merging them into a single
365    /// intersection loses prim-rect information: a UV-mapped primitive whose
366    /// rect changes size while the clip keeps the visible region constant would
367    /// produce an unchanged intersection yet sample different source pixels.
368    /// Using coverage_rect (rather than the raw local_clip_rect) avoids
369    /// spurious invalidations when the clip changes outside the prim extent.
370    pub cov_scratch: VertRange,
371    /// Per-clip data: (clip intern uid, scratch range for clip corners).
372    /// The uid covers the clip's shape/mode; position is captured in the scratch range.
373    pub clips: SmallVec<[(ItemUid, VertRange); 4]>,
374}
375
376impl PrimitiveDependencyInfo {
377    pub fn new(prim_uid: ItemUid, prim_clip_box: PictureBox2D) -> Self {
378        PrimitiveDependencyInfo {
379            prim_clip_box,
380            images: smallvec::SmallVec::new(),
381            opacity_bindings: smallvec::SmallVec::new(),
382            color_binding: None,
383            prim_uid,
384            prim_scratch: VertRange::INVALID,
385            cov_scratch: VertRange::INVALID,
386            clips: smallvec::SmallVec::new(),
387        }
388    }
389}
390
391/// Information about a primitive that is a dependency for a cached surface.
392#[derive(Debug, Clone)]
393#[cfg_attr(feature = "capture", derive(Serialize))]
394#[cfg_attr(feature = "replay", derive(Deserialize))]
395pub struct PrimitiveDescriptor {
396    /// Picture-space bounds, clamped to tile boundary. Used for quadtree
397    /// binning and local_valid_rect; not used for comparison.
398    pub prim_clip_box: PictureBox2D,
399    // TODO(gw): These two fields could be packed as a u24/u8
400    pub dep_offset: u32,
401    pub dep_count: u32,
402    /// Intern uid for this primitive. See PrimitiveDependencyInfo::prim_uid for the
403    /// scroll-stability guarantee.
404    pub prim_uid: ItemUid,
405    /// Range into vert_data for this primitive's corners (local_prim_rect).
406    pub prim_corners: VertRange,
407    /// Range into vert_data for the coverage rect corners (prim ∩ clip).
408    pub coverage_corners: VertRange,
409}
410
411
412/// An index into the prims array in a TileDescriptor.
413#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
414#[cfg_attr(feature = "capture", derive(Serialize))]
415#[cfg_attr(feature = "replay", derive(Deserialize))]
416pub struct PrimitiveDependencyIndex(pub u32);
417
418/// Uniquely describes the content of this cached surface, in a way that can be
419/// (reasonably) efficiently hashed and compared.
420#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
421#[cfg_attr(feature = "capture", derive(Serialize))]
422#[cfg_attr(feature = "replay", derive(Deserialize))]
423pub struct CachedSurfaceDescriptor {
424    /// List of primitive instance unique identifiers. The uid is guaranteed
425    /// to uniquely describe the content of the primitive template, while
426    /// the other parameters describe the clip chain and instance params.
427    pub prims: Vec<PrimitiveDescriptor>,
428
429    /// Picture space rect that contains valid pixels region of this tile.
430    pub local_valid_rect: PictureRect,
431
432    /// The last frame this tile had its dependencies updated (dependency updating is
433    /// skipped if a tile is off-screen).
434    pub last_updated_frame_id: FrameId,
435
436    /// Packed per-prim dependency information
437    pub dep_data: Vec<u8>,
438
439    /// Per-tile quantized raster-space vert data. VertRanges stored in
440    /// PrimitiveDescriptor and PrimitiveDependency::Clip index into this buffer.
441    pub vert_data: Vec<i32>,
442}
443
444impl CachedSurfaceDescriptor {
445    pub fn new() -> Self {
446        CachedSurfaceDescriptor {
447            local_valid_rect: PictureRect::zero(),
448            dep_data: Vec::new(),
449            vert_data: Vec::new(),
450            prims: Vec::new(),
451            last_updated_frame_id: FrameId::INVALID,
452        }
453    }
454
455    /// Print debug information about this tile descriptor to a tree printer.
456    pub fn print(&self, pt: &mut dyn crate::print_tree::PrintTreePrinter) {
457        pt.new_level("current_descriptor".to_string());
458
459        pt.new_level("prims".to_string());
460        for prim in &self.prims {
461            pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
462            pt.add_item(format!("clip: p0={},{} p1={},{}",
463                prim.prim_clip_box.min.x,
464                prim.prim_clip_box.min.y,
465                prim.prim_clip_box.max.x,
466                prim.prim_clip_box.max.y,
467            ));
468            pt.end_level();
469        }
470        pt.end_level();
471
472        pt.end_level();
473    }
474
475    /// Clear the dependency information for a tile, when the dependencies
476    /// are being rebuilt.
477    pub fn clear(&mut self) {
478        self.local_valid_rect = PictureRect::zero();
479        self.prims.clear();
480        self.dep_data.clear();
481        self.vert_data.clear();
482    }
483}