Commit efa4c64c authored by Chris Harrelson's avatar Chris Harrelson Committed by Commit Bot

Apply non-dirty-rect clips to content under filter.

Previously, we omitted these clips, as an inadvertent side-effect
of avoiding clipping to the dirty rect.

Bug: 734116
Change-Id: I6dcd92b80b8158f36689d5b48d92a0bdf43bb016
Reviewed-on: https://chromium-review.googlesource.com/567520
Commit-Queue: Chris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#486944}
parent d9d43422
<!DOCTYPE html> <!DOCTYPE html>
<div style="position:absolute; left:50px; top:50px; width:100px; height:100px; clip:rect(20px, 80px, 80px, 20px);"> <div style="position:absolute; left:50px; top:50px; width:100px; height:100px; clip:rect(20px, 80px, 80px, 20px);">
<div style="width:100px; height:100px; filter:blur(5px)"> <div style="position:absolute; left: 0px; top: 0px; width:100px; height:100px; filter:blur(5px)">
<div style="width:100px; height:100px; background:green;"></div> <div style="position:absolute; left: 0px; top: 0px; width:100px; height:100px; background:green; clip:rect(20px, 80px, 80px, 20px);"></div>
</div> </div>
</div> </div>
This test verifies CSS clip correctly applies to the output of stacking context effects if both present on the same element. This test verifies CSS clip correctly applies to the output of stacking context effects if both present on the same element.
layer at (0,0) size 800x600
LayoutView at (0,0) size 800x600
layer at (0,0) size 800x138
LayoutBlockFlow {HTML} at (0,0) size 800x138
LayoutBlockFlow {BODY} at (8,8) size 784x122
LayoutBlockFlow (anonymous) at (0,102) size 784x20
LayoutSVGRoot {svg} at (0,15) size 100x0
LayoutSVGHiddenContainer {defs} at (0,0) size 0x0
LayoutSVGResourceFilter {filter} [id="grain"] [filterUnits=objectBoundingBox] [primitiveUnits=userSpaceOnUse]
[feBlend mode="multiply"]
[SourceGraphic]
[feTurbulence type="NOISE" baseFrequency="0.90, 0.90" seed="0.00" numOctaves="3" stitchTiles="0"]
LayoutText {#text} at (0,0) size 0x0
layer at (108,108) size 102x102 clip at (109,109) size 100x100
LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 102x102 [border: (1px solid #000000)]
LayoutBlockFlow {DIV} at (-39,-39) size 100x100 [bgcolor=#ADD8E6]
<!doctype HTML>
<div id=clipper style="width: 100px; height: 100px; overflow: hidden; position: relative; left: 100px; top: 100px; border: 1px solid black; filter: url('#grain')">
<div style="width: 100px; height: 100px; margin-left: -40px; margin-top: -40px; background: lightblue"></div>
</div>
<svg height="0" width="100" viewbox="00 0 100 100">
<defs>
<filter x="-20%" y="-20%" width="120%" height="120%" filterUnits="objectBoundingBox" id="grain">
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="3" result="fpr1"></feTurbulence>
<feBlend in="SourceGraphic" in2="fpr1" mode="multiply" result="fpr2"></feBlend>
</filter>
</defs>
</svg>
layer at (0,0) size 800x600
LayoutView at (0,0) size 800x600
layer at (0,0) size 800x136
LayoutBlockFlow {HTML} at (0,0) size 800x136
LayoutBlockFlow {BODY} at (8,8) size 784x120
LayoutBlockFlow (anonymous) at (0,102) size 784x18
LayoutSVGRoot {svg} at (0,14) size 100x0
LayoutSVGHiddenContainer {defs} at (0,0) size 0x0
LayoutSVGResourceFilter {filter} [id="grain"] [filterUnits=objectBoundingBox] [primitiveUnits=userSpaceOnUse]
[feBlend mode="multiply"]
[SourceGraphic]
[feTurbulence type="NOISE" baseFrequency="0.90, 0.90" seed="0.00" numOctaves="3" stitchTiles="0"]
LayoutText {#text} at (0,0) size 0x0
layer at (108,108) size 102x102 clip at (109,109) size 100x100
LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 102x102 [border: (1px solid #000000)]
LayoutBlockFlow {DIV} at (-39,-39) size 100x100 [bgcolor=#ADD8E6]
...@@ -13,13 +13,26 @@ ...@@ -13,13 +13,26 @@
#include "platform/graphics/GraphicsContext.h" #include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/GraphicsLayer.h" #include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/filters/FilterEffect.h" #include "platform/graphics/filters/FilterEffect.h"
#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "platform/graphics/paint/FilterDisplayItem.h" #include "platform/graphics/paint/FilterDisplayItem.h"
#include "platform/graphics/paint/PaintController.h" #include "platform/graphics/paint/PaintController.h"
#include "platform/wtf/PtrUtil.h" #include "platform/wtf/PtrUtil.h"
namespace blink { namespace blink {
sk_sp<SkImageFilter> FilterPainter::GetImageFilter(PaintLayer& layer) {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return nullptr;
if (!layer.PaintsWithFilters())
return nullptr;
FilterEffect* last_effect = layer.LastFilterEffect();
if (!last_effect)
return nullptr;
return SkiaImageFilterBuilder::Build(last_effect, kInterpolationSpaceSRGB);
}
FilterPainter::FilterPainter(PaintLayer& layer, FilterPainter::FilterPainter(PaintLayer& layer,
GraphicsContext& context, GraphicsContext& context,
const LayoutPoint& offset_from_root, const LayoutPoint& offset_from_root,
...@@ -29,33 +42,14 @@ FilterPainter::FilterPainter(PaintLayer& layer, ...@@ -29,33 +42,14 @@ FilterPainter::FilterPainter(PaintLayer& layer,
: filter_in_progress_(false), : filter_in_progress_(false),
context_(context), context_(context),
layout_object_(layer.GetLayoutObject()) { layout_object_(layer.GetLayoutObject()) {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) sk_sp<SkImageFilter> image_filter = GetImageFilter(layer);
return;
if (!layer.PaintsWithFilters())
return;
FilterEffect* last_effect = layer.LastFilterEffect();
if (!last_effect)
return;
sk_sp<SkImageFilter> image_filter =
SkiaImageFilterBuilder::Build(last_effect, kInterpolationSpaceSRGB);
if (!image_filter) if (!image_filter)
return; return;
// We'll handle clipping to the dirty rect before filter rasterization.
// Filter processing will automatically expand the clip rect and the offscreen
// to accommodate any filter outsets.
// FIXME: It is incorrect to just clip to the damageRect here once multiple
// fragments are involved.
// Subsequent code should not clip to the dirty rect, since we've already
// done it above, and doing it later will defeat the outsets.
painting_info.clip_to_dirty_rect = false;
if (clip_rect.Rect() != painting_info.paint_dirty_rect || if (clip_rect.Rect() != painting_info.paint_dirty_rect ||
clip_rect.HasRadius()) { clip_rect.HasRadius()) {
// Apply clips outside the filter. See discussion about these clips
// in PaintLayerPainter regarding "clipping in the presence of filters".
clip_recorder_ = WTF::WrapUnique(new LayerClipRecorder( clip_recorder_ = WTF::WrapUnique(new LayerClipRecorder(
context, layer, DisplayItem::kClipLayerFilter, clip_rect, context, layer, DisplayItem::kClipLayerFilter, clip_rect,
painting_info.root_layer, LayoutPoint(), paint_flags, painting_info.root_layer, LayoutPoint(), paint_flags,
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <memory> #include <memory>
#include "core/paint/PaintLayerPaintingInfo.h" #include "core/paint/PaintLayerPaintingInfo.h"
#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "platform/wtf/Allocator.h" #include "platform/wtf/Allocator.h"
namespace blink { namespace blink {
...@@ -18,8 +19,6 @@ class LayerClipRecorder; ...@@ -18,8 +19,6 @@ class LayerClipRecorder;
class LayoutObject; class LayoutObject;
class FilterPainter { class FilterPainter {
STACK_ALLOCATED();
public: public:
FilterPainter(PaintLayer&, FilterPainter(PaintLayer&,
GraphicsContext&, GraphicsContext&,
...@@ -29,6 +28,11 @@ class FilterPainter { ...@@ -29,6 +28,11 @@ class FilterPainter {
PaintLayerFlags paint_flags); PaintLayerFlags paint_flags);
~FilterPainter(); ~FilterPainter();
// Returns whether it's ok to clip this PaintLayer's painted outputs
// the dirty rect. Some filters require input from outside this rect, in
// which case this method would return true.
static sk_sp<SkImageFilter> GetImageFilter(PaintLayer&);
private: private:
bool filter_in_progress_; bool filter_in_progress_;
GraphicsContext& context_; GraphicsContext& context_;
......
...@@ -527,10 +527,6 @@ LayoutRect PaintLayerClipper::LocalVisualRect() const { ...@@ -527,10 +527,6 @@ LayoutRect PaintLayerClipper::LocalVisualRect() const {
// PaintLayer are in physical coordinates, so the overflow has to be // PaintLayer are in physical coordinates, so the overflow has to be
// flipped. // flipped.
layer_bounds_with_visual_overflow); layer_bounds_with_visual_overflow);
if (layer_.PaintsWithFilters()) {
layer_bounds_with_visual_overflow =
layer_.MapLayoutRectForFilter(layer_bounds_with_visual_overflow);
}
return layer_bounds_with_visual_overflow; return layer_bounds_with_visual_overflow;
} }
......
...@@ -429,7 +429,9 @@ TEST_F(PaintLayerClipperTest, Filter) { ...@@ -429,7 +429,9 @@ TEST_F(PaintLayerClipperTest, Filter) {
.CalculateRects(context, infinite_rect, layer_bounds, background_rect, .CalculateRects(context, infinite_rect, layer_bounds, background_rect,
foreground_rect); foreground_rect);
EXPECT_EQ(LayoutRect(-12, -9, 124, 224), background_rect.Rect()); // The foreground and background should both be 100x200, since the filter
// applies after clip.
EXPECT_EQ(LayoutRect(0, 0, 100, 200), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 100, 200), foreground_rect.Rect()); EXPECT_EQ(LayoutRect(0, 0, 100, 200), foreground_rect.Rect());
} }
......
...@@ -400,18 +400,22 @@ PaintResult PaintLayerPainter::PaintLayerContents( ...@@ -400,18 +400,22 @@ PaintResult PaintLayerPainter::PaintLayerContents(
PaintLayerPaintingInfo local_painting_info(painting_info); PaintLayerPaintingInfo local_painting_info(painting_info);
local_painting_info.sub_pixel_accumulation = subpixel_accumulation; local_painting_info.sub_pixel_accumulation = subpixel_accumulation;
sk_sp<SkImageFilter> image_filter =
FilterPainter::GetImageFilter(paint_layer_);
bool should_paint_content = paint_layer_.HasVisibleContent() && bool should_paint_content = paint_layer_.HasVisibleContent() &&
is_self_painting_layer && is_self_painting_layer &&
!is_painting_overlay_scrollbars; !is_painting_overlay_scrollbars;
PaintLayerFragments layer_fragments; PaintLayerFragments layer_fragments;
ClipRectsCacheSlot cache_slot = (paint_flags & kPaintLayerUncachedClipRects)
? kUncachedClipRects
: kPaintingClipRects;
if (should_paint_content || should_paint_self_outline || if (should_paint_content || should_paint_self_outline ||
is_painting_overlay_scrollbars) { is_painting_overlay_scrollbars) {
// Collect the fragments. This will compute the clip rectangles and paint // Collect the fragments. This will compute the clip rectangles and paint
// offsets for each layer fragment. // offsets for each layer fragment.
ClipRectsCacheSlot cache_slot = (paint_flags & kPaintLayerUncachedClipRects)
? kUncachedClipRects
: kPaintingClipRects;
LayoutPoint offset_to_clipper; LayoutPoint offset_to_clipper;
const PaintLayer* paint_layer_for_fragments = &paint_layer_; const PaintLayer* paint_layer_for_fragments = &paint_layer_;
if (paint_flags & kPaintLayerPaintingAncestorClippingMaskPhase) { if (paint_flags & kPaintLayerPaintingAncestorClippingMaskPhase) {
...@@ -452,6 +456,31 @@ PaintResult PaintLayerPainter::PaintLayerContents( ...@@ -452,6 +456,31 @@ PaintResult PaintLayerPainter::PaintLayerContents(
local_painting_info.sub_pixel_accumulation); local_painting_info.sub_pixel_accumulation);
RepeatFixedPositionObjectInPages(single_fragment[0], painting_info, RepeatFixedPositionObjectInPages(single_fragment[0], painting_info,
layer_fragments); layer_fragments);
} else if (image_filter && !paint_layer_.EnclosingPaginationLayer()) {
// Clipping in the presence of filters needs to happen in two phases.
// It proceeds like this:
// 1. Apply clips inside the filter (including any clips on
// paint_layer_), not including the dirty rect.
// 2. Paint the filter.
// 3. Apply clips outside the filter.
//
// It is critical to avoid clipping to the dirty rect or any clips
// above the filter before applying the filter, because content which
// appears to be clipped may affect visual output if the filter moves
// pixels.
//
// #1 is applied in the lines below. #3 is applied in FilterPainter,
// and computed just before construction of the FilterPainter.
paint_layer_.AppendSingleFragmentIgnoringPagination(
layer_fragments, &paint_layer_,
LayoutRect(LayoutRect::InfiniteIntRect()), cache_slot,
PaintLayer::kUseGeometryMapper, kIgnorePlatformOverlayScrollbarSize,
kRespectOverflowClip, nullptr,
local_painting_info.sub_pixel_accumulation);
layer_fragments[0].layer_bounds.MoveBy(offset_from_root);
layer_fragments[0].background_rect.MoveBy(offset_from_root);
layer_fragments[0].foreground_rect.MoveBy(offset_from_root);
} else { } else {
paint_layer_for_fragments->CollectFragments( paint_layer_for_fragments->CollectFragments(
layer_fragments, local_painting_info.root_layer, layer_fragments, local_painting_info.root_layer,
...@@ -459,6 +488,7 @@ PaintResult PaintLayerPainter::PaintLayerContents( ...@@ -459,6 +488,7 @@ PaintResult PaintLayerPainter::PaintLayerContents(
PaintLayer::kUseGeometryMapper, kIgnorePlatformOverlayScrollbarSize, PaintLayer::kUseGeometryMapper, kIgnorePlatformOverlayScrollbarSize,
respect_overflow_clip, &offset_from_root, respect_overflow_clip, &offset_from_root,
local_painting_info.sub_pixel_accumulation); local_painting_info.sub_pixel_accumulation);
// PaintLayer::collectFragments depends on the paint dirty rect in // PaintLayer::collectFragments depends on the paint dirty rect in
// complicated ways. For now, always assume a partially painted output // complicated ways. For now, always assume a partially painted output
// for fragmented content. // for fragmented content.
...@@ -513,11 +543,23 @@ PaintResult PaintLayerPainter::PaintLayerContents( ...@@ -513,11 +543,23 @@ PaintResult PaintLayerPainter::PaintLayerContents(
bool selection_only = bool selection_only =
local_painting_info.GetGlobalPaintFlags() & kGlobalPaintSelectionOnly; local_painting_info.GetGlobalPaintFlags() & kGlobalPaintSelectionOnly;
{ // Begin block for the lifetime of any filter. { // Begin block for the lifetime of any filter.
FilterPainter filter_painter(paint_layer_, context, offset_from_root, Optional<FilterPainter> filter_painter;
layer_fragments.IsEmpty() if (image_filter) {
? ClipRect() // Compute clips outside the filter (#3, see above for discussion).
: layer_fragments[0].background_rect, PaintLayerFragments filter_fragments;
local_painting_info, paint_flags); paint_layer_.AppendSingleFragmentIgnoringPagination(
filter_fragments, local_painting_info.root_layer,
local_painting_info.paint_dirty_rect, cache_slot,
PaintLayer::kUseGeometryMapper, kIgnorePlatformOverlayScrollbarSize,
respect_overflow_clip, &offset_from_root,
local_painting_info.sub_pixel_accumulation);
filter_painter.emplace(paint_layer_, context, offset_from_root,
filter_fragments.IsEmpty()
? ClipRect()
: filter_fragments[0].background_rect,
local_painting_info, paint_flags);
}
bool is_painting_root_layer = (&paint_layer_) == painting_info.root_layer; bool is_painting_root_layer = (&paint_layer_) == painting_info.root_layer;
bool should_paint_background = bool should_paint_background =
...@@ -536,7 +578,6 @@ PaintResult PaintLayerPainter::PaintLayerContents( ...@@ -536,7 +578,6 @@ PaintResult PaintLayerPainter::PaintLayerContents(
if (should_paint_background) { if (should_paint_background) {
PaintBackgroundForFragments(layer_fragments, context, PaintBackgroundForFragments(layer_fragments, context,
painting_info.paint_dirty_rect,
local_painting_info, paint_flags); local_painting_info, paint_flags);
} }
...@@ -547,9 +588,8 @@ PaintResult PaintLayerPainter::PaintLayerContents( ...@@ -547,9 +588,8 @@ PaintResult PaintLayerPainter::PaintLayerContents(
} }
if (should_paint_own_contents) { if (should_paint_own_contents) {
PaintForegroundForFragments( PaintForegroundForFragments(layer_fragments, context, local_painting_info,
layer_fragments, context, painting_info.paint_dirty_rect, selection_only, paint_flags);
local_painting_info, selection_only, paint_flags);
} }
if (should_paint_self_outline) if (should_paint_self_outline)
...@@ -964,7 +1004,7 @@ void PaintLayerPainter::PaintFragmentWithPhase( ...@@ -964,7 +1004,7 @@ void PaintLayerPainter::PaintFragmentWithPhase(
DisplayItemClient* client = &paint_layer_.GetLayoutObject(); DisplayItemClient* client = &paint_layer_.GetLayoutObject();
Optional<LayerClipRecorder> clip_recorder; Optional<LayerClipRecorder> clip_recorder;
if (clip_state != kHasClipped && painting_info.clip_to_dirty_rect && if (clip_state != kHasClipped &&
(NeedsToClip(painting_info, clip_rect, paint_flags) || (NeedsToClip(painting_info, clip_rect, paint_flags) ||
paint_flags & kPaintLayerPaintingAncestorClippingMaskPhase)) { paint_flags & kPaintLayerPaintingAncestorClippingMaskPhase)) {
DisplayItem::Type clip_type = DisplayItem::Type clip_type =
...@@ -1039,7 +1079,6 @@ void PaintLayerPainter::PaintFragmentWithPhase( ...@@ -1039,7 +1079,6 @@ void PaintLayerPainter::PaintFragmentWithPhase(
void PaintLayerPainter::PaintBackgroundForFragments( void PaintLayerPainter::PaintBackgroundForFragments(
const PaintLayerFragments& layer_fragments, const PaintLayerFragments& layer_fragments,
GraphicsContext& context, GraphicsContext& context,
const LayoutRect& transparency_paint_dirty_rect,
const PaintLayerPaintingInfo& local_painting_info, const PaintLayerPaintingInfo& local_painting_info,
PaintLayerFlags paint_flags) { PaintLayerFlags paint_flags) {
Optional<DisplayItemCacheSkipper> cache_skipper; Optional<DisplayItemCacheSkipper> cache_skipper;
...@@ -1055,15 +1094,13 @@ void PaintLayerPainter::PaintBackgroundForFragments( ...@@ -1055,15 +1094,13 @@ void PaintLayerPainter::PaintBackgroundForFragments(
void PaintLayerPainter::PaintForegroundForFragments( void PaintLayerPainter::PaintForegroundForFragments(
const PaintLayerFragments& layer_fragments, const PaintLayerFragments& layer_fragments,
GraphicsContext& context, GraphicsContext& context,
const LayoutRect& transparency_paint_dirty_rect,
const PaintLayerPaintingInfo& local_painting_info, const PaintLayerPaintingInfo& local_painting_info,
bool selection_only, bool selection_only,
PaintLayerFlags paint_flags) { PaintLayerFlags paint_flags) {
DCHECK(!(paint_flags & kPaintLayerPaintingRootBackgroundOnly)); DCHECK(!(paint_flags & kPaintLayerPaintingRootBackgroundOnly));
// Optimize clipping for the single fragment case. // Optimize clipping for the single fragment case.
bool should_clip = local_painting_info.clip_to_dirty_rect && bool should_clip = layer_fragments.size() == 1 &&
layer_fragments.size() == 1 &&
!layer_fragments[0].foreground_rect.IsEmpty(); !layer_fragments[0].foreground_rect.IsEmpty();
ClipState clip_state = kHasNotClipped; ClipState clip_state = kHasNotClipped;
Optional<LayerClipRecorder> clip_recorder; Optional<LayerClipRecorder> clip_recorder;
......
...@@ -104,13 +104,11 @@ class CORE_EXPORT PaintLayerPainter { ...@@ -104,13 +104,11 @@ class CORE_EXPORT PaintLayerPainter {
void PaintBackgroundForFragments( void PaintBackgroundForFragments(
const PaintLayerFragments&, const PaintLayerFragments&,
GraphicsContext&, GraphicsContext&,
const LayoutRect& transparency_paint_dirty_rect,
const PaintLayerPaintingInfo&, const PaintLayerPaintingInfo&,
PaintLayerFlags); PaintLayerFlags);
void PaintForegroundForFragments( void PaintForegroundForFragments(
const PaintLayerFragments&, const PaintLayerFragments&,
GraphicsContext&, GraphicsContext&,
const LayoutRect& transparency_paint_dirty_rect,
const PaintLayerPaintingInfo&, const PaintLayerPaintingInfo&,
bool selection_only, bool selection_only,
PaintLayerFlags); PaintLayerFlags);
......
...@@ -89,7 +89,6 @@ struct PaintLayerPaintingInfo { ...@@ -89,7 +89,6 @@ struct PaintLayerPaintingInfo {
: root_layer(in_root_layer), : root_layer(in_root_layer),
paint_dirty_rect(in_dirty_rect), paint_dirty_rect(in_dirty_rect),
sub_pixel_accumulation(in_sub_pixel_accumulation), sub_pixel_accumulation(in_sub_pixel_accumulation),
clip_to_dirty_rect(true),
ancestor_has_clip_path_clipping(false), ancestor_has_clip_path_clipping(false),
global_paint_flags_(global_paint_flags) {} global_paint_flags_(global_paint_flags) {}
...@@ -100,7 +99,6 @@ struct PaintLayerPaintingInfo { ...@@ -100,7 +99,6 @@ struct PaintLayerPaintingInfo {
LayoutRect paint_dirty_rect; // relative to rootLayer; LayoutRect paint_dirty_rect; // relative to rootLayer;
LayoutSize sub_pixel_accumulation; LayoutSize sub_pixel_accumulation;
IntSize scroll_offset_accumulation; IntSize scroll_offset_accumulation;
bool clip_to_dirty_rect;
bool ancestor_has_clip_path_clipping; bool ancestor_has_clip_path_clipping;
private: private:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment