mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 23:02:20 +00:00
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:
parent
a10eea5360
commit
731133fa2d
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user