Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::ColorF;
use api::PropertyBindingId;
use api::units::*;
use smallvec::SmallVec;
use crate::ItemUid;
use crate::composite::CompositeState;
use crate::internal_types::{FastHashMap, FrameId};
use crate::invalidation::compare::ImageDependency;
use crate::invalidation::compare::{ColorBinding, OpacityBinding, OpacityBindingInfo, PrimitiveComparisonKey};
use crate::invalidation::compare::{SpatialNodeComparer, PrimitiveComparer, PrimitiveDependency, ColorBindingInfo};
use crate::invalidation::{InvalidationReason, PrimitiveCompareResult, quadtree::TileNode};
use crate::picture::{PictureCompositeMode, SurfaceIndex, clampf};
use crate::print_tree::PrintTreePrinter;
use crate::resource_cache::ResourceCache;
use crate::space::SpaceMapper;
use crate::spatial_tree::SpatialNodeIndex;
use crate::visibility::FrameVisibilityContext;
use peek_poke::poke_into_vec;
use std::mem;
pub struct CachedSurface {
pub current_descriptor: CachedSurfaceDescriptor,
pub prev_descriptor: CachedSurfaceDescriptor,
pub is_valid: bool,
pub local_valid_rect: PictureBox2D,
pub local_dirty_rect: PictureRect,
pub local_rect: PictureRect,
pub root: TileNode,
pub background_color: Option<ColorF>,
pub invalidation_reason: Option<InvalidationReason>,
pub sub_graphs: Vec<(PictureRect, Vec<(PictureCompositeMode, SurfaceIndex)>)>,
}
impl CachedSurface {
pub fn new() -> Self {
CachedSurface {
current_descriptor: CachedSurfaceDescriptor::new(),
prev_descriptor: CachedSurfaceDescriptor::new(),
is_valid: false,
local_valid_rect: PictureBox2D::zero(),
local_dirty_rect: PictureRect::zero(),
local_rect: PictureRect::zero(),
root: TileNode::new_leaf(Vec::new()),
background_color: None,
invalidation_reason: None,
sub_graphs: Vec::new(),
}
}
pub fn print(&self, pt: &mut dyn PrintTreePrinter) {
pt.add_item(format!("background_color: {:?}", self.background_color));
pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
self.current_descriptor.print(pt);
}
/// Setup state before primitive dependency calculation.
pub fn pre_update(
&mut self,
background_color: Option<ColorF>,
local_tile_rect: PictureRect,
frame_id: FrameId,
is_visible: bool,
) {
// TODO(gw): This is a hack / fix for Box2D::union in euclid not working with
// zero sized rect accumulation. Once that lands, we'll revert this
// to be zero.
self.local_valid_rect = PictureBox2D::new(
PicturePoint::new( 1.0e32, 1.0e32),
PicturePoint::new(-1.0e32, -1.0e32),
);
self.invalidation_reason = None;
self.sub_graphs.clear();
// If the tile isn't visible, early exit, skipping the normal set up to
// validate dependencies. Instead, we will only compare the current tile
// dependencies the next time it comes into view.
if !is_visible {
return;
}
if background_color != self.background_color {
self.invalidate(None, InvalidationReason::BackgroundColor);
self.background_color = background_color;
}
// Clear any dependencies so that when we rebuild them we
// can compare if the tile has the same content.
mem::swap(
&mut self.current_descriptor,
&mut self.prev_descriptor,
);
self.current_descriptor.clear();
self.root.clear(local_tile_rect);
self.current_descriptor.last_updated_frame_id = frame_id;
}
pub fn add_prim_dependency(
&mut self,
info: &PrimitiveDependencyInfo,
local_tile_rect: PictureRect,
) {
// Incorporate the bounding rect of the primitive in the local valid rect
// for this tile. This is used to minimize the size of the scissor rect
// during rasterization and the draw rect during composition of partial tiles.
self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box);
// TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port,
// which can cause invalidations when a new display list with changed
// display port is received. To work around this, clamp the prim clip rect
// to the tile boundaries - if the clip hasn't affected the tile, then the
// changed clip can't affect the content of the primitive on this tile.
// In future, we could consider supplying the display port clip from Gecko
// in a different way (e.g. as a scroll frame clip) which still provides
// the desired clip for checkerboarding, but doesn't require this extra
// work below.
// TODO(gw): This is a hot part of the code - we could probably optimize further by:
// - Using min/max instead of clamps below (if we guarantee the rects are well formed)
let pmin = local_tile_rect.min;
let pmax = local_tile_rect.max;
let prim_clip_box = PictureBox2D::new(
PicturePoint::new(
clampf(info.prim_clip_box.min.x, pmin.x, pmax.x),
clampf(info.prim_clip_box.min.y, pmin.y, pmax.y),
),
PicturePoint::new(
clampf(info.prim_clip_box.max.x, pmin.x, pmax.x),
clampf(info.prim_clip_box.max.y, pmin.y, pmax.y),
),
);
// Update the tile descriptor, used for tile comparison during scene swaps.
let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
// Encode the deps for this primitive in the `dep_data` byte buffer.
let dep_offset = self.current_descriptor.dep_data.len() as u32;
let mut dep_count = 0;
for clip in &info.clips {
dep_count += 1;
poke_into_vec(
&PrimitiveDependency::Clip {
clip: *clip,
},
&mut self.current_descriptor.dep_data,
);
}
for spatial_node_index in &info.spatial_nodes {
dep_count += 1;
poke_into_vec(
&PrimitiveDependency::SpatialNode {
index: *spatial_node_index,
},
&mut self.current_descriptor.dep_data,
);
}
for image in &info.images {
dep_count += 1;
poke_into_vec(
&PrimitiveDependency::Image {
image: *image,
},
&mut self.current_descriptor.dep_data,
);
}
for binding in &info.opacity_bindings {
dep_count += 1;
poke_into_vec(
&PrimitiveDependency::OpacityBinding {
binding: *binding,
},
&mut self.current_descriptor.dep_data,
);
}
if let Some(ref binding) = info.color_binding {
dep_count += 1;
poke_into_vec(
&PrimitiveDependency::ColorBinding {
binding: *binding,
},
&mut self.current_descriptor.dep_data,
);
}
self.current_descriptor.prims.push(PrimitiveDescriptor {
prim_uid: info.prim_uid,
prim_clip_box,
dep_offset,
dep_count,
});
// Add this primitive to the dirty rect quadtree.
self.root.add_prim(prim_index, &info.prim_clip_box);
}
/// Check if the content of the previous and current tile descriptors match
fn update_dirty_rects(
&mut self,
ctx: &TileUpdateDirtyContext,
state: &mut TileUpdateDirtyState,
invalidation_reason: &mut Option<InvalidationReason>,
frame_context: &FrameVisibilityContext,
) -> PictureRect {
let mut prim_comparer = PrimitiveComparer::new(
&self.prev_descriptor,
&self.current_descriptor,
state.resource_cache,
state.spatial_node_comparer,
ctx.opacity_bindings,
ctx.color_bindings,
);
let mut dirty_rect = PictureBox2D::zero();
self.root.update_dirty_rects(
&self.prev_descriptor.prims,
&self.current_descriptor.prims,
&mut prim_comparer,
&mut dirty_rect,
state.compare_cache,
invalidation_reason,
frame_context,
);
dirty_rect
}
/// Invalidate a tile based on change in content. This
/// must be called even if the tile is not currently
/// visible on screen. We might be able to improve this
/// later by changing how ComparableVec is used.
pub fn update_content_validity(
&mut self,
ctx: &TileUpdateDirtyContext,
state: &mut TileUpdateDirtyState,
frame_context: &FrameVisibilityContext,
) {
// Check if the contents of the primitives, clips, and
// other dependencies are the same.
state.compare_cache.clear();
let mut invalidation_reason = None;
let dirty_rect = self.update_dirty_rects(
ctx,
state,
&mut invalidation_reason,
frame_context,
);
if !dirty_rect.is_empty() {
self.invalidate(
Some(dirty_rect),
invalidation_reason.expect("bug: no invalidation_reason")
);
}
if ctx.invalidate_all {
self.invalidate(None, InvalidationReason::ScaleChanged);
}
// TODO(gw): We can avoid invalidating the whole tile in some cases here,
// but it should be a fairly rare invalidation case.
if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
self.invalidate(None, InvalidationReason::ValidRectChanged);
state.composite_state.dirty_rects_are_valid = false;
}
}
/// Invalidate this tile. If `invalidation_rect` is None, the entire
/// tile is invalidated.
pub fn invalidate(
&mut self,
invalidation_rect: Option<PictureRect>,
reason: InvalidationReason,
) {
self.is_valid = false;
match invalidation_rect {
Some(rect) => {
self.local_dirty_rect = self.local_dirty_rect.union(&rect);
}
None => {
self.local_dirty_rect = self.local_rect;
}
}
if self.invalidation_reason.is_none() {
self.invalidation_reason = Some(reason);
}
}
}
// Immutable context passed to picture cache tiles during update_dirty_and_valid_rects
pub struct TileUpdateDirtyContext<'a> {
/// Maps from picture cache coords -> world space coords.
pub pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
/// Global scale factor from world -> device pixels.
pub global_device_pixel_scale: DevicePixelScale,
/// Information about opacity bindings from the picture cache.
pub opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
/// Information about color bindings from the picture cache.
pub color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
/// The local rect of the overall picture cache
pub local_rect: PictureRect,
/// If true, the scale factor of the root transform for this picture
/// cache changed, so we need to invalidate the tile and re-render.
pub invalidate_all: bool,
}
// Mutable state passed to picture cache tiles during update_dirty_and_valid_rects
pub struct TileUpdateDirtyState<'a> {
/// Allow access to the texture cache for requesting tiles
pub resource_cache: &'a mut ResourceCache,
/// Current configuration and setup for compositing all the picture cache tiles in renderer.
pub composite_state: &'a mut CompositeState,
/// A cache of comparison results to avoid re-computation during invalidation.
pub compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
/// Information about transform node differences from last frame.
pub spatial_node_comparer: &'a mut SpatialNodeComparer,
}
/// Information about the dependencies of a single primitive instance.
pub struct PrimitiveDependencyInfo {
/// Unique content identifier of the primitive.
pub prim_uid: ItemUid,
/// The (conservative) clipped area in picture space this primitive occupies.
pub prim_clip_box: PictureBox2D,
/// Image keys this primitive depends on.
pub images: SmallVec<[ImageDependency; 8]>,
/// Opacity bindings this primitive depends on.
pub opacity_bindings: SmallVec<[OpacityBinding; 4]>,
/// Color binding this primitive depends on.
pub color_binding: Option<ColorBinding>,
/// Clips that this primitive depends on.
pub clips: SmallVec<[ItemUid; 8]>,
/// Spatial nodes references by the clip dependencies of this primitive.
pub spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
}
impl PrimitiveDependencyInfo {
pub fn new(
prim_uid: crate::intern::ItemUid,
prim_clip_box: PictureBox2D,
) -> Self {
PrimitiveDependencyInfo {
prim_uid,
prim_clip_box,
images: smallvec::SmallVec::new(),
opacity_bindings: smallvec::SmallVec::new(),
color_binding: None,
clips: smallvec::SmallVec::new(),
spatial_nodes: smallvec::SmallVec::new(),
}
}
}
/// Information about a primitive that is a dependency for a cached surface.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PrimitiveDescriptor {
pub prim_uid: ItemUid,
pub prim_clip_box: PictureBox2D,
// TODO(gw): These two fields could be packed as a u24/u8
pub dep_offset: u32,
pub dep_count: u32,
}
impl PartialEq for PrimitiveDescriptor {
fn eq(&self, other: &Self) -> bool {
const EPSILON: f32 = 0.001;
if self.prim_uid != other.prim_uid {
return false;
}
use euclid::approxeq::ApproxEq;
if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) {
return false;
}
if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) {
return false;
}
if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) {
return false;
}
if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) {
return false;
}
if self.dep_count != other.dep_count {
return false;
}
true
}
}
impl PartialEq<PrimitiveDescriptor> for (&ItemUid, &PictureBox2D) {
fn eq(&self, other: &PrimitiveDescriptor) -> bool {
self.0 == &other.prim_uid && self.1 == &other.prim_clip_box
}
}
/// An index into the prims array in a TileDescriptor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PrimitiveDependencyIndex(pub u32);
/// Uniquely describes the content of this cached surface, in a way that can be
/// (reasonably) efficiently hashed and compared.
#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct CachedSurfaceDescriptor {
/// List of primitive instance unique identifiers. The uid is guaranteed
/// to uniquely describe the content of the primitive template, while
/// the other parameters describe the clip chain and instance params.
pub prims: Vec<PrimitiveDescriptor>,
/// Picture space rect that contains valid pixels region of this tile.
pub local_valid_rect: PictureRect,
/// The last frame this tile had its dependencies updated (dependency updating is
/// skipped if a tile is off-screen).
pub last_updated_frame_id: FrameId,
/// Packed per-prim dependency information
pub dep_data: Vec<u8>,
}
impl CachedSurfaceDescriptor {
pub fn new() -> Self {
CachedSurfaceDescriptor {
local_valid_rect: PictureRect::zero(),
dep_data: Vec::new(),
prims: Vec::new(),
last_updated_frame_id: FrameId::INVALID,
}
}
/// Print debug information about this tile descriptor to a tree printer.
pub fn print(&self, pt: &mut dyn crate::print_tree::PrintTreePrinter) {
pt.new_level("current_descriptor".to_string());
pt.new_level("prims".to_string());
for prim in &self.prims {
pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
pt.add_item(format!("clip: p0={},{} p1={},{}",
prim.prim_clip_box.min.x,
prim.prim_clip_box.min.y,
prim.prim_clip_box.max.x,
prim.prim_clip_box.max.y,
));
pt.end_level();
}
pt.end_level();
pt.end_level();
}
/// Clear the dependency information for a tile, when the dependencies
/// are being rebuilt.
pub fn clear(&mut self) {
self.local_valid_rect = PictureRect::zero();
self.prims.clear();
self.dep_data.clear();
}
}