Commit f62169af authored by Tien-Ren Chen's avatar Tien-Ren Chen Committed by Commit Bot

[SPv2] Apply non-composited effects in group

This CL reworks PaintChunksToCcLayer to handle effect grouping properly.

Prior to this CL each paint chunk applies its property state all the way from the
layer state then pop all the way back. This CL implemented an algorithm to lazily
pop states so that contiguous paint chunks enclosed by the same effect will apply
the effect atomically.

As an optimization, clip region is also popped lazily. Note that this doesn't mean
AA-clips are applied as a group but still apply to individual draw commands
separately. It does improve performance by reusing previously generated clip region
when a complex clip region applies to multiple chunks repeatedly. Rounded clip
support is also added as a side change.

Transforms are always applied on the spot. This is because we can compute the
matrix between arbitrary nodes in O(1). Also this automatically handles "transform
inversion" cases as we don't even need to detect the relationship between nodes.

Cq-Include-Trybots: master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: I64657b657fad3e19132b9eb0d64db494955f2139
Reviewed-on: https://chromium-review.googlesource.com/540737
Commit-Queue: Tien-Ren Chen <trchen@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#485109}
parent e1603262
...@@ -520,11 +520,9 @@ Bug(none) fast/canvas/OffscreenCanvas-strokeRect-in-worker.html [ Failure ] ...@@ -520,11 +520,9 @@ Bug(none) fast/canvas/OffscreenCanvas-strokeRect-in-worker.html [ Failure ]
Bug(none) fast/canvas/OffscreenCanvas-transform-shadow-in-worker.html [ Failure ] Bug(none) fast/canvas/OffscreenCanvas-transform-shadow-in-worker.html [ Failure ]
Bug(none) fast/canvas/webgl/pixelated.html [ Failure ] Bug(none) fast/canvas/webgl/pixelated.html [ Failure ]
Bug(none) fast/clip/nestedTransparencyClip.html [ Failure ] Bug(none) fast/clip/nestedTransparencyClip.html [ Failure ]
Bug(none) fast/clip/overflow-border-radius-clip.html [ Failure ]
Bug(none) fast/clip/overflow-border-radius-combinations.html [ Failure ] Bug(none) fast/clip/overflow-border-radius-combinations.html [ Failure ]
Bug(none) fast/clip/overflow-border-radius-composited-parent.html [ Failure ] Bug(none) fast/clip/overflow-border-radius-composited-parent.html [ Failure ]
Bug(none) fast/clip/overflow-border-radius-composited.html [ Failure ] Bug(none) fast/clip/overflow-border-radius-composited.html [ Failure ]
Bug(none) fast/clip/overflow-border-radius-fixed-position.html [ Failure ]
Bug(none) fast/clip/overflow-border-radius-transformed.html [ Failure ] Bug(none) fast/clip/overflow-border-radius-transformed.html [ Failure ]
Bug(none) fast/css-generated-content/014.html [ Failure ] Bug(none) fast/css-generated-content/014.html [ Failure ]
Bug(none) fast/css-generated-content/table-parts-before-and-after.html [ Failure ] Bug(none) fast/css-generated-content/table-parts-before-and-after.html [ Failure ]
...@@ -637,7 +635,6 @@ Bug(none) fast/inline/inline-borders-with-bidi-override.html [ Failure ] ...@@ -637,7 +635,6 @@ Bug(none) fast/inline/inline-borders-with-bidi-override.html [ Failure ]
Bug(none) fast/inline/inline-continuation-borders.html [ Failure ] Bug(none) fast/inline/inline-continuation-borders.html [ Failure ]
Bug(none) fast/layers/normal-flow-hit-test.html [ Crash Failure ] Bug(none) fast/layers/normal-flow-hit-test.html [ Crash Failure ]
Bug(none) fast/layers/opacity-outline.html [ Failure ] Bug(none) fast/layers/opacity-outline.html [ Failure ]
Bug(none) fast/layers/overflow-hidden-rounded-corners-occlusion.html [ Failure ]
Bug(none) fast/layers/remove-layer-with-nested-stacking.html [ Skip ] Bug(none) fast/layers/remove-layer-with-nested-stacking.html [ Skip ]
Bug(none) fast/layers/scroll-rect-to-visible.html [ Failure ] Bug(none) fast/layers/scroll-rect-to-visible.html [ Failure ]
Bug(none) fast/lists/008.html [ Failure ] Bug(none) fast/lists/008.html [ Failure ]
...@@ -1006,7 +1003,6 @@ Bug(none) paint/invalidation/single-line-cells-repeating-thead-break-inside-on-t ...@@ -1006,7 +1003,6 @@ Bug(none) paint/invalidation/single-line-cells-repeating-thead-break-inside-on-t
Bug(none) paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-1.html [ Failure ] Bug(none) paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-1.html [ Failure ]
Bug(none) paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-2.html [ Failure ] Bug(none) paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-2.html [ Failure ]
Bug(none) paint/invalidation/svg/use-detach.svg [ Failure ] Bug(none) paint/invalidation/svg/use-detach.svg [ Failure ]
Bug(none) paint/background/rounded-clip-fractional-offset.html [ Failure ]
Bug(none) paint/clipath/clip-path-with-background-and-box-behind.html [ Failure ] Bug(none) paint/clipath/clip-path-with-background-and-box-behind.html [ Failure ]
Bug(none) paint/frames/frameset-with-stacking-context-and-not-stacking-context-children.html [ Failure ] Bug(none) paint/frames/frameset-with-stacking-context-and-not-stacking-context-children.html [ Failure ]
Bug(none) paint/frames/frameset-with-stacking-contexts.html [ Failure ] Bug(none) paint/frames/frameset-with-stacking-contexts.html [ Failure ]
...@@ -1252,7 +1248,6 @@ Bug(none) paint/selection/text-selection-newline-rtl-double-linebreak.html [ Fai ...@@ -1252,7 +1248,6 @@ Bug(none) paint/selection/text-selection-newline-rtl-double-linebreak.html [ Fai
# Mask does not work correctly # Mask does not work correctly
crbug.com/707444 svg/W3C-SVG-1.1/masking-intro-01-f.svg [ Failure ] crbug.com/707444 svg/W3C-SVG-1.1/masking-intro-01-f.svg [ Failure ]
crbug.com/707444 svg/as-background-image/svg-as-background-6.html [ Failure ]
crbug.com/707444 svg/batik/masking/maskRegions.svg [ Failure ] crbug.com/707444 svg/batik/masking/maskRegions.svg [ Failure ]
crbug.com/707444 svg/custom/clamped-masking-clipping.svg [ Failure ] crbug.com/707444 svg/custom/clamped-masking-clipping.svg [ Failure ]
crbug.com/707444 svg/custom/clip-mask-negative-scale.svg [ Failure ] crbug.com/707444 svg/custom/clip-mask-negative-scale.svg [ Failure ]
...@@ -1568,9 +1563,16 @@ crbug.com/589265 css3/blending/mix-blend-mode-isolated-group-2.html [ Failure ] ...@@ -1568,9 +1563,16 @@ crbug.com/589265 css3/blending/mix-blend-mode-isolated-group-2.html [ Failure ]
crbug.com/589265 css3/blending/mix-blend-mode-isolated-group-3.html [ Failure ] crbug.com/589265 css3/blending/mix-blend-mode-isolated-group-3.html [ Failure ]
crbug.com/589265 css3/blending/svg-blend-exclusion.html [ Failure ] crbug.com/589265 css3/blending/svg-blend-exclusion.html [ Failure ]
crbug.com/589265 css3/blending/svg-blend-screen.html [ Failure ] crbug.com/589265 css3/blending/svg-blend-screen.html [ Failure ]
crbug.com/589265 fast/backgrounds/size/contain-and-cover-zoomed.html [ Failure ]
crbug.com/589265 fast/css/ZeroOpacityLayers.html [ Failure ] crbug.com/589265 fast/css/ZeroOpacityLayers.html [ Failure ]
crbug.com/589265 fast/css/ZeroOpacityLayers2.html [ Failure ] crbug.com/589265 fast/css/ZeroOpacityLayers2.html [ Failure ]
crbug.com/589265 fast/forms/datalist/input-appearance-range-with-transform.html [ Failure ]
crbug.com/589265 fast/forms/indeterminate.html [ Failure ] crbug.com/589265 fast/forms/indeterminate.html [ Failure ]
crbug.com/589265 images/color-profile-filter.html [ Failure ]
crbug.com/589265 paint/invalidation/svg/repaint-paintorder.svg [ Failure ]
crbug.com/589265 paint/invalidation/svg/tabgroup.svg [ Failure ]
crbug.com/589265 svg/W3C-SVG-1.1/animate-elem-33-t.svg [ Failure ]
crbug.com/589265 svg/as-background-image/background-image-preserveaspectRatio-support.html [ Failure ]
crbug.com/589265 svg/custom/small-rect-scale.svg [ Failure ] crbug.com/589265 svg/custom/small-rect-scale.svg [ Failure ]
crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-dom-in-attr.html [ Failure ] crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-dom-in-attr.html [ Failure ]
crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-dom-in2-attr.html [ Failure ] crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-dom-in2-attr.html [ Failure ]
...@@ -1578,6 +1580,9 @@ crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-dom-mode-attr.html [ Fail ...@@ -1578,6 +1580,9 @@ crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-dom-mode-attr.html [ Fail
crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-svgdom-in-prop.html [ Failure ] crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-svgdom-in-prop.html [ Failure ]
crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-svgdom-in2-prop.html [ Failure ] crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-svgdom-in2-prop.html [ Failure ]
crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-svgdom-mode-prop.html [ Failure ] crbug.com/589265 svg/dynamic-updates/SVGFEBlendElement-svgdom-mode-prop.html [ Failure ]
crbug.com/589265 svg/text/text-selection-align-01-b.svg [ Failure ]
crbug.com/589265 svg/text/text-selection-align-05-b.svg [ Failure ]
crbug.com/589265 svg/zoom/page/zoom-svg-as-background-with-relative-size.html [ Failure ]
# Failures due to SPv2 using SkBlendMode::kDstIn to implement masks. # Failures due to SPv2 using SkBlendMode::kDstIn to implement masks.
# Some rounding differences is expected but none of them should be apparent. # Some rounding differences is expected but none of them should be apparent.
...@@ -1620,7 +1625,6 @@ crbug.com/157218 compositing/overflow/border-radius-on-two-ancestors-composited- ...@@ -1620,7 +1625,6 @@ crbug.com/157218 compositing/overflow/border-radius-on-two-ancestors-composited-
# Subpixel adjustments due to differences in compositing # Subpixel adjustments due to differences in compositing
crbug.com/589265 animations/animated-filter-svg-element.html [ Failure Crash ] crbug.com/589265 animations/animated-filter-svg-element.html [ Failure Crash ]
crbug.com/589265 compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents.html [ Failure ] crbug.com/589265 compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents.html [ Failure ]
crbug.com/589265 fast/css/all-shorthand-first-letter.html [ Failure ]
crbug.com/589265 fast/forms/number/number-appearance-spinbutton-layer.html [ Failure ] crbug.com/589265 fast/forms/number/number-appearance-spinbutton-layer.html [ Failure ]
crbug.com/589265 fast/layers/add-layer-with-nested-stacking.html [ Failure ] crbug.com/589265 fast/layers/add-layer-with-nested-stacking.html [ Failure ]
crbug.com/589265 fast/layers/opacity-transforms.html [ Failure ] crbug.com/589265 fast/layers/opacity-transforms.html [ Failure ]
...@@ -1673,7 +1677,6 @@ Bug(700530) compositing/geometry/abs-position-inside-opacity.html [ Failure ] ...@@ -1673,7 +1677,6 @@ Bug(700530) compositing/geometry/abs-position-inside-opacity.html [ Failure ]
Bug(700530) fast/css3-text/css3-text-decoration/text-underline-position/text-underline-position-cjk.html [ Failure ] Bug(700530) fast/css3-text/css3-text-decoration/text-underline-position/text-underline-position-cjk.html [ Failure ]
# The following debug crashes have not been triaged. # The following debug crashes have not been triaged.
crbug.com/702805 css3/blending/svg-blend-layer-filter.html [ Crash ]
crbug.com/702805 virtual/threaded/compositing/visibility/overlays-persist-on-navigation.html [ Crash ] crbug.com/702805 virtual/threaded/compositing/visibility/overlays-persist-on-navigation.html [ Crash ]
crbug.com/702805 svg/filters/feImage-self-referencing.html [ Crash ] crbug.com/702805 svg/filters/feImage-self-referencing.html [ Crash ]
crbug.com/702805 svg/filters/feImage-self-and-other-referencing.html [ Crash ] crbug.com/702805 svg/filters/feImage-self-and-other-referencing.html [ Crash ]
...@@ -1745,3 +1748,5 @@ Bug(none) fast/block/float/float-change-composited-scrolling.html [ Failure ] ...@@ -1745,3 +1748,5 @@ Bug(none) fast/block/float/float-change-composited-scrolling.html [ Failure ]
# reasons, in particular that the composited layerization algorithm provides # reasons, in particular that the composited layerization algorithm provides
# different results. # different results.
Bug(none) paint/invalidation/compositing/subpixel-offset-scaled-transform-composited.html [ Failure ] Bug(none) paint/invalidation/compositing/subpixel-offset-scaled-transform-composited.html [ Failure ]
crbug.com/737275 svg/as-background-image/svg-as-background-6.html [ Crash Failure ]
...@@ -1876,6 +1876,7 @@ test("blink_platform_unittests") { ...@@ -1876,6 +1876,7 @@ test("blink_platform_unittests") {
"graphics/RecordingImageBufferSurfaceTest.cpp", "graphics/RecordingImageBufferSurfaceTest.cpp",
"graphics/compositing/ContentLayerClientImplTest.cpp", "graphics/compositing/ContentLayerClientImplTest.cpp",
"graphics/compositing/PaintArtifactCompositorTest.cpp", "graphics/compositing/PaintArtifactCompositorTest.cpp",
"graphics/compositing/PaintChunksToCcLayerTest.cpp",
"graphics/filters/ImageFilterBuilderTest.cpp", "graphics/filters/ImageFilterBuilderTest.cpp",
"graphics/gpu/DrawingBufferTest.cpp", "graphics/gpu/DrawingBufferTest.cpp",
"graphics/gpu/SharedGpuContextTest.cpp", "graphics/gpu/SharedGpuContextTest.cpp",
......
...@@ -1478,7 +1478,7 @@ TEST_F(PaintArtifactCompositorTestWithPropertyTrees, TwoTransformsClipBetween) { ...@@ -1478,7 +1478,7 @@ TEST_F(PaintArtifactCompositorTestWithPropertyTrees, TwoTransformsClipBetween) {
rects_with_color.push_back( rects_with_color.push_back(
RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite)); RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
rects_with_color.push_back( rects_with_color.push_back(
RectWithColor(FloatRect(40, 50, 50, 60), Color(Color::kBlack))); RectWithColor(FloatRect(40, 50, 10, 10), Color(Color::kBlack)));
rects_with_color.push_back( rects_with_color.push_back(
RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray)); RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = ContentLayerAt(0); const cc::Layer* layer = ContentLayerAt(0);
......
...@@ -19,207 +19,338 @@ namespace blink { ...@@ -19,207 +19,338 @@ namespace blink {
namespace { namespace {
// Applies the clips between |localState| and |ancestorState| into a single constexpr gfx::Rect g_large_rect(-200000, -200000, 400000, 400000);
// combined cc::FloatClipDisplayItem on |ccList|. void AppendDisplayItemToCcDisplayItemList(const DisplayItem& display_item,
static void ApplyClipsBetweenStates(const PropertyTreeState& local_state, cc::DisplayItemList& list) {
const PropertyTreeState& ancestor_state, DCHECK(DisplayItem::IsDrawingType(display_item.GetType()));
cc::DisplayItemList& cc_list,
Vector<int>& needed_restores) { sk_sp<const PaintRecord> record =
DCHECK(local_state.Transform() == ancestor_state.Transform()); static_cast<const DrawingDisplayItem&>(display_item).GetPaintRecord();
#if DCHECK_IS_ON() if (!record)
const TransformPaintPropertyNode* transform_node = return;
local_state.Clip()->LocalTransformSpace(); cc::PaintOpBuffer* buffer = list.StartPaint();
if (transform_node != ancestor_state.Transform()) { buffer->push<cc::DrawRecordOp>(std::move(record));
const TransformationMatrix& local_to_ancestor_matrix = // TODO(trchen): Pass correct visual rect here.
GeometryMapper::SourceToDestinationProjection( // The visual rect of the item can be used by cc to skip replaying items
transform_node, ancestor_state.Transform()); // that can't be seen. To workaround a space conversion bug, the optimization
// Clips are only in descendant spaces that are transformed by one // is suppressed by passing a large rect.
// or more scrolls. list.EndPaintOfUnpaired(g_large_rect);
DCHECK(local_to_ancestor_matrix.IsIdentityOrTranslation()); }
void AppendRestore(cc::DisplayItemList& list, size_t n) {
cc::PaintOpBuffer* buffer = list.StartPaint();
while (n--)
buffer->push<cc::RestoreOp>();
list.EndPaintOfPairedEnd();
}
class ConversionContext {
public:
ConversionContext(const PropertyTreeState& layer_state,
cc::DisplayItemList& cc_list)
: current_transform_(layer_state.Transform()),
current_clip_(layer_state.Clip()),
current_effect_(layer_state.Effect()),
cc_list_(cc_list) {}
~ConversionContext();
// The main function of this class. It converts a list of paint chunks into
// non-pair display items, and paint properties associated with them are
// implemented by paired display items.
// This is done by closing and opening paired items to adjust the current
// property state to the chunk's state when each chunk is consumed.
// Note that the clip/effect state is "lazy" in the sense that it stays
// in whatever state the last chunk left with, and only adjusted when
// a new chunk is consumed. The class implemented a few helpers to manage
// state switching so that paired display items are nested properly.
//
// State management example (transform tree omitted).
// Corresponds to unit test PaintChunksToCcLayerTest.InterleavedClipEffect:
// Clip tree: C0 <-- C1 <-- C2 <-- C3 <-- C4
// Effect tree: E0(clip=C0) <-- E1(clip=C2) <-- E2(clip=C4)
// Layer state: C0, E0
// Paint chunks: P0(C3, E0), P1(C4, E2), P2(C3, E1), P3(C4, E0)
// Initialization:
// The current state is initalized with the layer state, and starts with
// an empty state stack.
// current_clip = C0
// current_effect = E0
// state_stack = []
// When P0 is consumed, C1, C2 and C3 need to be applied to the state:
// Output: Begin_C1 Begin_C2 Begin_C3 Draw_P0
// current_clip = C3
// state_stack = [C0, C1, C2]
// When P1 is consumed, C3 needs to be closed before E1 can be entered,
// then C3 and C4 need to be entered before E2 can be entered:
// Output: End_C3 Begin_E1 Begin_C3 Begin_C4 Begin_E2 Draw_P1
// current_clip = C4
// current_effect = E2
// state_stack = [C0, C1, E0, C2, C3, E1]
// When P2 is consumed, E2 then C4 need to be exited:
// Output: End_E2 End_C4 Draw_P2
// current_clip = C3
// current_effect = E1
// state_stack = [C0, C1, E0, C2]
// When P3 is consumed, C3 must exit before E1 can be exited, then we can
// enter C3 and C4:
// Output: End_C3 End_E1 Enter_C3 Enter_C4 Draw_P3
// current_clip = C4
// current_effect = E0
// state_stack = [C0, C1, C2, C3]
// At last, close all pushed states to balance pairs (this happens when the
// context object is destructed):
// Output: End_C4 End_C3 End_C2 End_C1
void Convert(const Vector<const PaintChunk*>&, const DisplayItemList&);
private:
// Switch the current clip to the target state, staying in the same effect.
// It is no-op if the context is already in the target state.
// Otherwise zero or more clips will be popped from or pushed onto the
// current state stack.
// INPUT:
// The target clip must be a descendant of the input clip of current effect.
// OUTPUT:
// The current transform may be changed.
// The current clip will change to the target clip.
// The current effect will not change.
void SwitchToClip(const ClipPaintPropertyNode*);
// Switch the current effect to the target state.
// It is no-op if the context is already in the target state.
// Otherwise zero or more effect effects will be popped from or pushed onto
// the state stack. As effects getting popped from the stack, clips applied
// on top of them will be popped as well. Also clips will be pushed at
// appropriate steps to apply output clip to newly pushed effects.
// INPUT:
// The target effect must be a descendant of the layer's effect.
// OUTPUT:
// The current transform may be changed.
// The current clip may be changed, and is guaranteed to be a descendant of
// the output clip of the target effect.
// The current effect will change to the target effect.
void SwitchToEffect(const EffectPaintPropertyNode*);
const TransformPaintPropertyNode* current_transform_;
const ClipPaintPropertyNode* current_clip_;
const EffectPaintPropertyNode* current_effect_;
// State stack.
// The size of the stack is the number of nested paired items that are
// currently nested. Note that this is a "restore stack", i.e. the top
// element does not represent the current state, but the state prior to
// applying the last paired begin.
struct StateEntry {
// Remembers the type of paired begin that caused a state to be saved.
// This is useful for emitting corresponding paired end.
enum class PairedType : char { kClip, kEffect } type;
const TransformPaintPropertyNode* transform;
const ClipPaintPropertyNode* clip;
const EffectPaintPropertyNode* effect;
};
Vector<StateEntry> state_stack_;
cc::DisplayItemList& cc_list_;
};
ConversionContext::~ConversionContext() {
for (size_t i = state_stack_.size(); i--;) {
if (state_stack_[i].type == StateEntry::PairedType::kClip) {
AppendRestore(cc_list_, 1);
} else {
DCHECK_EQ(StateEntry::PairedType::kEffect, state_stack_[i].type);
AppendRestore(cc_list_, 2);
}
}
}
void ConversionContext::SwitchToClip(const ClipPaintPropertyNode* target_clip) {
if (target_clip == current_clip_)
return;
// Step 1: Exit all clips until the lowest common ancestor is found.
const ClipPaintPropertyNode* lca_clip =
&LowestCommonAncestor(*target_clip, *current_clip_);
while (current_clip_ != lca_clip) {
DCHECK(state_stack_.size() &&
state_stack_.back().type == StateEntry::PairedType::kClip)
<< "Error: Chunk has a clip that escaped its effect's clip.";
if (!state_stack_.size() ||
state_stack_.back().type != StateEntry::PairedType::kClip)
break;
StateEntry& previous_state = state_stack_.back();
current_transform_ = previous_state.transform;
current_clip_ = previous_state.clip;
DCHECK_EQ(previous_state.effect, current_effect_);
state_stack_.pop_back();
AppendRestore(cc_list_, 1);
}
// Step 2: Collect all clips between the target clip and the current clip.
// At this point the current clip must be an ancestor of the target.
Vector<const ClipPaintPropertyNode*, 1u> pending_clips;
for (const ClipPaintPropertyNode* clip = target_clip; clip != current_clip_;
clip = clip->Parent()) {
// This should never happen unless the DCHECK in step 1 failed.
if (!clip)
break;
pending_clips.push_back(clip);
} }
#endif
const FloatClipRect& combined_clip = // Step 3: Now apply the list of clips in top-down order.
GeometryMapper::LocalToAncestorClipRect(local_state, ancestor_state); for (size_t i = pending_clips.size(); i--;) {
bool antialias = false; const ClipPaintPropertyNode* sub_clip = pending_clips[i];
DCHECK_EQ(current_clip_, sub_clip->Parent());
{ // Step 3a: Switch CTM to the clip's local space then apply clip.
cc::PaintOpBuffer* buffer = cc_list.StartPaint(); cc::PaintOpBuffer* buffer = cc_list_.StartPaint();
buffer->push<cc::SaveOp>(); buffer->push<cc::SaveOp>();
buffer->push<cc::ClipRectOp>(combined_clip.Rect(), SkClipOp::kIntersect, const TransformPaintPropertyNode* target_transform =
antialias); sub_clip->LocalTransformSpace();
cc_list.EndPaintOfPairedBegin(); if (current_transform_ != target_transform) {
buffer->push<cc::ConcatOp>(
static_cast<SkMatrix>(TransformationMatrix::ToSkMatrix44(
GeometryMapper::SourceToDestinationProjection(
target_transform, current_transform_))));
}
buffer->push<cc::ClipRectOp>(
static_cast<SkRect>(sub_clip->ClipRect().Rect()), SkClipOp::kIntersect,
false);
if (sub_clip->ClipRect().IsRounded()) {
buffer->push<cc::ClipRRectOp>(static_cast<SkRRect>(sub_clip->ClipRect()),
SkClipOp::kIntersect, true);
}
cc_list_.EndPaintOfPairedBegin();
// Step 3b: Adjust state and push previous state onto clip stack.
state_stack_.emplace_back(StateEntry{StateEntry::PairedType::kClip,
current_transform_, current_clip_,
current_effect_});
current_transform_ = target_transform;
current_clip_ = sub_clip;
} }
needed_restores.push_back(1);
} }
static void RecordPairedBeginDisplayItems( void ConversionContext::SwitchToEffect(
const Vector<PropertyTreeState>& paired_states, const EffectPaintPropertyNode* target_effect) {
const PropertyTreeState& pending_layer_state, if (target_effect == current_effect_)
cc::DisplayItemList& cc_list, return;
Vector<int>& needed_restores) {
PropertyTreeState mapped_clip_destination_space = pending_layer_state; // Step 1: Exit all effects until the lowest common ancestor is found.
PropertyTreeState clip_space = pending_layer_state; const EffectPaintPropertyNode* lca_effect =
bool has_clip = false; &LowestCommonAncestor(*target_effect, *current_effect_);
while (current_effect_ != lca_effect) {
for (Vector<PropertyTreeState>::const_reverse_iterator paired_state = DCHECK(state_stack_.size()) << "Error: Chunk layerized into a layer with "
paired_states.rbegin(); "an effect that's too deep.";
paired_state != paired_states.rend(); ++paired_state) { if (!state_stack_.size())
switch (paired_state->GetInnermostNode()) { break;
case PropertyTreeState::kTransform: {
if (has_clip) { StateEntry& previous_state = state_stack_.back();
ApplyClipsBetweenStates(clip_space, mapped_clip_destination_space, if (previous_state.type == StateEntry::PairedType::kClip) {
cc_list, needed_restores); AppendRestore(cc_list_, 1);
has_clip = false; } else {
} DCHECK_EQ(StateEntry::PairedType::kEffect, previous_state.type);
mapped_clip_destination_space = *paired_state; AppendRestore(cc_list_, 2);
clip_space = *paired_state;
TransformationMatrix matrix = paired_state->Transform()->Matrix();
matrix.ApplyTransformOrigin(paired_state->Transform()->Origin());
SkMatrix skmatrix =
static_cast<SkMatrix>(TransformationMatrix::ToSkMatrix44(matrix));
{
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
buffer->push<cc::SaveOp>();
buffer->push<cc::ConcatOp>(skmatrix);
cc_list.EndPaintOfPairedBegin();
}
needed_restores.push_back(1);
break;
}
case PropertyTreeState::kClip: {
// Clips are handled in |applyClips| when ending the iterator, or
// transitioning between transform spaces. Here we store off the
// PropertyTreeState of the first found clip, under the transform of
// pairedState->transform(). All subsequent clips before applying the
// transform will be applied in applyClips.
clip_space = *paired_state;
has_clip = true;
#if DCHECK_IS_ON()
if (paired_state->Clip()->LocalTransformSpace() !=
paired_state->Transform()) {
const TransformationMatrix& local_transform_matrix =
paired_state->Effect()->LocalTransformSpace()->Matrix();
// Clips are only in descendant spaces that are transformed by scroll.
DCHECK(local_transform_matrix.IsIdentityOrTranslation());
}
#endif
break;
}
case PropertyTreeState::kEffect: {
// TODO(chrishtr): skip effect and/or compositing display items if
// not necessary.
FloatRect clip_rect =
paired_state->Effect()->OutputClip()->ClipRect().Rect();
// TODO(chrishtr): specify origin of the filter.
FloatPoint filter_origin;
if (paired_state->Effect()->LocalTransformSpace() !=
paired_state->Transform()) {
const TransformPaintPropertyNode* transform_node =
paired_state->Effect()->LocalTransformSpace();
const TransformationMatrix& local_to_ancestor_matrix =
GeometryMapper::SourceToDestinationProjection(
transform_node, paired_state->Transform());
// Effects are only in descendant spaces that are transformed by one
// or more scrolls.
DCHECK(local_to_ancestor_matrix.IsIdentityOrTranslation());
clip_rect = local_to_ancestor_matrix.MapRect(clip_rect);
filter_origin = local_to_ancestor_matrix.MapPoint(filter_origin);
}
{
cc::PaintFlags flags;
flags.setBlendMode(paired_state->Effect()->BlendMode());
// TODO(ajuma): This should really be rounding instead of flooring the
// alpha value, but that breaks slimming paint reftests.
flags.setAlpha(static_cast<uint8_t>(
gfx::ToFlooredInt(255 * paired_state->Effect()->Opacity())));
flags.setColorFilter(
GraphicsContext::WebCoreColorFilterToSkiaColorFilter(
paired_state->Effect()->GetColorFilter()));
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
// TODO(chrishtr): compute bounds as necessary.
buffer->push<cc::SaveLayerOp>(nullptr, &flags);
cc_list.EndPaintOfPairedBegin();
}
needed_restores.push_back(1);
{
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
buffer->push<cc::SaveOp>();
buffer->push<cc::TranslateOp>(filter_origin.X(), filter_origin.Y());
cc::PaintFlags flags;
flags.setImageFilter(cc::RenderSurfaceFilters::BuildImageFilter(
paired_state->Effect()->Filter().AsCcFilterOperations(),
gfx::SizeF(clip_rect.Width(), clip_rect.Height())));
SkRect layer_bounds = clip_rect;
layer_bounds.offset(-filter_origin.X(), -filter_origin.Y());
buffer->push<cc::SaveLayerOp>(&layer_bounds, &flags);
buffer->push<cc::TranslateOp>(-filter_origin.X(), -filter_origin.Y());
cc_list.EndPaintOfPairedBegin();
}
// The SaveOp+SaveLayerOp above are grouped such that they share a
// visual rect, so group the two restores in the same way so we don't
// have a mismatch in the number of EndPaintOfPairedBegin() vs
// EndPaintOfPairedEnd().
needed_restores.push_back(2);
break;
}
case PropertyTreeState::kNone:
break;
} }
current_transform_ = previous_state.transform;
current_clip_ = previous_state.clip;
current_effect_ = previous_state.effect;
state_stack_.pop_back();
} }
if (has_clip) { // Step 2: Collect all effects between the target effect and the current
ApplyClipsBetweenStates(clip_space, mapped_clip_destination_space, cc_list, // effect. At this point the current effect must be an ancestor of the target.
needed_restores); Vector<const EffectPaintPropertyNode*, 1u> pending_effects;
for (const EffectPaintPropertyNode* effect = target_effect;
effect != current_effect_; effect = effect->Parent()) {
// This should never happen unless the DCHECK in step 1 failed.
if (!effect)
break;
pending_effects.push_back(effect);
} }
}
static void RecordPairedEndDisplayItems(const Vector<int>& needed_restores, // Step 3: Now apply the list of effects in top-down order.
cc::DisplayItemList& cc_list) { for (size_t i = pending_effects.size(); i--;) {
// TODO(danakj): This loop could use base::Reversed once it's allowed here. const EffectPaintPropertyNode* sub_effect = pending_effects[i];
for (auto it = needed_restores.rbegin(); it != needed_restores.rend(); ++it) { DCHECK_EQ(current_effect_, sub_effect->Parent());
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
int num_restores = *it; // Step 3a: Before each effect can be applied, we must enter its output
for (int i = 0; i < num_restores; ++i) // clip first.
buffer->push<cc::RestoreOp>(); SwitchToClip(sub_effect->OutputClip());
cc_list.EndPaintOfPairedEnd();
// Step 3b: Apply non-spatial effects first, adjust CTM, then apply spatial
// effects. Strictly speaking the CTM shall be appled first, it is done
// in this particular order only to save one SaveOp.
// TODO(trchen): Omit one of the SaveLayerOp if no-op.
cc::PaintOpBuffer* buffer = cc_list_.StartPaint();
cc::PaintFlags flags;
flags.setBlendMode(sub_effect->BlendMode());
// TODO(ajuma): This should really be rounding instead of flooring the
// alpha value, but that breaks slimming paint reftests.
flags.setAlpha(
static_cast<uint8_t>(gfx::ToFlooredInt(255 * sub_effect->Opacity())));
flags.setColorFilter(GraphicsContext::WebCoreColorFilterToSkiaColorFilter(
sub_effect->GetColorFilter()));
buffer->push<cc::SaveLayerOp>(nullptr, &flags);
const TransformPaintPropertyNode* target_transform =
sub_effect->LocalTransformSpace();
if (current_transform_ != target_transform) {
buffer->push<cc::ConcatOp>(
static_cast<SkMatrix>(TransformationMatrix::ToSkMatrix44(
GeometryMapper::SourceToDestinationProjection(
target_transform, current_transform_))));
}
// TODO(chrishtr): specify origin of the filter.
FloatPoint filter_origin;
buffer->push<cc::TranslateOp>(filter_origin.X(), filter_origin.Y());
// The size parameter is only used to computed the origin of zoom
// operation, which we never generate.
gfx::SizeF empty;
cc::PaintFlags filter_flags;
filter_flags.setImageFilter(cc::RenderSurfaceFilters::BuildImageFilter(
sub_effect->Filter().AsCcFilterOperations(), empty));
buffer->push<cc::SaveLayerOp>(nullptr, &filter_flags);
buffer->push<cc::TranslateOp>(-filter_origin.X(), -filter_origin.Y());
cc_list_.EndPaintOfPairedBegin();
// Step 3c: Adjust state and push previous state onto effect stack.
// TODO(trchen): Change input clip to expansion hint once implemented.
const ClipPaintPropertyNode* input_clip = current_clip_;
state_stack_.emplace_back(StateEntry{StateEntry::PairedType::kEffect,
current_transform_, current_clip_,
current_effect_});
current_transform_ = target_transform;
current_clip_ = input_clip;
current_effect_ = sub_effect;
} }
} }
constexpr gfx::Rect g_large_rect(-200000, -200000, 400000, 400000); void ConversionContext::Convert(const Vector<const PaintChunk*>& paint_chunks,
static void AppendDisplayItemToCcDisplayItemList( const DisplayItemList& display_items) {
const DisplayItem& display_item, for (auto chunk_it = paint_chunks.begin(); chunk_it != paint_chunks.end();
cc::DisplayItemList& cc_list) { chunk_it++) {
DCHECK(DisplayItem::IsDrawingType(display_item.GetType())); const PaintChunk& chunk = **chunk_it;
if (DisplayItem::IsDrawingType(display_item.GetType())) { const PropertyTreeState& chunk_state = chunk.properties.property_tree_state;
const auto& drawing_display_item = SwitchToEffect(chunk_state.Effect());
static_cast<const DrawingDisplayItem&>(display_item); SwitchToClip(chunk_state.Clip());
sk_sp<const cc::PaintOpBuffer> record = bool transformed = chunk_state.Transform() != current_transform_;
drawing_display_item.GetPaintRecord(); if (transformed) {
if (!record) cc::PaintOpBuffer* buffer = cc_list_.StartPaint();
return; buffer->push<cc::SaveOp>();
// In theory we would pass the bounds of the record, previously done as: buffer->push<cc::ConcatOp>(
// gfx::Rect bounds = gfx::SkIRectToRect(record->cullRect().roundOut()); static_cast<SkMatrix>(TransformationMatrix::ToSkMatrix44(
// or use the visual rect directly. However, clip content layers attempt GeometryMapper::SourceToDestinationProjection(
// to raster in a different space than that of the visual rects. We'll be chunk_state.Transform(), current_transform_))));
// reworking visual rects further for SPv2, so for now we just pass a cc_list_.EndPaintOfPairedBegin();
// visual rect large enough to make sure items raster.
{
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
buffer->push<cc::DrawRecordOp>(std::move(record));
cc_list.EndPaintOfUnpaired(g_large_rect);
} }
for (const auto& item : display_items.ItemsInPaintChunk(chunk))
AppendDisplayItemToCcDisplayItemList(item, cc_list_);
if (transformed)
AppendRestore(cc_list_, 1);
} }
} }
...@@ -241,36 +372,10 @@ scoped_refptr<cc::DisplayItemList> PaintChunksToCcLayer::Convert( ...@@ -241,36 +372,10 @@ scoped_refptr<cc::DisplayItemList> PaintChunksToCcLayer::Convert(
cc_list->EndPaintOfPairedBegin(); cc_list->EndPaintOfPairedBegin();
} }
for (const auto* paint_chunk : paint_chunks) { ConversionContext(layer_state, *cc_list).Convert(paint_chunks, display_items);
const PropertyTreeState* state =
&paint_chunk->properties.property_tree_state;
PropertyTreeStateIterator iterator(*state);
Vector<PropertyTreeState> paired_states;
for (; state && *state != layer_state; state = iterator.Next()) {
if (state->GetInnermostNode() != PropertyTreeState::kNone)
paired_states.push_back(*state);
}
// TODO(chrishtr): we can avoid some extra paired display items if
// multiple PaintChunks share them. We can also collapse clips between
// transforms into single clips in the same way that PaintLayerClipper does.
Vector<int> needed_restores;
RecordPairedBeginDisplayItems(paired_states, layer_state, *cc_list, if (need_translate)
needed_restores); AppendRestore(*cc_list, 1);
for (const auto& display_item :
display_items.ItemsInPaintChunk(*paint_chunk))
AppendDisplayItemToCcDisplayItemList(display_item, *cc_list);
RecordPairedEndDisplayItems(needed_restores, *cc_list);
}
if (need_translate) {
cc::PaintOpBuffer* buffer = cc_list->StartPaint();
buffer->push<cc::RestoreOp>();
cc_list->EndPaintOfPairedEnd();
}
if (under_invalidation_checking_params) { if (under_invalidation_checking_params) {
auto& params = *under_invalidation_checking_params; auto& params = *under_invalidation_checking_params;
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/graphics/compositing/PaintChunksToCcLayer.h"
#include <initializer_list>
#include "cc/paint/display_item_list.h"
#include "cc/paint/paint_op_buffer.h"
#include "platform/graphics/paint/ClipPaintPropertyNode.h"
#include "platform/graphics/paint/DisplayItemList.h"
#include "platform/graphics/paint/DrawingDisplayItem.h"
#include "platform/graphics/paint/EffectPaintPropertyNode.h"
#include "platform/graphics/paint/PaintChunk.h"
#include "platform/graphics/paint/TransformPaintPropertyNode.h"
#include "platform/testing/FakeDisplayItemClient.h"
#include "platform/testing/PaintPropertyTestHelpers.h"
#include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
using testing::CreateOpacityOnlyEffect;
class PaintChunksToCcLayerTest : public ::testing::Test,
private ScopedSlimmingPaintV2ForTest {
protected:
PaintChunksToCcLayerTest() : ScopedSlimmingPaintV2ForTest(true) {}
};
const char* GetOpName(cc::PaintOpType op) {
if (op == cc::PaintOpType::ClipRect)
return "ClipRect";
if (op == cc::PaintOpType::Concat)
return "Concat";
if (op == cc::PaintOpType::DrawRecord)
return "DrawRecord";
if (op == cc::PaintOpType::Save)
return "Save";
if (op == cc::PaintOpType::SaveLayer)
return "SaveLayer";
if (op == cc::PaintOpType::Restore)
return "Restore";
NOTREACHED();
return "ERROR";
}
// A simple matcher that only looks for a few ops, ignoring all else.
// Recognized ops: ClipRect, Concat, DrawRecord, Save, SaveLayer, Restore.
class PaintRecordMatcher
: public ::testing::MatcherInterface<const cc::PaintOpBuffer&> {
public:
static ::testing::Matcher<const cc::PaintOpBuffer&> Make(
std::initializer_list<cc::PaintOpType> args) {
return ::testing::MakeMatcher(new PaintRecordMatcher(args));
}
PaintRecordMatcher(std::initializer_list<cc::PaintOpType> args)
: expected_ops_(args) {}
bool MatchAndExplain(
const cc::PaintOpBuffer& buffer,
::testing::MatchResultListener* listener) const override {
auto next = expected_ops_.begin();
for (cc::PaintOpBuffer::Iterator it(&buffer); it; ++it) {
cc::PaintOpType op = (*it)->GetType();
switch (op) {
case cc::PaintOpType::ClipRect:
case cc::PaintOpType::Concat:
case cc::PaintOpType::DrawRecord:
case cc::PaintOpType::Save:
case cc::PaintOpType::SaveLayer:
case cc::PaintOpType::Restore:
if (next == expected_ops_.end()) {
if (listener->IsInterested()) {
*listener << "unexpected op " << GetOpName(op) << " at index "
<< it.op_idx() << ", expecting end of list.";
}
return false;
}
if (*next == op) {
next++;
continue;
}
if (listener->IsInterested()) {
*listener << "unexpected op " << GetOpName(op) << " at index "
<< it.op_idx() << ", expecting " << GetOpName(*next)
<< "(#" << (next - expected_ops_.begin()) << ").";
}
return false;
default:
continue;
}
}
if (next == expected_ops_.end())
return true;
if (listener->IsInterested()) {
*listener << "unexpected end of list, expecting " << GetOpName(*next)
<< "(#" << (next - expected_ops_.begin()) << ").";
}
return false;
}
void DescribeTo(::std::ostream* os) const override {
*os << "[";
bool first = true;
for (auto op : expected_ops_) {
if (first)
first = false;
else
*os << ", ";
*os << GetOpName(op);
}
*os << "]";
}
private:
Vector<cc::PaintOpType> expected_ops_;
};
// Convenient shorthands.
const TransformPaintPropertyNode* t0() {
return TransformPaintPropertyNode::Root();
}
const ClipPaintPropertyNode* c0() {
return ClipPaintPropertyNode::Root();
}
const EffectPaintPropertyNode* e0() {
return EffectPaintPropertyNode::Root();
}
PaintChunk::Id DefaultId() {
DEFINE_STATIC_LOCAL(FakeDisplayItemClient, fake_client, ());
return PaintChunk::Id(fake_client, DisplayItem::kDrawingFirst);
}
struct TestChunks {
Vector<PaintChunk> chunks;
DisplayItemList items = DisplayItemList(0);
void AddChunk(const TransformPaintPropertyNode* t,
const ClipPaintPropertyNode* c,
const EffectPaintPropertyNode* e) {
size_t i = items.size();
auto record = sk_make_sp<PaintRecord>();
record->push<cc::NoopOp>();
items.AllocateAndConstruct<DrawingDisplayItem>(
DefaultId().client, DefaultId().type, std::move(record),
FloatRect(-200000, -200000, 400000, 400000));
chunks.emplace_back(i, i + 1, DefaultId(), PropertyTreeState(t, c, e));
}
Vector<const PaintChunk*> GetChunkList() const {
Vector<const PaintChunk*> result;
for (const PaintChunk& chunk : chunks)
result.push_back(&chunk);
return result;
}
};
TEST_F(PaintChunksToCcLayerTest, EffectGroupingSimple) {
// This test verifies effects are applied as a group.
RefPtr<EffectPaintPropertyNode> e1 = CreateOpacityOnlyEffect(e0(), 0.5f);
TestChunks chunks;
chunks.AddChunk(t0(), c0(), e1.Get());
chunks.AddChunk(t0(), c0(), e1.Get());
sk_sp<PaintRecord> output =
PaintChunksToCcLayer::Convert(chunks.GetChunkList(),
PropertyTreeState(t0(), c0(), e0()),
gfx::Vector2dF(), chunks.items)
->ReleaseAsRecord();
EXPECT_THAT(
output,
Pointee(PaintRecordMatcher::Make(
{cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1>
cc::PaintOpType::DrawRecord, // <p0/>
cc::PaintOpType::DrawRecord, // <p1/>
cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1>
}
TEST_F(PaintChunksToCcLayerTest, EffectGroupingNested) {
// This test verifies nested effects are grouped properly.
RefPtr<EffectPaintPropertyNode> e1 = CreateOpacityOnlyEffect(e0(), 0.5f);
RefPtr<EffectPaintPropertyNode> e2 = CreateOpacityOnlyEffect(e1.Get(), 0.5f);
RefPtr<EffectPaintPropertyNode> e3 = CreateOpacityOnlyEffect(e1.Get(), 0.5f);
TestChunks chunks;
chunks.AddChunk(t0(), c0(), e2.Get());
chunks.AddChunk(t0(), c0(), e3.Get());
sk_sp<PaintRecord> output =
PaintChunksToCcLayer::Convert(chunks.GetChunkList(),
PropertyTreeState(t0(), c0(), e0()),
gfx::Vector2dF(), chunks.items)
->ReleaseAsRecord();
EXPECT_THAT(
output,
Pointee(PaintRecordMatcher::Make(
{cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1>
cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e2>
cc::PaintOpType::DrawRecord, // <p0/>
cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e2>
cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e3>
cc::PaintOpType::DrawRecord, // <p1/>
cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e3>
cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1>
}
TEST_F(PaintChunksToCcLayerTest, InterleavedClipEffect) {
// This test verifies effects are enclosed by their output clips.
// It is the same as the example made in the class comments of
// ConversionContext.
// Refer to PaintChunksToCcLayer.cpp for detailed explanation.
// (Search "State management example".)
RefPtr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
RefPtr<ClipPaintPropertyNode> c2 = ClipPaintPropertyNode::Create(
c1.Get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
RefPtr<ClipPaintPropertyNode> c3 = ClipPaintPropertyNode::Create(
c2.Get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
RefPtr<ClipPaintPropertyNode> c4 = ClipPaintPropertyNode::Create(
c3.Get(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
RefPtr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create(
e0(), t0(), c2.Get(), ColorFilter(), CompositorFilterOperations(), 0.5f,
SkBlendMode::kSrcOver);
RefPtr<EffectPaintPropertyNode> e2 = EffectPaintPropertyNode::Create(
e1.Get(), t0(), c4.Get(), ColorFilter(), CompositorFilterOperations(),
0.5f, SkBlendMode::kSrcOver);
TestChunks chunks;
chunks.AddChunk(t0(), c3.Get(), e0());
chunks.AddChunk(t0(), c4.Get(), e2.Get());
chunks.AddChunk(t0(), c3.Get(), e1.Get());
chunks.AddChunk(t0(), c4.Get(), e0());
sk_sp<PaintRecord> output =
PaintChunksToCcLayer::Convert(chunks.GetChunkList(),
PropertyTreeState(t0(), c0(), e0()),
gfx::Vector2dF(), chunks.items)
->ReleaseAsRecord();
EXPECT_THAT(
output,
Pointee(PaintRecordMatcher::Make(
{cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c1>
cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c2>
cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c3>
cc::PaintOpType::DrawRecord, // <p0/>
cc::PaintOpType::Restore, // </c3>
cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e1>
cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c3>
cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c4>
cc::PaintOpType::SaveLayer, cc::PaintOpType::SaveLayer, // <e2>
cc::PaintOpType::DrawRecord, // <p1/>
cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e2>
cc::PaintOpType::Restore, // </c4>
cc::PaintOpType::DrawRecord, // <p2/>
cc::PaintOpType::Restore, // </c3>
cc::PaintOpType::Restore, cc::PaintOpType::Restore, // </e1>
cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c3>
cc::PaintOpType::Save, cc::PaintOpType::ClipRect, // <c4>
cc::PaintOpType::DrawRecord, // <p3/>
cc::PaintOpType::Restore, // </c4>
cc::PaintOpType::Restore, // </c3>
cc::PaintOpType::Restore, // </c2>
cc::PaintOpType::Restore}))); // </c1>
}
TEST_F(PaintChunksToCcLayerTest, ClipSpaceInversion) {
// This test verifies chunks that have a shallower transform state than
// its clip can still be painted. The infamous CSS corner case:
// <div style="position:absolute; clip:rect(...)">
// <div style="position:fixed;">Clipped but not scroll along.</div>
// </div>
RefPtr<TransformPaintPropertyNode> t1 = TransformPaintPropertyNode::Create(
t0(), TransformationMatrix().Scale(2.f), FloatPoint3D());
RefPtr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
c0(), t1.Get(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
TestChunks chunks;
chunks.AddChunk(t0(), c1.Get(), e0());
sk_sp<PaintRecord> output =
PaintChunksToCcLayer::Convert(chunks.GetChunkList(),
PropertyTreeState(t0(), c0(), e0()),
gfx::Vector2dF(), chunks.items)
->ReleaseAsRecord();
EXPECT_THAT(output,
Pointee(PaintRecordMatcher::Make(
{cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1
cc::PaintOpType::ClipRect, // c1>
cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1^-1>
cc::PaintOpType::DrawRecord, // <p0/>
cc::PaintOpType::Restore, // </t1^-1>
cc::PaintOpType::Restore}))); // </c1 t1>
}
TEST_F(PaintChunksToCcLayerTest, EffectSpaceInversion) {
// This test verifies chunks that have a shallower transform state than
// its effect can still be painted. The infamous CSS corner case:
// <div style="overflow:scroll">
// <div style="opacity:0.5">
// <div style="position:absolute;">Transparent but not scroll
// along.</div>
// </div>
// </div>
RefPtr<TransformPaintPropertyNode> t1 = TransformPaintPropertyNode::Create(
t0(), TransformationMatrix().Scale(2.f), FloatPoint3D());
RefPtr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create(
e0(), t1.Get(), c0(), ColorFilter(), CompositorFilterOperations(), 0.5f,
SkBlendMode::kSrcOver);
TestChunks chunks;
chunks.AddChunk(t0(), c0(), e1.Get());
sk_sp<PaintRecord> output =
PaintChunksToCcLayer::Convert(chunks.GetChunkList(),
PropertyTreeState(t0(), c0(), e0()),
gfx::Vector2dF(), chunks.items)
->ReleaseAsRecord();
EXPECT_THAT(
output,
Pointee(PaintRecordMatcher::Make(
{cc::PaintOpType::SaveLayer, cc::PaintOpType::Concat, // <t1
cc::PaintOpType::SaveLayer, // e1>
cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1^-1>
cc::PaintOpType::DrawRecord, // <p0/>
cc::PaintOpType::Restore, // </t1^-1>
cc::PaintOpType::Restore, cc::PaintOpType::Restore}))); // </e1 t1>
}
TEST_F(PaintChunksToCcLayerTest, NonRootLayerSimple) {
// This test verifies a layer with composited property state does not
// apply properties again internally.
RefPtr<TransformPaintPropertyNode> t1 = TransformPaintPropertyNode::Create(
t0(), TransformationMatrix().Scale(2.f), FloatPoint3D());
RefPtr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
RefPtr<EffectPaintPropertyNode> e1 = CreateOpacityOnlyEffect(e0(), 0.5f);
TestChunks chunks;
chunks.AddChunk(t1.Get(), c1.Get(), e1.Get());
sk_sp<PaintRecord> output =
PaintChunksToCcLayer::Convert(
chunks.GetChunkList(),
PropertyTreeState(t1.Get(), c1.Get(), e1.Get()), gfx::Vector2dF(),
chunks.items)
->ReleaseAsRecord();
EXPECT_THAT(output,
Pointee(PaintRecordMatcher::Make({cc::PaintOpType::DrawRecord})));
}
TEST_F(PaintChunksToCcLayerTest, NonRootLayerTransformEscape) {
// This test verifies chunks that have a shallower transform state than the
// layer can still be painted.
RefPtr<TransformPaintPropertyNode> t1 = TransformPaintPropertyNode::Create(
t0(), TransformationMatrix().Scale(2.f), FloatPoint3D());
RefPtr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
RefPtr<EffectPaintPropertyNode> e1 = CreateOpacityOnlyEffect(e0(), 0.5f);
TestChunks chunks;
chunks.AddChunk(t0(), c1.Get(), e1.Get());
sk_sp<PaintRecord> output =
PaintChunksToCcLayer::Convert(
chunks.GetChunkList(),
PropertyTreeState(t1.Get(), c1.Get(), e1.Get()), gfx::Vector2dF(),
chunks.items)
->ReleaseAsRecord();
EXPECT_THAT(output,
Pointee(PaintRecordMatcher::Make(
{cc::PaintOpType::Save, cc::PaintOpType::Concat, // <t1^-1>
cc::PaintOpType::DrawRecord, // <p0/>
cc::PaintOpType::Restore}))); // </t1^-1>
}
} // namespace
} // namespace blink
...@@ -17,11 +17,6 @@ namespace blink { ...@@ -17,11 +17,6 @@ namespace blink {
namespace { namespace {
struct QuadWithColor {
FloatQuad quad;
Color color;
};
class DrawsRectangleCanvas : public SkCanvas { class DrawsRectangleCanvas : public SkCanvas {
public: public:
DrawsRectangleCanvas() DrawsRectangleCanvas()
...@@ -29,26 +24,27 @@ class DrawsRectangleCanvas : public SkCanvas { ...@@ -29,26 +24,27 @@ class DrawsRectangleCanvas : public SkCanvas {
save_count_(0), save_count_(0),
alpha_(255), alpha_(255),
alpha_save_layer_count_(-1) {} alpha_save_layer_count_(-1) {}
const Vector<QuadWithColor>& QuadsWithColor() const { return quads_; } const Vector<RectWithColor>& RectsWithColor() const { return rects_; }
void onDrawRect(const SkRect& rect, const SkPaint& paint) override { void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
SkRect clipped_rect(rect);
for (Vector<ClipAndIndex>::const_reverse_iterator clip = clips_.rbegin();
clip != clips_.rend(); clip++) {
if (SkRect::Intersects(rect, clip->rect))
CHECK(clipped_rect.intersect(clip->rect));
}
SkPoint quad[4]; SkPoint quad[4];
getTotalMatrix().mapRectToQuad(quad, clipped_rect); getTotalMatrix().mapRectToQuad(quad, rect);
QuadWithColor quad_with_color;
quad_with_color.quad = FloatQuad(quad); SkRect device_rect;
device_rect.set(quad, 4);
SkIRect device_clip_bounds;
FloatRect clipped_rect;
if (getDeviceClipBounds(&device_clip_bounds) &&
device_rect.intersect(SkRect::Make(device_clip_bounds)))
clipped_rect = device_rect;
unsigned paint_alpha = static_cast<unsigned>(paint.getAlpha()); unsigned paint_alpha = static_cast<unsigned>(paint.getAlpha());
SkPaint paint_with_alpha(paint); SkPaint paint_with_alpha(paint);
paint_with_alpha.setAlpha(static_cast<U8CPU>(alpha_ * paint_alpha / 255)); paint_with_alpha.setAlpha(static_cast<U8CPU>(alpha_ * paint_alpha / 255));
quad_with_color.color = Color(paint_with_alpha.getColor()); Color color = Color(paint_with_alpha.getColor());
quads_.push_back(quad_with_color);
SkCanvas::onDrawRect(clipped_rect, paint); rects_.emplace_back(clipped_rect, color);
SkCanvas::onDrawRect(rect, paint);
} }
SkCanvas::SaveLayerStrategy getSaveLayerStrategy( SkCanvas::SaveLayerStrategy getSaveLayerStrategy(
...@@ -70,8 +66,6 @@ class DrawsRectangleCanvas : public SkCanvas { ...@@ -70,8 +66,6 @@ class DrawsRectangleCanvas : public SkCanvas {
void willRestore() override { void willRestore() override {
DCHECK_GT(save_count_, 0); DCHECK_GT(save_count_, 0);
if (clips_.size() && save_count_ == clips_.back().save_count)
clips_.pop_back();
if (alpha_save_layer_count_ == save_count_) { if (alpha_save_layer_count_ == save_count_) {
alpha_ = 255; alpha_ = 255;
alpha_save_layer_count_ = -1; alpha_save_layer_count_ = -1;
...@@ -80,24 +74,8 @@ class DrawsRectangleCanvas : public SkCanvas { ...@@ -80,24 +74,8 @@ class DrawsRectangleCanvas : public SkCanvas {
SkCanvas::willRestore(); SkCanvas::willRestore();
} }
void onClipRect(const SkRect& rect,
SkClipOp op,
ClipEdgeStyle style) override {
ClipAndIndex clip_struct;
clip_struct.rect = rect;
clip_struct.save_count = save_count_;
clips_.push_back(clip_struct);
SkCanvas::onClipRect(rect, op, style);
}
struct ClipAndIndex {
SkRect rect;
int save_count;
};
private: private:
Vector<QuadWithColor> quads_; Vector<RectWithColor> rects_;
Vector<ClipAndIndex> clips_;
int save_count_; int save_count_;
unsigned alpha_; unsigned alpha_;
int alpha_save_layer_count_; int alpha_save_layer_count_;
...@@ -114,24 +92,24 @@ class DrawsRectanglesMatcher ...@@ -114,24 +92,24 @@ class DrawsRectanglesMatcher
::testing::MatchResultListener* listener) const override { ::testing::MatchResultListener* listener) const override {
DrawsRectangleCanvas canvas; DrawsRectangleCanvas canvas;
picture.playback(&canvas); picture.playback(&canvas);
const auto& quads = canvas.QuadsWithColor(); const auto& actual_rects = canvas.RectsWithColor();
if (quads.size() != rects_with_color_.size()) { if (actual_rects.size() != rects_with_color_.size()) {
*listener << "which draws " << quads.size() << " quads"; *listener << "which draws " << actual_rects.size() << " rects";
return false; return false;
} }
for (unsigned index = 0; index < quads.size(); index++) { for (unsigned index = 0; index < actual_rects.size(); index++) {
const auto& quad_with_color = quads[index]; const auto& actual_rect_with_color = actual_rects[index];
const auto& rect_with_color = rects_with_color_[index]; const auto& expect_rect_with_color = rects_with_color_[index];
const FloatRect& rect = quad_with_color.quad.BoundingBox(); if (EnclosingIntRect(actual_rect_with_color.rect) !=
if (EnclosingIntRect(rect) != EnclosingIntRect(rect_with_color.rect) || EnclosingIntRect(expect_rect_with_color.rect) ||
quad_with_color.color != rect_with_color.color) { actual_rect_with_color.color != expect_rect_with_color.color) {
if (listener->IsInterested()) { if (listener->IsInterested()) {
*listener << "at index " << index << " which draws "; *listener << "at index " << index << " which draws ";
PrintTo(rect, listener->stream()); PrintTo(actual_rect_with_color.rect, listener->stream());
*listener << " with color " *listener << " with color "
<< quad_with_color.color.Serialized().Ascii().data() << actual_rect_with_color.color.Serialized().Ascii().data()
<< "\n"; << "\n";
} }
return false; return false;
......
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