Bug 1836063 - Exclude common clip in subpictures r=gfx-reviewers,gw

When processing a picture-cache-tile we find the lowest common ancestor
clip of each primitive in the cache-tile and set that as the clip root.
Not only does this save separately applying the clip to each primitive,
it also means we actually draw the parts of the cache tile which are out
of view.  This means we don't have to redraw the cache tile every time
more of it scrolls in to view.

This mechanism doesn't work when we have Picture primitives inside a
picture-cache-tile, e.g. for applying a filter.  Primitives in the
sub-Picture still had the viewport clip applied and so when the
sub-Picture intersected with the viewport edge we had to redraw the
cache tile on every scroll event.

This diff copies the common-ancestor-clip logic to sub-Picture
primitives.  On pages with lots of opacity filtered areas (e.g.
w3schools.com) this eliminates unnecessary cache-tile invalidation and
massively improves scrolling performance on systems with a weak GPU.

Differential Revision: https://phabricator.services.mozilla.com/D181664
This commit is contained in:
David Turner 2023-07-13 19:42:20 +00:00
parent a10eea5360
commit 731133fa2d
3 changed files with 112 additions and 18 deletions

View File

@ -3211,7 +3211,7 @@ impl TileCacheInstance {
opaque_rect: pic_coverage_rect,
spanning_opaque_color: None,
kind: Some(BackdropKind::Clear),
backdrop_rect: pic_coverage_rect,
backdrop_rect: pic_coverage_rect,
});
}
PrimitiveInstanceKind::LinearGradient { data_handle, .. }
@ -4423,6 +4423,11 @@ pub struct PicturePrimitive {
/// Flags for this picture primitive
pub flags: PictureFlags,
/// The lowest common ancestor clip of all of the primitives in this
/// picture, to be ignored when clipping those primitives and applied
/// later when compositing the picture.
pub clip_root: Option<ClipNodeId>,
}
impl PicturePrimitive {
@ -4531,6 +4536,7 @@ impl PicturePrimitive {
is_opaque: false,
raster_space,
flags,
clip_root: None,
}
}

View File

@ -47,7 +47,7 @@ use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange,
use api::{ReferenceTransformBinding, Rotation, FillRule, SpatialTreeItem, ReferenceFrameDescriptor};
use api::units::*;
use crate::image_tiling::simplify_repeated_primitive;
use crate::clip::{ClipItemKey, ClipStore, ClipItemKeyKind};
use crate::clip::{ClipItemKey, ClipStore, ClipItemKeyKind, ClipIntern};
use crate::clip::{ClipInternData, ClipNodeId, ClipLeafId};
use crate::clip::{PolygonDataHandle, ClipTreeBuilder};
use crate::segment::EdgeAaSegmentMask;
@ -603,8 +603,12 @@ impl<'a> SceneBuilder<'a> {
builder.picture_graph.add_root(*pic_index);
SceneBuilder::finalize_picture(
*pic_index,
None,
&mut builder.prim_store.pictures,
None,
&builder.clip_tree_builder,
&builder.prim_instances,
&builder.interners.clip,
);
}
@ -628,15 +632,21 @@ impl<'a> SceneBuilder<'a> {
}
}
/// Traverse the picture prim list and update any late-set spatial nodes
/// Traverse the picture prim list and update any late-set spatial nodes.
/// Also, for each picture primitive, store the lowest-common-ancestor
/// of all of the contained primitives' clips.
// TODO(gw): This is somewhat hacky - it's unfortunate we need to do this, but it's
// because we can't determine the scroll root until we have checked all the
// primitives in the slice. Perhaps we could simplify this by doing some
// work earlier in the DL builder, so we know what scroll root will be picked?
fn finalize_picture(
pic_index: PictureIndex,
prim_index: Option<usize>,
pictures: &mut [PicturePrimitive],
parent_spatial_node_index: Option<SpatialNodeIndex>,
clip_tree_builder: &ClipTreeBuilder,
prim_instances: &[PrimitiveInstance],
clip_interner: &Interner<ClipIntern>,
) {
// Extract the prim_list (borrow check) and select the spatial node to
// assign to unknown clusters
@ -667,23 +677,92 @@ impl<'a> SceneBuilder<'a> {
}
}
// Update the spatial node of any child pictures
for child_pic_index in &prim_list.child_pictures {
let child_pic = &mut pictures[child_pic_index.0];
// Work out the lowest common clip which is shared by all the
// primitives in this picture. If it is the same as the picture clip
// then store it as the clip tree root for the picture so that it is
// applied later as part of picture compositing. Gecko gives every
// primitive a viewport clip which, if applied within the picture,
// will mess up tile caching and mean we have to redraw on every
// scroll event (for tile caching to work usefully we specifically
// want to draw things even if they are outside the viewport).
let mut shared_clip_node_id = None;
for cluster in &prim_list.clusters {
for prim_instance in &prim_instances[cluster.prim_range()] {
let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
if child_pic.spatial_node_index == SpatialNodeIndex::UNKNOWN {
child_pic.spatial_node_index = spatial_node_index;
shared_clip_node_id = match shared_clip_node_id {
Some(current) => {
Some(clip_tree_builder.find_lowest_common_ancestor(
current,
leaf.node_id,
))
}
None => Some(leaf.node_id)
};
}
}
// Recurse into child pictures which may also have unknown spatial nodes
SceneBuilder::finalize_picture(
*child_pic_index,
pictures,
Some(spatial_node_index),
);
let lca_node = shared_clip_node_id
.and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id))
.map(|node_id| clip_tree_builder.get_node(node_id))
.map(|tree_node| &clip_interner[tree_node.handle]);
let pic_node = prim_index
.map(|prim_index| clip_tree_builder.get_leaf(prim_instances[prim_index].clip_leaf_id).node_id)
.and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id))
.map(|node_id| clip_tree_builder.get_node(node_id))
.map(|tree_node| &clip_interner[tree_node.handle]);
if pictures[child_pic_index.0].flags.contains(PictureFlags::DISABLE_SNAPPING) {
pictures[pic_index.0].flags |= PictureFlags::DISABLE_SNAPPING;
// The logic behind this optimisation is that there's no need to clip
// the contents of a picture when the crop will be applied anyway as
// part of compositing the picture. However, this is not true if the
// picture includes a blur filter as the blur result depends on the
// offscreen pixels which may or may not be cropped away.
let has_blur = match &pictures[pic_index.0].composite_mode {
Some(PictureCompositeMode::Filter(Filter::Blur { .. })) => true,
Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => true,
Some(PictureCompositeMode::SvgFilter( .. )) => true,
_ => false,
};
if let Some((lca_node, pic_node)) = lca_node.zip(pic_node) {
// It is only safe to ignore the LCA clip (by making it the clip
// root) if it is equal to or larger than the picture clip. But
// this comparison also needs to take into account spatial nodes
// as the two clips may in general be on different spatial nodes.
// For this specific Gecko optimisation we expect the the two
// clips to be identical and have the same spatial node so it's
// simplest to just test for ClipItemKey equality (which includes
// both spatial node and the actual clip).
if lca_node.key == pic_node.key && !has_blur {
pictures[pic_index.0].clip_root = shared_clip_node_id;
}
}
// Update the spatial node of any child pictures
for cluster in &prim_list.clusters {
for prim_instance_index in cluster.prim_range() {
if let PrimitiveInstanceKind::Picture { pic_index: child_pic_index, .. } = prim_instances[prim_instance_index].kind {
let child_pic = &mut pictures[child_pic_index.0];
if child_pic.spatial_node_index == SpatialNodeIndex::UNKNOWN {
child_pic.spatial_node_index = spatial_node_index;
}
// Recurse into child pictures which may also have unknown spatial nodes
SceneBuilder::finalize_picture(
child_pic_index,
Some(prim_instance_index),
pictures,
Some(spatial_node_index),
clip_tree_builder,
prim_instances,
clip_interner,
);
if pictures[child_pic_index.0].flags.contains(PictureFlags::DISABLE_SNAPPING) {
pictures[pic_index.0].flags |= PictureFlags::DISABLE_SNAPPING;
}
}
}
}

View File

@ -229,9 +229,18 @@ pub fn update_prim_visibility(
};
if !is_passthrough {
frame_state.clip_tree.push_clip_root_leaf(
prim_instances[prim_instance_index].clip_leaf_id,
let clip_root = store
.pictures[pic_index.0]
.clip_root
.unwrap_or_else(|| {
// If we couldn't find a common ancestor then just use the
// clip node of the picture primitive itself
let leaf_id = prim_instances[prim_instance_index].clip_leaf_id;
frame_state.clip_tree.get_leaf(leaf_id).node_id
}
);
frame_state.clip_tree.push_clip_root_node(clip_root);
}
update_prim_visibility(