Bug 1787089 - Fix interactions between root level mix-blend containers and backdrop-filter r=gfx-reviewers,lsalzman

Differential Revision: https://phabricator.services.mozilla.com/D155536
This commit is contained in:
Glenn Watson 2022-08-29 00:30:24 +00:00
parent 5edbd09ad0
commit 4e87a61ffb
13 changed files with 151 additions and 98 deletions

View File

@ -2111,10 +2111,6 @@ impl<'a> SceneBuilder<'a> {
// clip node doesn't affect the stacking context rect.
let mut blit_reason = BlitReason::empty();
if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) {
blit_reason |= BlitReason::ISOLATE;
}
// If backface visibility is explicitly set, we force a surface. This
// simplifies handling this grouping as an atomic primitive that can
// be culled if the transform changes. It also allows us to cache
@ -2131,14 +2127,50 @@ impl<'a> SceneBuilder<'a> {
}
}
let is_redundant = FlattenedStackingContext::is_redundant(
flags,
// Check if we know this stacking context is redundant (doesn't need a surface)
// The check for blend-container redundancy is more involved so it's handled below.
let mut is_redundant = FlattenedStackingContext::is_redundant(
&context_3d,
&composite_ops,
blit_reason,
self.sc_stack.last(),
);
// If the stacking context is a blend container, and if we're at the top level
// of the stacking context tree, we may be able to make this blend container into a tile
// cache. This means that we get caching and correct scrolling invalidation for
// root level blend containers. For these cases, the readbacks of the backdrop
// are handled by doing partial reads of the picture cache tiles during rendering.
if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) {
// Check if we're inside a stacking context hierarchy with an existing surface
match self.sc_stack.last() {
Some(_) => {
// If we are already inside a stacking context hierarchy with a surface, then we
// need to do the normal isolate of this blend container as a regular surface
blit_reason |= BlitReason::ISOLATE;
is_redundant = false;
}
None => {
// If the current slice is empty, then we can just mark the slice as
// atomic (so that compositor surfaces don't get promoted within it)
// and use that slice as the backing surface for the blend container
if self.tile_cache_builder.is_current_slice_empty() &&
self.spatial_tree.is_root_coord_system(spatial_node_index) &&
!self.clip_tree_builder.clip_node_has_complex_clips(clip_node_id, &self.interners)
{
self.add_tile_cache_barrier_if_needed(SliceFlags::IS_ATOMIC);
self.tile_cache_builder.make_current_slice_atomic();
} else {
// If the slice wasn't empty, we need to isolate a separate surface
// to ensure that the content already in the slice is not used as
// an input to the mix-blend composite
blit_reason |= BlitReason::ISOLATE;
is_redundant = false;
}
}
}
}
// If stacking context is a scrollbar, force a new slice for the primitives
// within. The stacking context will be redundant and removed by above check.
let set_tile_cache_barrier = prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER);
@ -2209,31 +2241,6 @@ impl<'a> SceneBuilder<'a> {
let stacking_context = self.sc_stack.pop().unwrap();
// If the stacking context is a blend container, and if we're at the top level
// of the stacking context tree, we can make this blend container into a tile
// cache. This means that we get caching and correct scrolling invalidation for
// root level blend containers. For these cases, the readbacks of the backdrop
// are handled by doing partial reads of the picture cache tiles during rendering.
if stacking_context.flags.intersects(StackingContextFlags::IS_BLEND_CONTAINER) &&
self.sc_stack.is_empty() &&
self.spatial_tree.is_root_coord_system(stacking_context.spatial_node_index) &&
!self.clip_tree_builder.clip_node_has_complex_clips(stacking_context.clip_node_id, &self.interners)
{
self.tile_cache_builder.add_tile_cache(
stacking_context.prim_list,
self.root_iframe_clip,
);
return;
}
let parent_is_empty = match self.sc_stack.last() {
Some(parent_sc) => {
parent_sc.prim_list.is_empty()
},
None => true,
};
let mut source = match stacking_context.context_3d {
// TODO(gw): For now, as soon as this picture is in
// a 3D context, we draw it to an intermediate
@ -2449,29 +2456,18 @@ impl<'a> SceneBuilder<'a> {
// If we're the first primitive within a stacking context, then we can guarantee that the
// backdrop alpha will be 0, and then the blend equation collapses to just
// Cs = Cs, and the blend mode isn't taken into account at all.
if let (Some(mix_blend_mode), false) = (stacking_context.composite_ops.mix_blend_mode, parent_is_empty) {
let parent_is_isolated = match self.sc_stack.last() {
Some(parent_sc) => parent_sc.blit_reason.contains(BlitReason::ISOLATE),
None => false,
};
if parent_is_isolated {
let composite_mode = PictureCompositeMode::MixBlend(mix_blend_mode);
if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
let composite_mode = PictureCompositeMode::MixBlend(mix_blend_mode);
source = source.add_picture(
composite_mode,
stacking_context.clip_node_id,
Picture3DContext::Out,
&mut self.interners,
&mut self.prim_store,
&mut self.prim_instances,
&mut self.clip_tree_builder,
);
} else {
// If we have a mix-blend-mode, the stacking context needs to be isolated
// to blend correctly as per the CSS spec.
// If not already isolated, we can't correctly blend.
warn!("found a mix-blend-mode outside a blend container, ignoring");
}
source = source.add_picture(
composite_mode,
stacking_context.clip_node_id,
Picture3DContext::Out,
&mut self.interners,
&mut self.prim_store,
&mut self.prim_instances,
&mut self.clip_tree_builder,
);
}
// Set the stacking context clip on the outermost picture in the chain,
@ -3877,17 +3873,11 @@ impl FlattenedStackingContext {
/// Return true if the stacking context isn't needed.
pub fn is_redundant(
sc_flags: StackingContextFlags,
context_3d: &Picture3DContext<ExtendedPrimitiveInstance>,
composite_ops: &CompositeOps,
blit_reason: BlitReason,
parent: Option<&FlattenedStackingContext>,
) -> bool {
// If this is a blend container, it's needed
if sc_flags.intersects(StackingContextFlags::IS_BLEND_CONTAINER) {
return false;
}
// Any 3d context is required
if let Picture3DContext::In { .. } = context_3d {
return false;
@ -3898,11 +3888,20 @@ impl FlattenedStackingContext {
return false;
}
// We can skip mix-blend modes if they are the first primitive in a stacking context,
// see pop_stacking_context for a full explanation.
// If a mix-blend is active, we'll need to apply it in most cases
if composite_ops.mix_blend_mode.is_some() {
if let Some(parent) = parent {
if !parent.prim_list.is_empty() {
match parent {
Some(ref parent) => {
// However, if the parent stacking context is empty, then the mix-blend
// is a no-op, and we can skip it
if !parent.prim_list.is_empty() {
return false;
}
}
None => {
// TODO(gw): For now, we apply mix-blend ops that may be no-ops on a root
// level picture cache slice. We could apply a similar optimization
// to above with a few extra checks here, but it's probably quite rare.
return false;
}
}

View File

@ -79,18 +79,6 @@ impl PrimarySlice {
}
}
fn atomic(
prim_list: PrimitiveList,
iframe_clip: Option<ClipId>,
) -> Self {
PrimarySlice {
kind: SliceKind::Atomic { prim_list },
background_color: None,
iframe_clip,
slice_flags: SliceFlags::IS_ATOMIC,
}
}
fn has_too_many_slices(&self) -> bool {
match self.kind {
SliceKind::Atomic { .. } => false,
@ -172,6 +160,25 @@ impl TileCacheBuilder {
.merge();
}
/// Returns true if the current slice has no primitives added yet
pub fn is_current_slice_empty(&self) -> bool {
match self.primary_slices.last() {
Some(slice) => {
match slice.kind {
SliceKind::Default { ref secondary_slices } => {
secondary_slices.is_empty()
}
SliceKind::Atomic { ref prim_list } => {
prim_list.is_empty()
}
}
}
None => {
true
}
}
}
/// Set a barrier that forces a new tile cache next time a prim is added.
pub fn add_tile_cache_barrier(
&mut self,
@ -187,27 +194,6 @@ impl TileCacheBuilder {
self.primary_slices.push(new_slice);
}
/// Create a new tile cache for an existing prim_list
pub fn add_tile_cache(
&mut self,
prim_list: PrimitiveList,
iframe_clip: Option<ClipId>,
) {
let atomic_slice = PrimarySlice::atomic(
prim_list,
iframe_clip,
);
let next_slice = PrimarySlice::new(
SliceFlags::empty(),
iframe_clip,
None,
);
self.primary_slices.push(atomic_slice);
self.primary_slices.push(next_slice);
}
/// Create a new tile cache for an existing prim_list
fn build_tile_cache(
&mut self,

View File

@ -0,0 +1,8 @@
# verify that the results of a root-level mix-blend are available as
# input to a backdrop-filter following that follows the blend container
---
root:
items:
- type: rect
bounds: 0 0 100 100
color: cyan

View File

@ -0,0 +1,21 @@
# verify that the results of a root-level mix-blend are available as
# input to a backdrop-filter following that follows the blend container
---
root:
items:
- type: stacking-context
blend-container: true
items:
- type: rect
bounds: 0 0 100 100
color: red
- type: stacking-context
bounds: 0 0 100 100
mix-blend-mode: multiply
items:
- type: rect
bounds: 0 0 100 100
color: red
- type: backdrop-filter
bounds: 0 0 100 100
filters: invert(1)

View File

@ -0,0 +1,9 @@
---
root:
items:
- type: rect
bounds: 0 0 100 100
color: cyan
- type: rect
bounds: 100 0 100 100
color: magenta

View File

@ -0,0 +1,24 @@
# verify that content preceding a root level mix-blend container is still
# available as part of the backdrop root for subsequent backdrop-filters
---
root:
items:
- type: rect
bounds: 0 0 100 100
color: red
- type: stacking-context
blend-container: true
items:
- type: rect
bounds: 100 0 100 100
color: green
- type: stacking-context
bounds: 100 0 100 100
mix-blend-mode: multiply
items:
- type: rect
bounds: 0 0 100 100
color: green
- type: backdrop-filter
bounds: 0 0 200 100
filters: invert(1)

View File

@ -3,6 +3,7 @@ root:
items:
- type: stacking-context
bounds: [0, 0, 100, 100]
blend-container: true
items:
- type: stacking-context
bounds: [0, 0, 100, 100]

View File

@ -8,6 +8,7 @@ root:
- type: stacking-context
bounds: [0, 0, 100, 100]
filters: opacity(1.0)
blend-container: true
items:
- type: stacking-context
bounds: [0, 0, 100, 100]

View File

@ -8,6 +8,7 @@ root:
# causing the yellow rect to not blend with the green backdrop
- type: stacking-context
bounds: [0, 0, 100, 100]
blend-container: true
items:
- type: stacking-context
bounds: [0, 0, 100, 100]

View File

@ -3,6 +3,7 @@ root:
items:
- type: stacking-context
bounds: 0 0 2000 2000
blend-container: true
items:
- type: stacking-context
bounds: 0 0 2000 2000

View File

@ -27,3 +27,6 @@ platform(linux) == mix-blend-complex-transform.yaml mix-blend-complex-transform.
== raster-roots-1.yaml raster-roots-1-ref.yaml
== child-surface.yaml child-surface-ref.yaml
== blend-overflow.yaml blend-overflow-ref.yaml
== backdrop-filter-blend-container.yaml backdrop-filter-blend-container-ref.yaml
== backdrop-filter-preceding.yaml backdrop-filter-preceding-ref.yaml

View File

@ -88,7 +88,7 @@ test-pref(image.animation_mode,"none") == background-blending-image-color-gif.ht
== background-blending-background-attachement-fixed-scroll.html background-blending-background-attachement-fixed-scroll-ref.html
fuzzy(0-1,0-49719) == background-blend-mode-body-image.html background-blend-mode-body-image-ref.html
fuzzy(0-1,0-78472) == background-blend-mode-body-transparent-image.html background-blend-mode-body-transparent-image-ref.html
fuzzy(0-2,0-78472) == background-blend-mode-body-transparent-image.html background-blend-mode-body-transparent-image-ref.html
== background-blend-mode-body-fixed.html background-blend-mode-body-fixed-ref.html

View File

@ -2,5 +2,4 @@
expected:
if (os == "android") and debug: [PASS, FAIL]
fuzzy:
if os == "win": maxDifference=92;totalPixels=2135
if os == "mac" and swgl: maxDifference=1-1;totalPixels=2-2