summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc')
-rw-r--r--chromium/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc3361
1 files changed, 3361 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/chromium/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
new file mode 100644
index 00000000000..f6e3259974d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -0,0 +1,3361 @@
+// Copyright 2016 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 "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
+
+#include <memory>
+
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "cc/layers/layer.h"
+#include "cc/test/fake_layer_tree_frame_sink.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/trees/clip_node.h"
+#include "cc/trees/effect_node.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_settings.h"
+#include "cc/trees/scroll_node.h"
+#include "cc/trees/transform_node.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_layer_scroll_client.h"
+#include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_artifact.h"
+#include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
+#include "third_party/blink/renderer/platform/testing/fake_display_item_client.h"
+#include "third_party/blink/renderer/platform/testing/paint_property_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/picture_matchers.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/test_paint_artifact.h"
+#include "third_party/blink/renderer/platform/testing/web_layer_tree_view_impl_for_testing.h"
+
+namespace blink {
+
+using ::blink::test::CreateOpacityOnlyEffect;
+using testing::Pointee;
+
+PaintChunk::Id DefaultId() {
+ DEFINE_STATIC_LOCAL(FakeDisplayItemClient, fake_client, ());
+ return PaintChunk::Id(fake_client, DisplayItem::kDrawingFirst);
+}
+
+PaintChunkProperties DefaultPaintChunkProperties() {
+ PropertyTreeState property_tree_state(TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root());
+ return PaintChunkProperties(property_tree_state);
+}
+
+PaintChunk DefaultChunk() {
+ return PaintChunk(0, 1, DefaultId(), DefaultPaintChunkProperties());
+}
+
+gfx::Transform Translation(SkMScalar x, SkMScalar y) {
+ gfx::Transform transform;
+ transform.Translate(x, y);
+ return transform;
+}
+
+class WebLayerTreeViewWithLayerTreeFrameSink
+ : public WebLayerTreeViewImplForTesting {
+ public:
+ WebLayerTreeViewWithLayerTreeFrameSink(const cc::LayerTreeSettings& settings)
+ : WebLayerTreeViewImplForTesting(settings) {}
+
+ // cc::LayerTreeHostClient
+ void RequestNewLayerTreeFrameSink() override {
+ GetLayerTreeHost()->SetLayerTreeFrameSink(
+ cc::FakeLayerTreeFrameSink::Create3d());
+ }
+};
+
+class FakeScrollClient : public WebLayerScrollClient {
+ public:
+ FakeScrollClient() : did_scroll_count(0) {}
+
+ void DidScroll(const gfx::ScrollOffset& offset,
+ const CompositorElementId&) final {
+ did_scroll_count++;
+ last_scroll_offset = offset;
+ };
+
+ gfx::ScrollOffset last_scroll_offset;
+ unsigned did_scroll_count;
+};
+
+class PaintArtifactCompositorTest : public testing::Test,
+ private ScopedSlimmingPaintV2ForTest {
+ protected:
+ PaintArtifactCompositorTest()
+ : ScopedSlimmingPaintV2ForTest(true),
+ task_runner_(new base::TestSimpleTaskRunner),
+ task_runner_handle_(task_runner_) {}
+
+ void SetUp() override {
+ // Delay constructing the compositor until after the feature is set.
+ paint_artifact_compositor_ =
+ PaintArtifactCompositor::Create(scroll_client_);
+ paint_artifact_compositor_->EnableExtraDataForTesting();
+
+ cc::LayerTreeSettings settings =
+ WebLayerTreeViewImplForTesting::DefaultLayerTreeSettings();
+ settings.single_thread_proxy_scheduler = false;
+ settings.use_layer_lists = true;
+ web_layer_tree_view_ =
+ std::make_unique<WebLayerTreeViewWithLayerTreeFrameSink>(settings);
+ web_layer_tree_view_->SetRootLayer(
+ *paint_artifact_compositor_->GetWebLayer());
+ }
+
+ void TearDown() override {
+ // Make sure we remove all child layers to satisfy destructor
+ // child layer element id DCHECK.
+ WillBeRemovedFromFrame();
+ }
+
+ const cc::PropertyTrees& GetPropertyTrees() {
+ return *web_layer_tree_view_->GetLayerTreeHost()->property_trees();
+ }
+
+ const cc::LayerTreeHost& GetLayerTreeHost() {
+ return *web_layer_tree_view_->GetLayerTreeHost();
+ }
+
+ int ElementIdToEffectNodeIndex(CompositorElementId element_id) {
+ return web_layer_tree_view_->GetLayerTreeHost()
+ ->property_trees()
+ ->element_id_to_effect_node_index[element_id];
+ }
+
+ int ElementIdToTransformNodeIndex(CompositorElementId element_id) {
+ return web_layer_tree_view_->GetLayerTreeHost()
+ ->property_trees()
+ ->element_id_to_transform_node_index[element_id];
+ }
+
+ int ElementIdToScrollNodeIndex(CompositorElementId element_id) {
+ return web_layer_tree_view_->GetLayerTreeHost()
+ ->property_trees()
+ ->element_id_to_scroll_node_index[element_id];
+ }
+
+ const cc::TransformNode& GetTransformNode(const cc::Layer* layer) {
+ return *GetPropertyTrees().transform_tree.Node(
+ layer->transform_tree_index());
+ }
+
+ void Update(const PaintArtifact& artifact) {
+ CompositorElementIdSet element_ids;
+ Update(artifact, element_ids);
+ }
+
+ void Update(const PaintArtifact& artifact,
+ CompositorElementIdSet& element_ids) {
+ paint_artifact_compositor_->Update(artifact, element_ids);
+ web_layer_tree_view_->GetLayerTreeHost()->LayoutAndUpdateLayers();
+ }
+
+ void WillBeRemovedFromFrame() {
+ paint_artifact_compositor_->WillBeRemovedFromFrame();
+ }
+
+ cc::Layer* RootLayer() { return paint_artifact_compositor_->RootLayer(); }
+
+ size_t ContentLayerCount() {
+ return paint_artifact_compositor_->GetExtraDataForTesting()
+ ->content_layers.size();
+ }
+
+ cc::Layer* ContentLayerAt(unsigned index) {
+ return paint_artifact_compositor_->GetExtraDataForTesting()
+ ->content_layers[index]
+ .get();
+ }
+
+ CompositorElementId ScrollElementId(unsigned id) {
+ return CompositorElementIdFromUniqueObjectId(
+ id, CompositorElementIdNamespace::kScroll);
+ }
+
+ size_t SynthesizedClipLayerCount() {
+ return paint_artifact_compositor_->GetExtraDataForTesting()
+ ->synthesized_clip_layers.size();
+ }
+
+ cc::Layer* SynthesizedClipLayerAt(unsigned index) {
+ return paint_artifact_compositor_->GetExtraDataForTesting()
+ ->synthesized_clip_layers[index]
+ .get();
+ }
+
+ cc::Layer* ScrollHitTestLayerAt(unsigned index) {
+ return paint_artifact_compositor_->GetExtraDataForTesting()
+ ->scroll_hit_test_layers[index]
+ .get();
+ }
+
+ // Return the index of |layer| in the root layer list, or -1 if not found.
+ int LayerIndex(const cc::Layer* layer) {
+ for (size_t i = 0; i < RootLayer()->children().size(); ++i) {
+ if (RootLayer()->children()[i] == layer)
+ return i;
+ }
+ return -1;
+ }
+
+ void AddSimpleRectChunk(TestPaintArtifact& artifact) {
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
+ }
+
+ void CreateSimpleArtifactWithOpacity(TestPaintArtifact& artifact,
+ float opacity,
+ bool include_preceding_chunk,
+ bool include_subsequent_chunk) {
+ if (include_preceding_chunk)
+ AddSimpleRectChunk(artifact);
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), opacity);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), effect)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ if (include_subsequent_chunk)
+ AddSimpleRectChunk(artifact);
+ Update(artifact.Build());
+ }
+
+ using PendingLayer = PaintArtifactCompositor::PendingLayer;
+
+ bool MightOverlap(const PendingLayer& a, const PendingLayer& b) {
+ return PaintArtifactCompositor::MightOverlap(a, b);
+ }
+
+ FakeScrollClient& ScrollClient() { return scroll_client_; }
+
+ private:
+ FakeScrollClient scroll_client_;
+ std::unique_ptr<PaintArtifactCompositor> paint_artifact_compositor_;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle task_runner_handle_;
+ std::unique_ptr<WebLayerTreeViewWithLayerTreeFrameSink> web_layer_tree_view_;
+};
+
+const auto kNotScrollingOnMain = MainThreadScrollingReason::kNotScrollingOnMain;
+
+// Convenient shorthands.
+const TransformPaintPropertyNode* t0() {
+ return TransformPaintPropertyNode::Root();
+}
+const ClipPaintPropertyNode* c0() {
+ return ClipPaintPropertyNode::Root();
+}
+const EffectPaintPropertyNode* e0() {
+ return EffectPaintPropertyNode::Root();
+}
+
+TEST_F(PaintArtifactCompositorTest, EmptyPaintArtifact) {
+ PaintArtifact empty_artifact;
+ Update(empty_artifact);
+ EXPECT_TRUE(RootLayer()->children().empty());
+}
+
+TEST_F(PaintArtifactCompositorTest, OneChunkWithAnOffset) {
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(50, -50, 100, 100), Color::kWhite);
+ Update(artifact.Build());
+
+ ASSERT_EQ(1u, ContentLayerCount());
+ const cc::Layer* child = ContentLayerAt(0);
+ EXPECT_THAT(
+ child->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 100, 100), Color::kWhite)));
+ EXPECT_EQ(Translation(50, -50), child->ScreenSpaceTransform());
+ EXPECT_EQ(gfx::Size(100, 100), child->bounds());
+}
+
+TEST_F(PaintArtifactCompositorTest, OneTransform) {
+ // A 90 degree clockwise rotation about (100, 100).
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(), TransformationMatrix().Rotate(90),
+ FloatPoint3D(100, 100, 0), false, 0, CompositingReason::k3DTransform);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(transform, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
+ artifact
+ .Chunk(transform, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ ASSERT_EQ(2u, ContentLayerCount());
+ {
+ const cc::Layer* layer = ContentLayerAt(0);
+
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(100, 100, 200, 100), Color::kBlack));
+
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ gfx::RectF mapped_rect(0, 0, 100, 100);
+ layer->ScreenSpaceTransform().TransformRect(&mapped_rect);
+ EXPECT_EQ(gfx::RectF(100, 0, 100, 100), mapped_rect);
+ }
+ {
+ const cc::Layer* layer = ContentLayerAt(1);
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 100, 100), Color::kGray)));
+ EXPECT_EQ(gfx::Transform(), layer->ScreenSpaceTransform());
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, TransformCombining) {
+ // A translation by (5, 5) within a 2x scale about (10, 10).
+ scoped_refptr<TransformPaintPropertyNode> transform1 =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(), TransformationMatrix().Scale(2),
+ FloatPoint3D(10, 10, 0), false, 0, CompositingReason::k3DTransform);
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ transform1, TransformationMatrix().Translate(5, 5), FloatPoint3D(),
+ false, 0, CompositingReason::k3DTransform);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(transform1, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
+ artifact
+ .Chunk(transform2, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 200), Color::kBlack);
+ Update(artifact.Build());
+
+ ASSERT_EQ(2u, ContentLayerCount());
+ {
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kWhite)));
+ gfx::RectF mapped_rect(0, 0, 300, 200);
+ layer->ScreenSpaceTransform().TransformRect(&mapped_rect);
+ EXPECT_EQ(gfx::RectF(-10, -10, 600, 400), mapped_rect);
+ }
+ {
+ const cc::Layer* layer = ContentLayerAt(1);
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kBlack)));
+ gfx::RectF mapped_rect(0, 0, 300, 200);
+ layer->ScreenSpaceTransform().TransformRect(&mapped_rect);
+ EXPECT_EQ(gfx::RectF(0, 0, 600, 400), mapped_rect);
+ }
+ EXPECT_NE(ContentLayerAt(0)->transform_tree_index(),
+ ContentLayerAt(1)->transform_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, FlattensInheritedTransform) {
+ for (bool transform_is_flattened : {true, false}) {
+ SCOPED_TRACE(transform_is_flattened);
+
+ // The flattens_inherited_transform bit corresponds to whether the _parent_
+ // transform node flattens the transform. This is because Blink's notion of
+ // flattening determines whether content within the node's local transform
+ // is flattened, while cc's notion applies in the parent's coordinate space.
+ scoped_refptr<TransformPaintPropertyNode> transform1 =
+ TransformPaintPropertyNode::Create(TransformPaintPropertyNode::Root(),
+ TransformationMatrix(),
+ FloatPoint3D());
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ transform1, TransformationMatrix().Rotate3d(0, 45, 0),
+ FloatPoint3D());
+ scoped_refptr<TransformPaintPropertyNode> transform3 =
+ TransformPaintPropertyNode::Create(
+ transform2, TransformationMatrix().Rotate3d(0, 45, 0),
+ FloatPoint3D(), transform_is_flattened);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(transform3, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
+ Update(artifact.Build());
+
+ ASSERT_EQ(1u, ContentLayerCount());
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kWhite)));
+
+ // The leaf transform node should flatten its inherited transform node
+ // if and only if the intermediate rotation transform in the Blink tree
+ // flattens.
+ const cc::TransformNode* transform_node3 =
+ GetPropertyTrees().transform_tree.Node(layer->transform_tree_index());
+ EXPECT_EQ(transform_is_flattened,
+ transform_node3->flattens_inherited_transform);
+
+ // Given this, we should expect the correct screen space transform for
+ // each case. If the transform was flattened, we should see it getting
+ // an effective horizontal scale of 1/sqrt(2) each time, thus it gets
+ // half as wide. If the transform was not flattened, we should see an
+ // empty rectangle (as the total 90 degree rotation makes it
+ // perpendicular to the viewport).
+ gfx::RectF rect(0, 0, 100, 100);
+ layer->ScreenSpaceTransform().TransformRect(&rect);
+ if (transform_is_flattened)
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 50, 100), rect);
+ else
+ EXPECT_TRUE(rect.IsEmpty());
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, SortingContextID) {
+ // Has no 3D rendering context.
+ scoped_refptr<TransformPaintPropertyNode> transform1 =
+ TransformPaintPropertyNode::Create(TransformPaintPropertyNode::Root(),
+ TransformationMatrix(),
+ FloatPoint3D());
+ // Establishes a 3D rendering context.
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(transform1, TransformationMatrix(),
+ FloatPoint3D(), false, 1,
+ CompositingReason::k3DTransform);
+ // Extends the 3D rendering context of transform2.
+ scoped_refptr<TransformPaintPropertyNode> transform3 =
+ TransformPaintPropertyNode::Create(transform2, TransformationMatrix(),
+ FloatPoint3D(), false, 1,
+ CompositingReason::k3DTransform);
+ // Establishes a 3D rendering context distinct from transform2.
+ scoped_refptr<TransformPaintPropertyNode> transform4 =
+ TransformPaintPropertyNode::Create(transform2, TransformationMatrix(),
+ FloatPoint3D(), false, 2,
+ CompositingReason::k3DTransform);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(transform1, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
+ artifact
+ .Chunk(transform2, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 200), Color::kLightGray);
+ artifact
+ .Chunk(transform3, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 200), Color::kDarkGray);
+ artifact
+ .Chunk(transform4, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 200), Color::kBlack);
+ Update(artifact.Build());
+
+ ASSERT_EQ(4u, ContentLayerCount());
+
+ // The white layer is not 3D sorted.
+ const cc::Layer* white_layer = ContentLayerAt(0);
+ EXPECT_THAT(
+ white_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kWhite)));
+ int white_sorting_context_id =
+ GetTransformNode(white_layer).sorting_context_id;
+ EXPECT_EQ(white_layer->sorting_context_id(), white_sorting_context_id);
+ EXPECT_EQ(0, white_sorting_context_id);
+
+ // The light gray layer is 3D sorted.
+ const cc::Layer* light_gray_layer = ContentLayerAt(1);
+ EXPECT_THAT(
+ light_gray_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kLightGray)));
+ int light_gray_sorting_context_id =
+ GetTransformNode(light_gray_layer).sorting_context_id;
+ EXPECT_NE(0, light_gray_sorting_context_id);
+
+ // The dark gray layer is 3D sorted with the light gray layer, but has a
+ // separate transform node.
+ const cc::Layer* dark_gray_layer = ContentLayerAt(2);
+ EXPECT_THAT(
+ dark_gray_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kDarkGray)));
+ int dark_gray_sorting_context_id =
+ GetTransformNode(dark_gray_layer).sorting_context_id;
+ EXPECT_EQ(light_gray_sorting_context_id, dark_gray_sorting_context_id);
+ EXPECT_NE(light_gray_layer->transform_tree_index(),
+ dark_gray_layer->transform_tree_index());
+
+ // The black layer is 3D sorted, but in a separate context from the previous
+ // layers.
+ const cc::Layer* black_layer = ContentLayerAt(3);
+ EXPECT_THAT(
+ black_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kBlack)));
+ int black_sorting_context_id =
+ GetTransformNode(black_layer).sorting_context_id;
+ EXPECT_NE(0, black_sorting_context_id);
+ EXPECT_NE(light_gray_sorting_context_id, black_sorting_context_id);
+}
+
+TEST_F(PaintArtifactCompositorTest, OneClip) {
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(100, 100, 300, 200));
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(220, 80, 300, 200), Color::kBlack);
+ Update(artifact.Build());
+
+ ASSERT_EQ(1u, ContentLayerCount());
+ const cc::Layer* layer = ContentLayerAt(0);
+ // The layer is clipped.
+ EXPECT_EQ(gfx::Size(180, 180), layer->bounds());
+ EXPECT_EQ(gfx::Vector2dF(220, 100), layer->offset_to_transform_parent());
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 300, 180), Color::kBlack)));
+ EXPECT_EQ(Translation(220, 100), layer->ScreenSpaceTransform());
+
+ const cc::ClipNode* clip_node =
+ GetPropertyTrees().clip_tree.Node(layer->clip_tree_index());
+ EXPECT_EQ(cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP, clip_node->clip_type);
+ EXPECT_EQ(gfx::RectF(100, 100, 300, 200), clip_node->clip);
+}
+
+TEST_F(PaintArtifactCompositorTest, NestedClips) {
+ scoped_refptr<ClipPaintPropertyNode> clip1 = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(100, 100, 700, 700), nullptr, nullptr,
+ CompositingReason::kOverflowScrollingTouch);
+ scoped_refptr<ClipPaintPropertyNode> clip2 = ClipPaintPropertyNode::Create(
+ clip1, TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(200, 200, 700, 700), nullptr, nullptr,
+ CompositingReason::kOverflowScrollingTouch);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip1,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(300, 350, 100, 100), Color::kWhite);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip2,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(300, 350, 100, 100), Color::kLightGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip1,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(300, 350, 100, 100), Color::kDarkGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip2,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(300, 350, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ ASSERT_EQ(4u, ContentLayerCount());
+
+ const cc::Layer* white_layer = ContentLayerAt(0);
+ EXPECT_THAT(
+ white_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 100, 100), Color::kWhite)));
+ EXPECT_EQ(Translation(300, 350), white_layer->ScreenSpaceTransform());
+
+ const cc::Layer* light_gray_layer = ContentLayerAt(1);
+ EXPECT_THAT(
+ light_gray_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 100, 100), Color::kLightGray)));
+ EXPECT_EQ(Translation(300, 350), light_gray_layer->ScreenSpaceTransform());
+
+ const cc::Layer* dark_gray_layer = ContentLayerAt(2);
+ EXPECT_THAT(
+ dark_gray_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 100, 100), Color::kDarkGray)));
+ EXPECT_EQ(Translation(300, 350), dark_gray_layer->ScreenSpaceTransform());
+
+ const cc::Layer* black_layer = ContentLayerAt(3);
+ EXPECT_THAT(
+ black_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 100, 100), Color::kBlack)));
+ EXPECT_EQ(Translation(300, 350), black_layer->ScreenSpaceTransform());
+
+ EXPECT_EQ(white_layer->clip_tree_index(), dark_gray_layer->clip_tree_index());
+ const cc::ClipNode* outer_clip =
+ GetPropertyTrees().clip_tree.Node(white_layer->clip_tree_index());
+ EXPECT_EQ(cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP, outer_clip->clip_type);
+ EXPECT_EQ(gfx::RectF(100, 100, 700, 700), outer_clip->clip);
+
+ EXPECT_EQ(light_gray_layer->clip_tree_index(),
+ black_layer->clip_tree_index());
+ const cc::ClipNode* inner_clip =
+ GetPropertyTrees().clip_tree.Node(black_layer->clip_tree_index());
+ EXPECT_EQ(cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP, inner_clip->clip_type);
+ EXPECT_EQ(gfx::RectF(200, 200, 700, 700), inner_clip->clip);
+ EXPECT_EQ(outer_clip->id, inner_clip->parent_id);
+}
+
+TEST_F(PaintArtifactCompositorTest, DeeplyNestedClips) {
+ Vector<scoped_refptr<ClipPaintPropertyNode>> clips;
+ for (unsigned i = 1; i <= 10; i++) {
+ clips.push_back(ClipPaintPropertyNode::Create(
+ clips.IsEmpty() ? ClipPaintPropertyNode::Root() : clips.back(),
+ TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(5 * i, 0, 100, 200 - 10 * i)));
+ }
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clips.back(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 200), Color::kWhite);
+ Update(artifact.Build());
+
+ // Check the drawing layer. It's clipped.
+ ASSERT_EQ(1u, ContentLayerCount());
+ const cc::Layer* drawing_layer = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Size(100, 100), drawing_layer->bounds());
+ EXPECT_EQ(gfx::Vector2dF(50, 0), drawing_layer->offset_to_transform_parent());
+ EXPECT_THAT(
+ drawing_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 150, 200), Color::kWhite)));
+ EXPECT_EQ(Translation(50, 0), drawing_layer->ScreenSpaceTransform());
+
+ // Check the clip nodes.
+ const cc::ClipNode* clip_node =
+ GetPropertyTrees().clip_tree.Node(drawing_layer->clip_tree_index());
+ for (auto it = clips.rbegin(); it != clips.rend(); ++it) {
+ const ClipPaintPropertyNode* paint_clip_node = it->get();
+ EXPECT_EQ(cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP, clip_node->clip_type);
+ EXPECT_EQ(paint_clip_node->ClipRect().Rect(), clip_node->clip);
+ clip_node = GetPropertyTrees().clip_tree.Node(clip_node->parent_id);
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, SiblingClips) {
+ scoped_refptr<ClipPaintPropertyNode> common_clip =
+ ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
+ TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(0, 0, 800, 600));
+ scoped_refptr<ClipPaintPropertyNode> clip1 = ClipPaintPropertyNode::Create(
+ common_clip, TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(0, 0, 400, 600));
+ scoped_refptr<ClipPaintPropertyNode> clip2 = ClipPaintPropertyNode::Create(
+ common_clip, TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(400, 0, 400, 600));
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip1,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 640, 480), Color::kWhite);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip2,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 640, 480), Color::kBlack);
+ Update(artifact.Build());
+ ASSERT_EQ(2u, ContentLayerCount());
+
+ const cc::Layer* white_layer = ContentLayerAt(0);
+ EXPECT_THAT(
+ white_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 640, 480), Color::kWhite)));
+ EXPECT_EQ(gfx::Transform(), white_layer->ScreenSpaceTransform());
+ const cc::ClipNode* white_clip =
+ GetPropertyTrees().clip_tree.Node(white_layer->clip_tree_index());
+ EXPECT_EQ(cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP, white_clip->clip_type);
+ ASSERT_EQ(gfx::RectF(0, 0, 400, 600), white_clip->clip);
+
+ const cc::Layer* black_layer = ContentLayerAt(1);
+ // The layer is clipped.
+ EXPECT_EQ(gfx::Size(240, 480), black_layer->bounds());
+ EXPECT_EQ(gfx::Vector2dF(400, 0), black_layer->offset_to_transform_parent());
+ EXPECT_THAT(
+ black_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 240, 480), Color::kBlack)));
+ EXPECT_EQ(Translation(400, 0), black_layer->ScreenSpaceTransform());
+ const cc::ClipNode* black_clip =
+ GetPropertyTrees().clip_tree.Node(black_layer->clip_tree_index());
+ EXPECT_EQ(cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP, black_clip->clip_type);
+ ASSERT_EQ(gfx::RectF(400, 0, 400, 600), black_clip->clip);
+
+ EXPECT_EQ(white_clip->parent_id, black_clip->parent_id);
+ const cc::ClipNode* common_clip_node =
+ GetPropertyTrees().clip_tree.Node(white_clip->parent_id);
+ EXPECT_EQ(cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP,
+ common_clip_node->clip_type);
+ ASSERT_EQ(gfx::RectF(0, 0, 800, 600), common_clip_node->clip);
+}
+
+TEST_F(PaintArtifactCompositorTest, ForeignLayerPassesThrough) {
+ scoped_refptr<cc::Layer> layer = cc::Layer::Create();
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact.Chunk(DefaultPaintChunkProperties())
+ .ForeignLayer(FloatPoint(50, 60), IntSize(400, 300), layer);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+
+ ASSERT_EQ(3u, ContentLayerCount());
+ EXPECT_EQ(layer, ContentLayerAt(1));
+ EXPECT_EQ(gfx::Size(400, 300), layer->bounds());
+ EXPECT_EQ(Translation(50, 60), layer->ScreenSpaceTransform());
+}
+
+TEST_F(PaintArtifactCompositorTest, EffectTreeConversion) {
+ scoped_refptr<EffectPaintPropertyNode> effect1 =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), 0.5, SkBlendMode::kSrcOver,
+ CompositingReason::kAll, CompositorElementId(2));
+ scoped_refptr<EffectPaintPropertyNode> effect2 =
+ EffectPaintPropertyNode::Create(
+ effect1, TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), 0.3, SkBlendMode::kSrcOver,
+ CompositingReason::kAll);
+ scoped_refptr<EffectPaintPropertyNode> effect3 =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), 0.2, SkBlendMode::kSrcOver,
+ CompositingReason::kAll);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect2.get())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect1.get())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect3.get())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ Update(artifact.Build());
+
+ ASSERT_EQ(3u, ContentLayerCount());
+
+ const cc::EffectTree& effect_tree = GetPropertyTrees().effect_tree;
+ // Node #0 reserved for null; #1 for root render surface; #2 for
+ // EffectPaintPropertyNode::root(), plus 3 nodes for those created by
+ // this test.
+ ASSERT_EQ(5u, effect_tree.size());
+
+ const cc::EffectNode& converted_root_effect = *effect_tree.Node(1);
+ EXPECT_EQ(-1, converted_root_effect.parent_id);
+ EXPECT_EQ(CompositorElementIdFromUniqueObjectId(1).ToInternalValue(),
+ converted_root_effect.stable_id);
+
+ const cc::EffectNode& converted_effect1 = *effect_tree.Node(2);
+ EXPECT_EQ(converted_root_effect.id, converted_effect1.parent_id);
+ EXPECT_FLOAT_EQ(0.5, converted_effect1.opacity);
+ EXPECT_EQ(2u, converted_effect1.stable_id);
+
+ const cc::EffectNode& converted_effect2 = *effect_tree.Node(3);
+ EXPECT_EQ(converted_effect1.id, converted_effect2.parent_id);
+ EXPECT_FLOAT_EQ(0.3, converted_effect2.opacity);
+
+ const cc::EffectNode& converted_effect3 = *effect_tree.Node(4);
+ EXPECT_EQ(converted_root_effect.id, converted_effect3.parent_id);
+ EXPECT_FLOAT_EQ(0.2, converted_effect3.opacity);
+
+ EXPECT_EQ(converted_effect2.id, ContentLayerAt(0)->effect_tree_index());
+ EXPECT_EQ(converted_effect1.id, ContentLayerAt(1)->effect_tree_index());
+ EXPECT_EQ(converted_effect3.id, ContentLayerAt(2)->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, OneScrollNode) {
+ CompositorElementId scroll_element_id = ScrollElementId(2);
+ scoped_refptr<ScrollPaintPropertyNode> scroll =
+ ScrollPaintPropertyNode::Create(ScrollPaintPropertyNode::Root(),
+ IntRect(3, 5, 11, 13),
+ IntRect(-3, -5, 27, 31), true, false,
+ kNotScrollingOnMain, scroll_element_id);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(7, 9), FloatPoint3D(), false, 0,
+ CompositingReason::kNone, CompositorElementId(), scroll);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .ScrollHitTest(scroll_translation);
+ artifact
+ .Chunk(scroll_translation, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(-110, 12, 170, 19), Color::kWhite);
+ Update(artifact.Build());
+
+ const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
+ // Node #0 reserved for null; #1 for root render surface.
+ ASSERT_EQ(3u, scroll_tree.size());
+ const cc::ScrollNode& scroll_node = *scroll_tree.Node(2);
+ EXPECT_EQ(gfx::Size(11, 13), scroll_node.container_bounds);
+ EXPECT_EQ(gfx::Size(27, 31), scroll_node.bounds);
+ EXPECT_TRUE(scroll_node.user_scrollable_horizontal);
+ EXPECT_FALSE(scroll_node.user_scrollable_vertical);
+ EXPECT_EQ(1, scroll_node.parent_id);
+ EXPECT_EQ(scroll_element_id, scroll_node.element_id);
+ EXPECT_EQ(scroll_node.id, ElementIdToScrollNodeIndex(scroll_element_id));
+ EXPECT_EQ(scroll_element_id, ScrollHitTestLayerAt(0)->element_id());
+
+ const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree;
+ const cc::TransformNode& transform_node =
+ *transform_tree.Node(scroll_node.transform_id);
+ EXPECT_TRUE(transform_node.local.IsIdentity());
+ EXPECT_EQ(gfx::ScrollOffset(-7, -9), transform_node.scroll_offset);
+ EXPECT_EQ(kNotScrollingOnMain, scroll_node.main_thread_scrolling_reasons);
+
+ auto* layer = ContentLayerAt(0);
+ auto transform_node_index = layer->transform_tree_index();
+ EXPECT_EQ(transform_node_index, transform_node.id);
+ auto scroll_node_index = layer->scroll_tree_index();
+ EXPECT_EQ(scroll_node_index, scroll_node.id);
+
+ // The scrolling contents layer is clipped to the scrolling range.
+ EXPECT_EQ(gfx::Size(27, 14), layer->bounds());
+ EXPECT_EQ(gfx::Vector2dF(-3, 12), layer->offset_to_transform_parent());
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 63, 19), Color::kWhite)));
+
+ auto* scroll_layer = ScrollHitTestLayerAt(0);
+ EXPECT_TRUE(scroll_layer->scrollable());
+ // The scroll layer should be sized to the container bounds.
+ // TODO(pdr): The container bounds will not include scrollbars but the scroll
+ // layer should extend below scrollbars.
+ EXPECT_EQ(gfx::Size(11, 13), scroll_layer->bounds());
+ EXPECT_EQ(gfx::Vector2dF(3, 5), scroll_layer->offset_to_transform_parent());
+ EXPECT_EQ(scroll_layer->scroll_tree_index(), scroll_node.id);
+ EXPECT_EQ(scroll_layer->transform_tree_index(), transform_node.parent_id);
+
+ EXPECT_EQ(0u, ScrollClient().did_scroll_count);
+ scroll_layer->SetScrollOffsetFromImplSide(gfx::ScrollOffset(1, 2));
+ EXPECT_EQ(1u, ScrollClient().did_scroll_count);
+ EXPECT_EQ(gfx::ScrollOffset(1, 2), ScrollClient().last_scroll_offset);
+}
+
+TEST_F(PaintArtifactCompositorTest, TransformUnderScrollNode) {
+ scoped_refptr<ScrollPaintPropertyNode> scroll =
+ ScrollPaintPropertyNode::Create(
+ ScrollPaintPropertyNode::Root(), IntRect(0, 0, 11, 13),
+ IntRect(-3, -5, 27, 31), true, false, kNotScrollingOnMain,
+ CompositorElementId());
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(7, 9), FloatPoint3D(), false, 0,
+ CompositingReason::kNone, CompositorElementId(), scroll);
+
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ scroll_translation, TransformationMatrix(), FloatPoint3D(), false, 0,
+ CompositingReason::k3DTransform);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(scroll_translation, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(-20, 4, 60, 8), Color::kBlack)
+ .Chunk(transform, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(1, -30, 5, 70), Color::kWhite);
+ Update(artifact.Build());
+
+ const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
+ // Node #0 reserved for null; #1 for root render surface.
+ ASSERT_EQ(3u, scroll_tree.size());
+ const cc::ScrollNode& scroll_node = *scroll_tree.Node(2);
+
+ // Both layers should refer to the same scroll tree node.
+ const auto* layer0 = ContentLayerAt(0);
+ const auto* layer1 = ContentLayerAt(1);
+ EXPECT_EQ(scroll_node.id, layer0->scroll_tree_index());
+ EXPECT_EQ(scroll_node.id, layer1->scroll_tree_index());
+
+ // The scrolling layer is clipped to the scrollable range.
+ EXPECT_EQ(gfx::Vector2dF(-3, 4), layer0->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(27, 8), layer0->bounds());
+ EXPECT_THAT(layer0->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 43, 8), Color::kBlack)));
+
+ // The layer under the transform without a scroll node is not clipped.
+ EXPECT_EQ(gfx::Vector2dF(1, -30), layer1->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(5, 70), layer1->bounds());
+ EXPECT_THAT(layer1->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 5, 70), Color::kWhite)));
+
+ const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree;
+ const cc::TransformNode& scroll_transform_node =
+ *transform_tree.Node(scroll_node.transform_id);
+ // The layers have different transform nodes.
+ EXPECT_EQ(scroll_transform_node.id, layer0->transform_tree_index());
+ EXPECT_NE(scroll_transform_node.id, layer1->transform_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, NestedScrollNodes) {
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), 0.5);
+
+ CompositorElementId scroll_element_id_a = ScrollElementId(2);
+ scoped_refptr<ScrollPaintPropertyNode> scroll_a =
+ ScrollPaintPropertyNode::Create(
+ ScrollPaintPropertyNode::Root(), IntRect(0, 0, 2, 3),
+ IntRect(0, 0, 5, 7), false, true,
+ MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
+ scroll_element_id_a);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation_a =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(11, 13), FloatPoint3D(), false, 0,
+ CompositingReason::kLayerForScrollingContents, CompositorElementId(),
+ scroll_a);
+
+ CompositorElementId scroll_element_id_b = ScrollElementId(3);
+ scoped_refptr<ScrollPaintPropertyNode> scroll_b =
+ ScrollPaintPropertyNode::Create(scroll_translation_a->ScrollNode(),
+ IntRect(0, 0, 19, 23),
+ IntRect(0, 0, 29, 31), true, false,
+ kNotScrollingOnMain, scroll_element_id_b);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation_b =
+ TransformPaintPropertyNode::Create(
+ scroll_translation_a, TransformationMatrix().Translate(37, 41),
+ FloatPoint3D(), false, 0, CompositingReason::kNone,
+ CompositorElementId(), scroll_b);
+ TestPaintArtifact artifact;
+ artifact.Chunk(scroll_translation_a, ClipPaintPropertyNode::Root(), effect)
+ .RectDrawing(FloatRect(7, 11, 13, 17), Color::kWhite);
+ artifact
+ .Chunk(scroll_translation_a->Parent(), ClipPaintPropertyNode::Root(),
+ effect)
+ .ScrollHitTest(scroll_translation_a);
+ artifact.Chunk(scroll_translation_b, ClipPaintPropertyNode::Root(), effect)
+ .RectDrawing(FloatRect(1, 2, 3, 5), Color::kWhite);
+ artifact
+ .Chunk(scroll_translation_b->Parent(), ClipPaintPropertyNode::Root(),
+ effect)
+ .ScrollHitTest(scroll_translation_b);
+ Update(artifact.Build());
+
+ const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
+ // Node #0 reserved for null; #1 for root render surface.
+ ASSERT_EQ(4u, scroll_tree.size());
+ const cc::ScrollNode& scroll_node_a = *scroll_tree.Node(2);
+ EXPECT_EQ(gfx::Size(2, 3), scroll_node_a.container_bounds);
+ EXPECT_EQ(gfx::Size(5, 7), scroll_node_a.bounds);
+ EXPECT_FALSE(scroll_node_a.user_scrollable_horizontal);
+ EXPECT_TRUE(scroll_node_a.user_scrollable_vertical);
+ EXPECT_EQ(1, scroll_node_a.parent_id);
+ EXPECT_EQ(scroll_element_id_a, scroll_node_a.element_id);
+ EXPECT_EQ(scroll_node_a.id, ElementIdToScrollNodeIndex(scroll_element_id_a));
+ EXPECT_TRUE(scroll_node_a.main_thread_scrolling_reasons &
+ MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
+ EXPECT_EQ(scroll_element_id_a, ScrollHitTestLayerAt(0)->element_id());
+
+ const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree;
+ const cc::TransformNode& transform_node_a =
+ *transform_tree.Node(scroll_node_a.transform_id);
+ EXPECT_TRUE(transform_node_a.local.IsIdentity());
+ EXPECT_EQ(gfx::ScrollOffset(-11, -13), transform_node_a.scroll_offset);
+
+ const cc::ScrollNode& scroll_node_b = *scroll_tree.Node(3);
+ EXPECT_EQ(gfx::Size(19, 23), scroll_node_b.container_bounds);
+ EXPECT_EQ(gfx::Size(29, 31), scroll_node_b.bounds);
+ EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
+ EXPECT_EQ(scroll_element_id_b, scroll_node_b.element_id);
+ EXPECT_EQ(scroll_node_b.id, ElementIdToScrollNodeIndex(scroll_element_id_b));
+ EXPECT_EQ(scroll_element_id_b, ScrollHitTestLayerAt(1)->element_id());
+
+ const cc::TransformNode& transform_node_b =
+ *transform_tree.Node(scroll_node_b.transform_id);
+ EXPECT_TRUE(transform_node_b.local.IsIdentity());
+ EXPECT_EQ(gfx::ScrollOffset(-37, -41), transform_node_b.scroll_offset);
+}
+
+TEST_F(PaintArtifactCompositorTest, ScrollHitTestLayerOrder) {
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(0, 0, 100, 100));
+
+ CompositorElementId scroll_element_id = ScrollElementId(2);
+ scoped_refptr<ScrollPaintPropertyNode> scroll =
+ ScrollPaintPropertyNode::Create(ScrollPaintPropertyNode::Root(),
+ IntRect(0, 0, 100, 100),
+ IntRect(0, 0, 100, 100), true, false,
+ kNotScrollingOnMain, scroll_element_id);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(7, 9), FloatPoint3D(), false, 0,
+ CompositingReason::kWillChangeCompositingHint, CompositorElementId(),
+ scroll);
+
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ scroll_translation, TransformationMatrix().Translate(5, 5),
+ FloatPoint3D(), false, 0, CompositingReason::k3DTransform);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(scroll_translation, clip, EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ artifact
+ .Chunk(scroll_translation->Parent(), clip,
+ EffectPaintPropertyNode::Root())
+ .ScrollHitTest(scroll_translation);
+ artifact.Chunk(transform, clip, EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 50, 50), Color::kBlack);
+ Update(artifact.Build());
+
+ // The first content layer (background) should not have the scrolling element
+ // id set.
+ EXPECT_EQ(CompositorElementId(), ContentLayerAt(0)->element_id());
+
+ // The scroll layer should be after the first content layer (background).
+ EXPECT_LT(LayerIndex(ContentLayerAt(0)), LayerIndex(ScrollHitTestLayerAt(0)));
+ const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
+ auto* scroll_node =
+ scroll_tree.Node(ScrollHitTestLayerAt(0)->scroll_tree_index());
+ ASSERT_EQ(scroll_element_id, scroll_node->element_id);
+ EXPECT_EQ(scroll_element_id, ScrollHitTestLayerAt(0)->element_id());
+
+ // The second content layer should appear after the first.
+ EXPECT_LT(LayerIndex(ScrollHitTestLayerAt(0)), LayerIndex(ContentLayerAt(1)));
+ EXPECT_EQ(CompositorElementId(), ContentLayerAt(1)->element_id());
+}
+
+TEST_F(PaintArtifactCompositorTest, NestedScrollHitTestLayerOrder) {
+ scoped_refptr<ClipPaintPropertyNode> clip_1 = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(0, 0, 100, 100));
+ CompositorElementId scroll_1_element_id = ScrollElementId(1);
+ scoped_refptr<ScrollPaintPropertyNode> scroll_1 =
+ ScrollPaintPropertyNode::Create(ScrollPaintPropertyNode::Root(),
+ IntRect(0, 0, 100, 100),
+ IntRect(0, 0, 100, 100), true, false,
+ kNotScrollingOnMain, scroll_1_element_id);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation_1 =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(7, 9), FloatPoint3D(), false, 0,
+ CompositingReason::kWillChangeCompositingHint, CompositorElementId(),
+ scroll_1);
+
+ scoped_refptr<ClipPaintPropertyNode> clip_2 = ClipPaintPropertyNode::Create(
+ clip_1, scroll_translation_1, FloatRoundedRect(0, 0, 50, 50));
+ CompositorElementId scroll_2_element_id = ScrollElementId(2);
+ scoped_refptr<ScrollPaintPropertyNode> scroll_2 =
+ ScrollPaintPropertyNode::Create(ScrollPaintPropertyNode::Root(),
+ IntRect(0, 0, 50, 50),
+ IntRect(0, 0, 50, 50), true, false,
+ kNotScrollingOnMain, scroll_2_element_id);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation_2 =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(0, 0), FloatPoint3D(), false, 0,
+ CompositingReason::kWillChangeCompositingHint, CompositorElementId(),
+ scroll_2);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(scroll_translation_1->Parent(), clip_1->Parent(),
+ EffectPaintPropertyNode::Root())
+ .ScrollHitTest(scroll_translation_1);
+ artifact
+ .Chunk(scroll_translation_2->Parent(), clip_2->Parent(),
+ EffectPaintPropertyNode::Root())
+ .ScrollHitTest(scroll_translation_2);
+ artifact.Chunk(scroll_translation_2, clip_2, EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 50, 50), Color::kWhite);
+ Update(artifact.Build());
+
+ // Two scroll layers should be created for each scroll translation node.
+ const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
+ const cc::ClipTree& clip_tree = GetPropertyTrees().clip_tree;
+ auto* scroll_1_node =
+ scroll_tree.Node(ScrollHitTestLayerAt(0)->scroll_tree_index());
+ ASSERT_EQ(scroll_1_element_id, scroll_1_node->element_id);
+ auto* scroll_1_clip_node =
+ clip_tree.Node(ScrollHitTestLayerAt(0)->clip_tree_index());
+ // The scroll is not under clip_1.
+ EXPECT_EQ(gfx::RectF(0, 0, 0, 0), scroll_1_clip_node->clip);
+
+ auto* scroll_2_node =
+ scroll_tree.Node(ScrollHitTestLayerAt(1)->scroll_tree_index());
+ ASSERT_EQ(scroll_2_element_id, scroll_2_node->element_id);
+ auto* scroll_2_clip_node =
+ clip_tree.Node(ScrollHitTestLayerAt(1)->clip_tree_index());
+ // The scroll is not under clip_2 but is under the parent clip, clip_1.
+ EXPECT_EQ(gfx::RectF(0, 0, 100, 100), scroll_2_clip_node->clip);
+
+ // The first layer should be before the second scroll layer.
+ EXPECT_LT(LayerIndex(ScrollHitTestLayerAt(0)),
+ LayerIndex(ScrollHitTestLayerAt(1)));
+
+ // The content layer should be after the second scroll layer.
+ EXPECT_LT(LayerIndex(ScrollHitTestLayerAt(1)), LayerIndex(ContentLayerAt(0)));
+}
+
+// If a scroll node is encountered before its parent, ensure the parent scroll
+// node is correctly created.
+TEST_F(PaintArtifactCompositorTest, AncestorScrollNodes) {
+ CompositorElementId scroll_element_id_a = ScrollElementId(2);
+ scoped_refptr<ScrollPaintPropertyNode> scroll_a =
+ ScrollPaintPropertyNode::Create(ScrollPaintPropertyNode::Root(),
+ IntRect(0, 0, 2, 3), IntRect(0, 0, 5, 7),
+ false, true, kNotScrollingOnMain,
+ scroll_element_id_a);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation_a =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(11, 13), FloatPoint3D(), false, 0,
+ CompositingReason::kLayerForScrollingContents, CompositorElementId(),
+ scroll_a);
+
+ CompositorElementId scroll_element_id_b = ScrollElementId(3);
+ scoped_refptr<ScrollPaintPropertyNode> scroll_b =
+ ScrollPaintPropertyNode::Create(scroll_translation_a->ScrollNode(),
+ IntRect(0, 0, 19, 23),
+ IntRect(0, 0, 29, 31), true, false,
+ kNotScrollingOnMain, scroll_element_id_b);
+ scoped_refptr<TransformPaintPropertyNode> scroll_translation_b =
+ TransformPaintPropertyNode::Create(
+ scroll_translation_a, TransformationMatrix().Translate(37, 41),
+ FloatPoint3D(), false, 0, CompositingReason::kNone,
+ CompositorElementId(), scroll_b);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .ScrollHitTest(scroll_translation_b);
+ artifact
+ .Chunk(scroll_translation_b, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .ScrollHitTest(scroll_translation_a);
+ Update(artifact.Build());
+
+ const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
+ // Node #0 reserved for null; #1 for root render surface.
+ ASSERT_EQ(4u, scroll_tree.size());
+
+ const cc::ScrollNode& scroll_node_a = *scroll_tree.Node(2);
+ EXPECT_EQ(1, scroll_node_a.parent_id);
+ EXPECT_EQ(scroll_element_id_a, scroll_node_a.element_id);
+ EXPECT_EQ(scroll_node_a.id, ElementIdToScrollNodeIndex(scroll_element_id_a));
+ // The second scroll hit test layer should be associated with the first
+ // scroll node (a).
+ EXPECT_EQ(scroll_element_id_a, ScrollHitTestLayerAt(1)->element_id());
+
+ const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree;
+ const cc::TransformNode& transform_node_a =
+ *transform_tree.Node(scroll_node_a.transform_id);
+ EXPECT_TRUE(transform_node_a.local.IsIdentity());
+ EXPECT_EQ(gfx::ScrollOffset(-11, -13), transform_node_a.scroll_offset);
+
+ const cc::ScrollNode& scroll_node_b = *scroll_tree.Node(3);
+ EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
+ EXPECT_EQ(scroll_element_id_b, scroll_node_b.element_id);
+ EXPECT_EQ(scroll_node_b.id, ElementIdToScrollNodeIndex(scroll_element_id_b));
+ // The first scroll hit test layer should be associated with the second scroll
+ // node (b).
+ EXPECT_EQ(scroll_element_id_b, ScrollHitTestLayerAt(0)->element_id());
+
+ const cc::TransformNode& transform_node_b =
+ *transform_tree.Node(scroll_node_b.transform_id);
+ EXPECT_TRUE(transform_node_b.local.IsIdentity());
+ EXPECT_EQ(gfx::ScrollOffset(-37, -41), transform_node_b.scroll_offset);
+}
+
+TEST_F(PaintArtifactCompositorTest, MergeSimpleChunks) {
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(2u, artifact.PaintChunks().size());
+ Update(artifact);
+
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, MergeClip) {
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(10, 20, 50, 60));
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip.get(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 400), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // Clip is applied to this PaintChunk.
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(10, 20, 50, 60), Color::kBlack));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 300, 400), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, Merge2DTransform) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(50, 50), FloatPoint3D(100, 100, 0),
+ false, 0);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // Transform is applied to this PaintChunk.
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(50, 50, 100, 100), Color::kBlack));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, Merge2DTransformDirectAncestor) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(), TransformationMatrix(),
+ FloatPoint3D(), false, 0, CompositingReason::k3DTransform);
+
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ transform.get(), TransformationMatrix().Translate(50, 50),
+ FloatPoint3D(100, 100, 0), false, 0);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ // The second chunk can merge into the first because it has a descendant
+ // state of the first's transform and no direct compositing reason.
+ test_artifact
+ .Chunk(transform2.get(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(2u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // Transform is applied to this PaintChunk.
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(50, 50, 100, 100), Color::kBlack));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, MergeTransformOrigin) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Rotate(45),
+ FloatPoint3D(100, 100, 0), false, 0);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 42, 100, 100), Color::kWhite));
+ // Transform is applied to this PaintChunk.
+ rects_with_color.push_back(RectWithColor(
+ FloatRect(29.2893, 0.578644, 141.421, 141.421), Color::kBlack));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 42, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, MergeOpacity) {
+ float opacity = 2.0 / 255.0;
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), opacity);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // Transform is applied to this PaintChunk.
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100),
+ Color(Color::kBlack).CombineWithAlpha(opacity).Rgb()));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, MergeNested) {
+ // Tests merging of an opacity effect, inside of a clip, inside of a
+ // transform.
+
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(50, 50), FloatPoint3D(100, 100, 0),
+ false, 0);
+
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), transform.get(),
+ FloatRoundedRect(10, 20, 50, 60));
+
+ float opacity = 2.0 / 255.0;
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), transform.get(), clip.get(),
+ kColorFilterNone, CompositorFilterOperations(), opacity,
+ SkBlendMode::kSrcOver);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact.Chunk(transform.get(), clip.get(), effect.get())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // Transform is applied to this PaintChunk.
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(60, 70, 50, 60),
+ Color(Color::kBlack).CombineWithAlpha(opacity).Rgb()));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, ClipPushedUp) {
+ // Tests merging of an element which has a clipapplied to it,
+ // but has an ancestor transform of them. This can happen for fixed-
+ // or absolute-position elements which escape scroll transforms.
+
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(20, 25), FloatPoint3D(100, 100, 0),
+ false, 0);
+
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ transform.get(), TransformationMatrix().Translate(20, 25),
+ FloatPoint3D(100, 100, 0), false, 0);
+
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), transform2.get(),
+ FloatRoundedRect(10, 20, 50, 60));
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip.get(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // The two transforms (combined translation of (40, 50)) are applied here,
+ // before clipping.
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(50, 70, 50, 60), Color(Color::kBlack)));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+// TODO(crbug.com/696842): The effect refuses to "decomposite" because it's in
+// a deeper transform space than its chunk. We should allow decomposite if
+// the two transform nodes share the same direct compositing ancestor.
+TEST_F(PaintArtifactCompositorTest, EffectPushedUp_DISABLED) {
+ // Tests merging of an element which has an effect applied to it,
+ // but has an ancestor transform of them. This can happen for fixed-
+ // or absolute-position elements which escape scroll transforms.
+
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(20, 25), FloatPoint3D(100, 100, 0),
+ false, 0);
+
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ transform.get(), TransformationMatrix().Translate(20, 25),
+ FloatPoint3D(100, 100, 0), false, 0);
+
+ float opacity = 2.0 / 255.0;
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), transform2.get(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), opacity, SkBlendMode::kSrcOver);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 300, 400),
+ Color(Color::kBlack).CombineWithAlpha(opacity).Rgb()));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+// TODO(crbug.com/696842): The effect refuses to "decomposite" because it's in
+// a deeper transform space than its chunk. We should allow decomposite if
+// the two transform nodes share the same direct compositing ancestor.
+TEST_F(PaintArtifactCompositorTest, EffectAndClipPushedUp_DISABLED) {
+ // Tests merging of an element which has an effect applied to it,
+ // but has an ancestor transform of them. This can happen for fixed-
+ // or absolute-position elements which escape scroll transforms.
+
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(20, 25), FloatPoint3D(100, 100, 0),
+ false, 0);
+
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ transform.get(), TransformationMatrix().Translate(20, 25),
+ FloatPoint3D(100, 100, 0), false, 0);
+
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), transform.get(),
+ FloatRoundedRect(10, 20, 50, 60));
+
+ float opacity = 2.0 / 255.0;
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), transform2.get(), clip.get(),
+ kColorFilterNone, CompositorFilterOperations(), opacity,
+ SkBlendMode::kSrcOver);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip.get(), effect.get())
+ .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // The clip is under |transform| but not |transform2|, so only an adjustment
+ // of (20, 25) occurs.
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(30, 45, 50, 60),
+ Color(Color::kBlack).CombineWithAlpha(opacity).Rgb()));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, ClipAndEffectNoTransform) {
+ // Tests merging of an element which has a clip and effect in the root
+ // transform space.
+
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(10, 20, 50, 60));
+
+ float opacity = 2.0 / 255.0;
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ clip.get(), kColorFilterNone, CompositorFilterOperations(), opacity,
+ SkBlendMode::kSrcOver);
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip.get(), effect.get())
+ .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(10, 20, 50, 60),
+ Color(Color::kBlack).CombineWithAlpha(opacity).Rgb()));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, TwoClips) {
+ // Tests merging of an element which has two clips in the root
+ // transform space.
+
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(20, 30, 10, 20));
+
+ scoped_refptr<ClipPaintPropertyNode> clip2 = ClipPaintPropertyNode::Create(
+ clip.get(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(10, 20, 50, 60));
+
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip2.get(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ // The interesction of the two clips is (20, 30, 10, 20).
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(20, 30, 10, 20), Color(Color::kBlack)));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, TwoTransformsClipBetween) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(20, 25), FloatPoint3D(100, 100, 0),
+ false, 0);
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(0, 0, 50, 60));
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ transform.get(), TransformationMatrix().Translate(20, 25),
+ FloatPoint3D(100, 100, 0), false, 0);
+ TestPaintArtifact test_artifact;
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(transform2.get(), clip.get(), EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
+ test_artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ ASSERT_EQ(1u, ContentLayerCount());
+ {
+ Vector<RectWithColor> rects_with_color;
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 100, 100), Color::kWhite));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(40, 50, 10, 10), Color(Color::kBlack)));
+ rects_with_color.push_back(
+ RectWithColor(FloatRect(0, 0, 200, 300), Color::kGray));
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_THAT(layer->GetPicture(),
+ Pointee(DrawsRectangles(rects_with_color)));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, OverlapTransform) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(50, 50), FloatPoint3D(100, 100, 0),
+ false, 0, CompositingReason::k3DTransform);
+
+ TestPaintArtifact test_artifact;
+ test_artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ test_artifact
+ .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ test_artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
+
+ const PaintArtifact& artifact = test_artifact.Build();
+ ASSERT_EQ(3u, artifact.PaintChunks().size());
+ Update(artifact);
+ // The third paint chunk overlaps the second but can't merge due to
+ // incompatible transform. The second paint chunk can't merge into the first
+ // due to a direct compositing reason.
+ ASSERT_EQ(3u, ContentLayerCount());
+}
+
+TEST_F(PaintArtifactCompositorTest, MightOverlap) {
+ PaintChunk paint_chunk = DefaultChunk();
+ paint_chunk.bounds = FloatRect(0, 0, 100, 100);
+ PendingLayer pending_layer(paint_chunk, 0, false);
+
+ PaintChunk paint_chunk2 = DefaultChunk();
+ paint_chunk2.bounds = FloatRect(0, 0, 100, 100);
+
+ {
+ PendingLayer pending_layer2(paint_chunk2, 1, false);
+ EXPECT_TRUE(MightOverlap(pending_layer, pending_layer2));
+ }
+
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(99, 0), FloatPoint3D(100, 100, 0),
+ false);
+ {
+ paint_chunk2.properties.property_tree_state.SetTransform(transform.get());
+ PendingLayer pending_layer2(paint_chunk2, 1, false);
+ EXPECT_TRUE(MightOverlap(pending_layer, pending_layer2));
+ }
+
+ scoped_refptr<TransformPaintPropertyNode> transform2 =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(100, 0), FloatPoint3D(100, 100, 0),
+ false);
+ {
+ paint_chunk2.properties.property_tree_state.SetTransform(transform2.get());
+ PendingLayer pending_layer2(paint_chunk2, 1, false);
+ EXPECT_FALSE(MightOverlap(pending_layer, pending_layer2));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, PendingLayer) {
+ PaintChunk chunk1 = DefaultChunk();
+ chunk1.properties.property_tree_state = PropertyTreeState(
+ TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root());
+ chunk1.properties.backface_hidden = true;
+ chunk1.known_to_be_opaque = true;
+ chunk1.bounds = FloatRect(0, 0, 30, 40);
+
+ PendingLayer pending_layer(chunk1, 0, false);
+
+ EXPECT_TRUE(pending_layer.backface_hidden);
+ EXPECT_EQ(FloatRect(0, 0, 30, 40), pending_layer.bounds);
+ EXPECT_EQ((Vector<size_t>{0}), pending_layer.paint_chunk_indices);
+ EXPECT_EQ(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
+
+ PaintChunk chunk2 = DefaultChunk();
+ chunk2.properties.property_tree_state = chunk1.properties.property_tree_state;
+ chunk2.properties.backface_hidden = true;
+ chunk2.known_to_be_opaque = true;
+ chunk2.bounds = FloatRect(10, 20, 30, 40);
+ pending_layer.Merge(PendingLayer(chunk2, 1, false));
+
+ EXPECT_TRUE(pending_layer.backface_hidden);
+ // Bounds not equal to one PaintChunk.
+ EXPECT_EQ(FloatRect(0, 0, 40, 60), pending_layer.bounds);
+ EXPECT_EQ((Vector<size_t>{0, 1}), pending_layer.paint_chunk_indices);
+ EXPECT_NE(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
+
+ PaintChunk chunk3 = DefaultChunk();
+ chunk3.properties.property_tree_state = chunk1.properties.property_tree_state;
+ chunk3.properties.backface_hidden = true;
+ chunk3.known_to_be_opaque = true;
+ chunk3.bounds = FloatRect(-5, -25, 20, 20);
+ pending_layer.Merge(PendingLayer(chunk3, 2, false));
+
+ EXPECT_TRUE(pending_layer.backface_hidden);
+ EXPECT_EQ(FloatRect(-5, -25, 45, 85), pending_layer.bounds);
+ EXPECT_EQ((Vector<size_t>{0, 1, 2}), pending_layer.paint_chunk_indices);
+ EXPECT_NE(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
+}
+
+TEST_F(PaintArtifactCompositorTest, PendingLayerWithGeometry) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(),
+ TransformationMatrix().Translate(20, 25), FloatPoint3D(100, 100, 0),
+ false, 0);
+
+ PaintChunk chunk1 = DefaultChunk();
+ chunk1.properties.property_tree_state = PropertyTreeState(
+ TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root());
+ chunk1.bounds = FloatRect(0, 0, 30, 40);
+
+ PendingLayer pending_layer(chunk1, 0, false);
+
+ EXPECT_EQ(FloatRect(0, 0, 30, 40), pending_layer.bounds);
+
+ PaintChunk chunk2 = DefaultChunk();
+ chunk2.properties.property_tree_state = chunk1.properties.property_tree_state;
+ chunk2.properties.property_tree_state.SetTransform(transform);
+ chunk2.bounds = FloatRect(0, 0, 50, 60);
+ pending_layer.Merge(PendingLayer(chunk2, 1, false));
+
+ EXPECT_EQ(FloatRect(0, 0, 70, 85), pending_layer.bounds);
+}
+
+// TODO(crbug.com/701991):
+// The test is disabled because opaque rect mapping is not implemented yet.
+TEST_F(PaintArtifactCompositorTest, PendingLayerKnownOpaque_DISABLED) {
+ PaintChunk chunk1 = DefaultChunk();
+ chunk1.properties.property_tree_state = PropertyTreeState(
+ TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root());
+ chunk1.bounds = FloatRect(0, 0, 30, 40);
+ chunk1.known_to_be_opaque = false;
+ PendingLayer pending_layer(chunk1, 0, false);
+
+ EXPECT_TRUE(pending_layer.rect_known_to_be_opaque.IsEmpty());
+
+ PaintChunk chunk2 = DefaultChunk();
+ chunk2.properties.property_tree_state = chunk1.properties.property_tree_state;
+ chunk2.bounds = FloatRect(0, 0, 25, 35);
+ chunk2.known_to_be_opaque = true;
+ pending_layer.Merge(PendingLayer(chunk2, 1, false));
+
+ // Chunk 2 doesn't cover the entire layer, so not opaque.
+ EXPECT_EQ(chunk2.bounds, pending_layer.rect_known_to_be_opaque);
+ EXPECT_NE(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
+
+ PaintChunk chunk3 = DefaultChunk();
+ chunk3.properties.property_tree_state = chunk1.properties.property_tree_state;
+ chunk3.bounds = FloatRect(0, 0, 50, 60);
+ chunk3.known_to_be_opaque = true;
+ pending_layer.Merge(PendingLayer(chunk3, 2, false));
+
+ // Chunk 3 covers the entire layer, so now it's opaque.
+ EXPECT_EQ(chunk3.bounds, pending_layer.bounds);
+ EXPECT_EQ(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
+}
+
+scoped_refptr<EffectPaintPropertyNode> CreateSampleEffectNodeWithElementId() {
+ CompositorElementId expected_compositor_element_id(2);
+ float opacity = 2.0 / 255.0;
+ return EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), opacity, SkBlendMode::kSrcOver,
+ CompositingReason::kActiveOpacityAnimation,
+ expected_compositor_element_id);
+}
+
+scoped_refptr<TransformPaintPropertyNode>
+CreateSampleTransformNodeWithElementId() {
+ CompositorElementId expected_compositor_element_id(3);
+ return TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(), TransformationMatrix().Rotate(90),
+ FloatPoint3D(100, 100, 0), false, 0, CompositingReason::k3DTransform,
+ expected_compositor_element_id);
+}
+
+TEST_F(PaintArtifactCompositorTest, TransformWithElementId) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ CreateSampleTransformNodeWithElementId();
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(transform, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ EXPECT_EQ(2,
+ ElementIdToTransformNodeIndex(transform->GetCompositorElementId()));
+}
+
+TEST_F(PaintArtifactCompositorTest, EffectWithElementId) {
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateSampleEffectNodeWithElementId();
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ EXPECT_EQ(2, ElementIdToEffectNodeIndex(effect->GetCompositorElementId()));
+}
+
+TEST_F(PaintArtifactCompositorTest, CompositedLuminanceMask) {
+ scoped_refptr<EffectPaintPropertyNode> masked =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), 1.0, SkBlendMode::kSrcOver,
+ CompositingReason::kIsolateCompositedDescendants);
+ scoped_refptr<EffectPaintPropertyNode> masking =
+ EffectPaintPropertyNode::Create(
+ masked, TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterLuminanceToAlpha,
+ CompositorFilterOperations(), 1.0, SkBlendMode::kDstIn,
+ CompositingReason::kSquashingDisallowed);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ masked.get())
+ .RectDrawing(FloatRect(100, 100, 200, 200), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ masking.get())
+ .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite);
+ Update(artifact.Build());
+ ASSERT_EQ(2u, ContentLayerCount());
+
+ const cc::Layer* masked_layer = ContentLayerAt(0);
+ EXPECT_THAT(masked_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 200, 200), Color::kGray)));
+ EXPECT_EQ(Translation(100, 100), masked_layer->ScreenSpaceTransform());
+ EXPECT_EQ(gfx::Size(200, 200), masked_layer->bounds());
+ const cc::EffectNode* masked_group =
+ GetPropertyTrees().effect_tree.Node(masked_layer->effect_tree_index());
+ EXPECT_TRUE(masked_group->has_render_surface);
+
+ const cc::Layer* masking_layer = ContentLayerAt(1);
+ EXPECT_THAT(
+ masking_layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 100, 100), Color::kWhite)));
+ EXPECT_EQ(Translation(150, 150), masking_layer->ScreenSpaceTransform());
+ EXPECT_EQ(gfx::Size(100, 100), masking_layer->bounds());
+ const cc::EffectNode* masking_group =
+ GetPropertyTrees().effect_tree.Node(masking_layer->effect_tree_index());
+ EXPECT_TRUE(masking_group->has_render_surface);
+ EXPECT_EQ(masked_group->id, masking_group->parent_id);
+ ASSERT_EQ(1u, masking_group->filters.size());
+ EXPECT_EQ(cc::FilterOperation::REFERENCE,
+ masking_group->filters.at(0).type());
+}
+
+TEST_F(PaintArtifactCompositorTest, UpdateProducesNewSequenceNumber) {
+ // A 90 degree clockwise rotation about (100, 100).
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(), TransformationMatrix().Rotate(90),
+ FloatPoint3D(100, 100, 0), false, 0, CompositingReason::k3DTransform);
+
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(100, 100, 300, 200));
+
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), 0.5);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(transform, clip, effect)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
+ Update(artifact.Build());
+
+ // Two content layers for the differentiated rect drawings and three dummy
+ // layers for each of the transform, clip and effect nodes.
+ EXPECT_EQ(2u, RootLayer()->children().size());
+ int sequence_number = GetPropertyTrees().sequence_number;
+ EXPECT_GT(sequence_number, 0);
+ for (auto layer : RootLayer()->children()) {
+ EXPECT_EQ(sequence_number, layer->property_tree_sequence_number());
+ }
+
+ Update(artifact.Build());
+
+ EXPECT_EQ(2u, RootLayer()->children().size());
+ sequence_number++;
+ EXPECT_EQ(sequence_number, GetPropertyTrees().sequence_number);
+ for (auto layer : RootLayer()->children()) {
+ EXPECT_EQ(sequence_number, layer->property_tree_sequence_number());
+ }
+
+ Update(artifact.Build());
+
+ EXPECT_EQ(2u, RootLayer()->children().size());
+ sequence_number++;
+ EXPECT_EQ(sequence_number, GetPropertyTrees().sequence_number);
+ for (auto layer : RootLayer()->children()) {
+ EXPECT_EQ(sequence_number, layer->property_tree_sequence_number());
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, DecompositeClip) {
+ // A clipped paint chunk that gets merged into a previous layer should
+ // only contribute clipped bounds to the layer bound.
+
+ scoped_refptr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(75, 75, 100, 100));
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), clip.get(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(100, 100, 100, 100), Color::kGray);
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(50.f, 50.f), layer->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(125, 125), layer->bounds());
+}
+
+TEST_F(PaintArtifactCompositorTest, DecompositeEffect) {
+ // An effect node without direct compositing reason and does not need to
+ // group compositing descendants should not be composited and can merge
+ // with other chunks.
+
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), 0.5);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(25.f, 25.f), layer->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(150, 150), layer->bounds());
+ EXPECT_EQ(1, layer->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, DirectlyCompositedEffect) {
+ // An effect node with direct compositing shall be composited.
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), 0.5f, SkBlendMode::kSrcOver,
+ CompositingReason::kAll);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
+ Update(artifact.Build());
+ ASSERT_EQ(3u, ContentLayerCount());
+
+ const cc::Layer* layer1 = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(50.f, 25.f), layer1->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer1->bounds());
+ EXPECT_EQ(1, layer1->effect_tree_index());
+
+ const cc::Layer* layer2 = ContentLayerAt(1);
+ EXPECT_EQ(gfx::Vector2dF(25.f, 75.f), layer2->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer2->bounds());
+ const cc::EffectNode* effect_node =
+ GetPropertyTrees().effect_tree.Node(layer2->effect_tree_index());
+ EXPECT_EQ(1, effect_node->parent_id);
+ EXPECT_EQ(0.5f, effect_node->opacity);
+
+ const cc::Layer* layer3 = ContentLayerAt(2);
+ EXPECT_EQ(gfx::Vector2dF(75.f, 75.f), layer3->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer3->bounds());
+ EXPECT_EQ(1, layer3->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, DecompositeDeepEffect) {
+ // A paint chunk may enter multiple level effects with or without compositing
+ // reasons. This test verifies we still decomposite effects without a direct
+ // reason, but stop at a directly composited effect.
+ scoped_refptr<EffectPaintPropertyNode> effect1 =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), 0.1f);
+ scoped_refptr<EffectPaintPropertyNode> effect2 =
+ EffectPaintPropertyNode::Create(
+ effect1, TransformPaintPropertyNode::Root(),
+ ClipPaintPropertyNode::Root(), kColorFilterNone,
+ CompositorFilterOperations(), 0.2f, SkBlendMode::kSrcOver,
+ CompositingReason::kAll);
+ scoped_refptr<EffectPaintPropertyNode> effect3 =
+ CreateOpacityOnlyEffect(effect2, 0.3f);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect3.get())
+ .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
+ Update(artifact.Build());
+ ASSERT_EQ(3u, ContentLayerCount());
+
+ const cc::Layer* layer1 = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(50.f, 25.f), layer1->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer1->bounds());
+ EXPECT_EQ(1, layer1->effect_tree_index());
+
+ const cc::Layer* layer2 = ContentLayerAt(1);
+ EXPECT_EQ(gfx::Vector2dF(25.f, 75.f), layer2->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer2->bounds());
+ const cc::EffectNode* effect_node2 =
+ GetPropertyTrees().effect_tree.Node(layer2->effect_tree_index());
+ EXPECT_EQ(0.2f, effect_node2->opacity);
+ const cc::EffectNode* effect_node1 =
+ GetPropertyTrees().effect_tree.Node(effect_node2->parent_id);
+ EXPECT_EQ(1, effect_node1->parent_id);
+ EXPECT_EQ(0.1f, effect_node1->opacity);
+
+ const cc::Layer* layer3 = ContentLayerAt(2);
+ EXPECT_EQ(gfx::Vector2dF(75.f, 75.f), layer3->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer3->bounds());
+ EXPECT_EQ(1, layer3->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, IndirectlyCompositedEffect) {
+ // An effect node without direct compositing still needs to be composited
+ // for grouping, if some chunks need to be composited.
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), 0.5f);
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(), TransformationMatrix(),
+ FloatPoint3D(), false, 0, CompositingReason::k3DTransform);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
+ artifact.Chunk(transform.get(), ClipPaintPropertyNode::Root(), effect.get())
+ .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
+ Update(artifact.Build());
+ ASSERT_EQ(3u, ContentLayerCount());
+
+ const cc::Layer* layer1 = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(50.f, 25.f), layer1->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer1->bounds());
+ EXPECT_EQ(1, layer1->effect_tree_index());
+
+ const cc::Layer* layer2 = ContentLayerAt(1);
+ EXPECT_EQ(gfx::Vector2dF(25.f, 75.f), layer2->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer2->bounds());
+ const cc::EffectNode* effect_node =
+ GetPropertyTrees().effect_tree.Node(layer2->effect_tree_index());
+ EXPECT_EQ(1, effect_node->parent_id);
+ EXPECT_EQ(0.5f, effect_node->opacity);
+
+ const cc::Layer* layer3 = ContentLayerAt(2);
+ EXPECT_EQ(gfx::Vector2dF(75.f, 75.f), layer3->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(100, 100), layer3->bounds());
+ EXPECT_EQ(effect_node->id, layer3->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, DecompositedEffectNotMergingDueToOverlap) {
+ // This tests an effect that doesn't need to be composited, but needs
+ // separate backing due to overlap with a previous composited effect.
+ scoped_refptr<EffectPaintPropertyNode> effect1 =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), 0.1f);
+ scoped_refptr<EffectPaintPropertyNode> effect2 =
+ CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), 0.2f);
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ TransformPaintPropertyNode::Create(
+ TransformPaintPropertyNode::Root(), TransformationMatrix(),
+ FloatPoint3D(), false, 0, CompositingReason::k3DTransform);
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 50, 50), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect1.get())
+ .RectDrawing(FloatRect(100, 0, 50, 50), Color::kGray);
+ // This chunk has a transform that must be composited, thus causing effect1
+ // to be composited too.
+ artifact.Chunk(transform.get(), ClipPaintPropertyNode::Root(), effect1.get())
+ .RectDrawing(FloatRect(200, 0, 50, 50), Color::kGray);
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect2.get())
+ .RectDrawing(FloatRect(200, 100, 50, 50), Color::kGray);
+ // This chunk overlaps with the 2nd chunk, but is seemingly safe to merge.
+ // However because effect1 gets composited due to a composited transform,
+ // we can't merge with effect1 nor skip it to merge with the first chunk.
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect2.get())
+ .RectDrawing(FloatRect(100, 0, 50, 50), Color::kGray);
+
+ Update(artifact.Build());
+ ASSERT_EQ(4u, ContentLayerCount());
+
+ const cc::Layer* layer1 = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(0.f, 0.f), layer1->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(50, 50), layer1->bounds());
+ EXPECT_EQ(1, layer1->effect_tree_index());
+
+ const cc::Layer* layer2 = ContentLayerAt(1);
+ EXPECT_EQ(gfx::Vector2dF(100.f, 0.f), layer2->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(50, 50), layer2->bounds());
+ const cc::EffectNode* effect_node =
+ GetPropertyTrees().effect_tree.Node(layer2->effect_tree_index());
+ EXPECT_EQ(1, effect_node->parent_id);
+ EXPECT_EQ(0.1f, effect_node->opacity);
+
+ const cc::Layer* layer3 = ContentLayerAt(2);
+ EXPECT_EQ(gfx::Vector2dF(200.f, 0.f), layer3->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(50, 50), layer3->bounds());
+ EXPECT_EQ(effect_node->id, layer3->effect_tree_index());
+
+ const cc::Layer* layer4 = ContentLayerAt(3);
+ EXPECT_EQ(gfx::Vector2dF(100.f, 0.f), layer4->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(150, 150), layer4->bounds());
+ EXPECT_EQ(1, layer4->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, UpdatePopulatesCompositedElementIds) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ CreateSampleTransformNodeWithElementId();
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateSampleEffectNodeWithElementId();
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(transform, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack)
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
+
+ CompositorElementIdSet composited_element_ids;
+ Update(artifact.Build(), composited_element_ids);
+
+ EXPECT_EQ(2u, composited_element_ids.size());
+ EXPECT_TRUE(
+ composited_element_ids.Contains(transform->GetCompositorElementId()));
+ EXPECT_TRUE(
+ composited_element_ids.Contains(effect->GetCompositorElementId()));
+}
+
+TEST_F(PaintArtifactCompositorTest, SkipChunkWithOpacityZero) {
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0, false, false);
+ ASSERT_EQ(0u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0, true, false);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0, true, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0, false, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, SkipChunkWithTinyOpacity) {
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0003f, false, false);
+ ASSERT_EQ(0u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0003f, true, false);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0003f, true, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0003f, false, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, DontSkipChunkWithMinimumOpacity) {
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0004f, false, false);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0004f, true, false);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0004f, true, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.0004f, false, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, DontSkipChunkWithAboveMinimumOpacity) {
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.3f, false, false);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.3f, true, false);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.3f, true, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+ {
+ TestPaintArtifact artifact;
+ CreateSimpleArtifactWithOpacity(artifact, 0.3f, false, true);
+ ASSERT_EQ(1u, ContentLayerCount());
+ }
+}
+
+scoped_refptr<EffectPaintPropertyNode> CreateEffectWithOpacityAndReason(
+ float opacity,
+ CompositingReasons reason,
+ scoped_refptr<EffectPaintPropertyNode> parent = nullptr) {
+ return EffectPaintPropertyNode::Create(
+ parent ? parent : EffectPaintPropertyNode::Root(),
+ TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ kColorFilterNone, CompositorFilterOperations(), opacity,
+ SkBlendMode::kSrcOver, reason);
+}
+
+TEST_F(PaintArtifactCompositorTest,
+ DontSkipChunkWithTinyOpacityAndDirectCompositingReason) {
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateEffectWithOpacityAndReason(0.0001f, CompositingReason::kCanvas);
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+}
+
+TEST_F(PaintArtifactCompositorTest,
+ SkipChunkWithTinyOpacityAndVisibleChildEffectNode) {
+ scoped_refptr<EffectPaintPropertyNode> tinyEffect =
+ CreateEffectWithOpacityAndReason(0.0001f, CompositingReason::kNone);
+ scoped_refptr<EffectPaintPropertyNode> visibleEffect =
+ CreateEffectWithOpacityAndReason(0.5f, CompositingReason::kNone,
+ tinyEffect);
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ visibleEffect)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+ ASSERT_EQ(0u, ContentLayerCount());
+}
+
+TEST_F(
+ PaintArtifactCompositorTest,
+ DontSkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingParent) {
+ scoped_refptr<EffectPaintPropertyNode> tinyEffect =
+ CreateEffectWithOpacityAndReason(0.0001f, CompositingReason::kCanvas);
+ scoped_refptr<EffectPaintPropertyNode> visibleEffect =
+ CreateEffectWithOpacityAndReason(0.5f, CompositingReason::kNone,
+ tinyEffect);
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ visibleEffect)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+}
+
+TEST_F(PaintArtifactCompositorTest,
+ SkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingChild) {
+ scoped_refptr<EffectPaintPropertyNode> tinyEffect =
+ CreateEffectWithOpacityAndReason(0.0001f, CompositingReason::kNone);
+ scoped_refptr<EffectPaintPropertyNode> visibleEffect =
+ CreateEffectWithOpacityAndReason(0.5f, CompositingReason::kCanvas,
+ tinyEffect);
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ visibleEffect)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+ ASSERT_EQ(0u, ContentLayerCount());
+}
+
+TEST_F(PaintArtifactCompositorTest, UpdateManagesLayerElementIds) {
+ scoped_refptr<TransformPaintPropertyNode> transform =
+ CreateSampleTransformNodeWithElementId();
+ CompositorElementId element_id = transform->GetCompositorElementId();
+
+ {
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(transform, ClipPaintPropertyNode::Root(),
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ ASSERT_TRUE(GetLayerTreeHost().LayerByElementId(element_id));
+ }
+
+ {
+ TestPaintArtifact artifact;
+ ASSERT_TRUE(GetLayerTreeHost().LayerByElementId(element_id));
+ Update(artifact.Build());
+ ASSERT_EQ(0u, ContentLayerCount());
+ ASSERT_FALSE(GetLayerTreeHost().LayerByElementId(element_id));
+ }
+}
+
+TEST_F(PaintArtifactCompositorTest, SynthesizedClipSimple) {
+ // This tests the simplist case that a single layer needs to be clipped
+ // by a single composited rounded clip.
+ FloatSize corner(5, 5);
+ FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
+ corner);
+ scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
+ c0(), t0(), rrect, nullptr, nullptr,
+ CompositingReason::kWillChangeCompositingHint);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ // Expectation in effect stack diagram:
+ // l1
+ // l0 [ mask_effect_0 ]
+ // [ mask_isolation_0 ]
+ // [ e0 ]
+ // One content layer, one clip mask.
+ ASSERT_EQ(2u, RootLayer()->children().size());
+ ASSERT_EQ(1u, ContentLayerCount());
+ ASSERT_EQ(1u, SynthesizedClipLayerCount());
+
+ const cc::Layer* content0 = RootLayer()->children()[0].get();
+ const cc::Layer* clip_mask0 = RootLayer()->children()[1].get();
+
+ constexpr int c0_id = 1;
+ constexpr int e0_id = 1;
+
+ EXPECT_EQ(ContentLayerAt(0), content0);
+ int c1_id = content0->clip_tree_index();
+ const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+ EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+ ASSERT_EQ(c0_id, cc_c1.parent_id);
+ int mask_isolation_0_id = content0->effect_tree_index();
+ const cc::EffectNode& mask_isolation_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
+ ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
+ EXPECT_EQ(c1_id, clip_mask0->clip_tree_index());
+ int mask_effect_0_id = clip_mask0->effect_tree_index();
+ const cc::EffectNode& mask_effect_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_0_id);
+ ASSERT_EQ(mask_isolation_0_id, mask_effect_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_0.blend_mode);
+}
+
+TEST_F(PaintArtifactCompositorTest,
+ SynthesizedClipIndirectlyCompositedClipPath) {
+ // This tests the case that a clip node needs to be synthesized due to
+ // applying clip path to a composited effect.
+ FloatRoundedRect clip_rect(50, 50, 300, 200);
+ scoped_refptr<RefCountedPath> clip_path = base::AdoptRef(new RefCountedPath);
+ scoped_refptr<ClipPaintPropertyNode> c1 =
+ ClipPaintPropertyNode::Create(c0(), t0(), clip_rect, nullptr, clip_path);
+ scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create(
+ e0(), t0(), c1, ColorFilter(), CompositorFilterOperations(), 1,
+ SkBlendMode::kSrcOver, CompositingReason::kWillChangeCompositingHint);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(t0(), c1, e1)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ // Expectation in effect stack diagram:
+ // l0 l1
+ // [ e1 ][ mask_effect_0 ]
+ // [ mask_isolation_0 ]
+ // [ e0 ]
+ // One content layer, one clip mask.
+ ASSERT_EQ(2u, RootLayer()->children().size());
+ ASSERT_EQ(1u, ContentLayerCount());
+ ASSERT_EQ(1u, SynthesizedClipLayerCount());
+
+ const cc::Layer* content0 = RootLayer()->children()[0].get();
+ const cc::Layer* clip_mask0 = RootLayer()->children()[1].get();
+
+ constexpr int c0_id = 1;
+ constexpr int e0_id = 1;
+
+ EXPECT_EQ(ContentLayerAt(0), content0);
+ int c1_id = content0->clip_tree_index();
+ const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+ EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+ ASSERT_EQ(c0_id, cc_c1.parent_id);
+ int e1_id = content0->effect_tree_index();
+ const cc::EffectNode& cc_e1 = *GetPropertyTrees().effect_tree.Node(e1_id);
+ EXPECT_EQ(c1_id, cc_e1.clip_id);
+ int mask_isolation_0_id = cc_e1.parent_id;
+ const cc::EffectNode& mask_isolation_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
+ ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+ EXPECT_EQ(c1_id, mask_isolation_0.clip_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
+ EXPECT_EQ(c1_id, clip_mask0->clip_tree_index());
+ int mask_effect_0_id = clip_mask0->effect_tree_index();
+ const cc::EffectNode& mask_effect_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_0_id);
+ ASSERT_EQ(mask_isolation_0_id, mask_effect_0.parent_id);
+ EXPECT_EQ(c1_id, mask_effect_0.clip_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_0.blend_mode);
+}
+
+TEST_F(PaintArtifactCompositorTest, SynthesizedClipContiguous) {
+ // This tests the case that a two back-to-back composited layers having
+ // the same composited rounded clip can share the synthesized mask.
+ scoped_refptr<TransformPaintPropertyNode> t1 =
+ TransformPaintPropertyNode::Create(
+ t0(), TransformationMatrix(), FloatPoint3D(), false, 0,
+ CompositingReason::kWillChangeCompositingHint);
+
+ FloatSize corner(5, 5);
+ FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
+ corner);
+ scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
+ c0(), t0(), rrect, nullptr, nullptr,
+ CompositingReason::kWillChangeCompositingHint);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t1, c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ // Expectation in effect stack diagram:
+ // l2
+ // l0 l1 [ mask_effect_0 ]
+ // [ mask_isolation_0 ]
+ // [ e0 ]
+ // Two content layers, one clip mask.
+ ASSERT_EQ(3u, RootLayer()->children().size());
+ ASSERT_EQ(2u, ContentLayerCount());
+ ASSERT_EQ(1u, SynthesizedClipLayerCount());
+
+ const cc::Layer* content0 = RootLayer()->children()[0].get();
+ const cc::Layer* content1 = RootLayer()->children()[1].get();
+ const cc::Layer* clip_mask0 = RootLayer()->children()[2].get();
+
+ constexpr int t0_id = 1;
+ constexpr int c0_id = 1;
+ constexpr int e0_id = 1;
+
+ EXPECT_EQ(ContentLayerAt(0), content0);
+ EXPECT_EQ(t0_id, content0->transform_tree_index());
+ int c1_id = content0->clip_tree_index();
+ const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+ EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+ ASSERT_EQ(c0_id, cc_c1.parent_id);
+ int mask_isolation_0_id = content0->effect_tree_index();
+ const cc::EffectNode& mask_isolation_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
+ ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(ContentLayerAt(1), content1);
+ int t1_id = content1->transform_tree_index();
+ const cc::TransformNode& cc_t1 =
+ *GetPropertyTrees().transform_tree.Node(t1_id);
+ ASSERT_EQ(t0_id, cc_t1.parent_id);
+ EXPECT_EQ(c1_id, content1->clip_tree_index());
+ EXPECT_EQ(mask_isolation_0_id, content1->effect_tree_index());
+
+ EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
+ EXPECT_EQ(t0_id, clip_mask0->transform_tree_index());
+ EXPECT_EQ(c1_id, clip_mask0->clip_tree_index());
+ int mask_effect_0_id = clip_mask0->effect_tree_index();
+ const cc::EffectNode& mask_effect_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_0_id);
+ ASSERT_EQ(mask_isolation_0_id, mask_effect_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_0.blend_mode);
+}
+
+TEST_F(PaintArtifactCompositorTest, SynthesizedClipDiscontiguous) {
+ // This tests the case that a two composited layers having the same
+ // composited rounded clip cannot share the synthesized mask if there is
+ // another layer in the middle.
+ scoped_refptr<TransformPaintPropertyNode> t1 =
+ TransformPaintPropertyNode::Create(
+ t0(), TransformationMatrix(), FloatPoint3D(), false, 0,
+ CompositingReason::kWillChangeCompositingHint);
+
+ FloatSize corner(5, 5);
+ FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
+ corner);
+ scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
+ c0(), t0(), rrect, nullptr, nullptr,
+ CompositingReason::kWillChangeCompositingHint);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t1, c0(), e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ // Expectation in effect stack diagram:
+ // l1 l4
+ // l0 [ mask_effect_0 ] l3 [ mask_effect_1 ]
+ // [ mask_isolation_0 ] l2 [ mask_isolation_1 ]
+ // [ e0 ]
+ // Three content layers, two clip mask.
+ ASSERT_EQ(5u, RootLayer()->children().size());
+ ASSERT_EQ(3u, ContentLayerCount());
+ ASSERT_EQ(2u, SynthesizedClipLayerCount());
+
+ const cc::Layer* content0 = RootLayer()->children()[0].get();
+ const cc::Layer* clip_mask0 = RootLayer()->children()[1].get();
+ const cc::Layer* content1 = RootLayer()->children()[2].get();
+ const cc::Layer* content2 = RootLayer()->children()[3].get();
+ const cc::Layer* clip_mask1 = RootLayer()->children()[4].get();
+
+ constexpr int t0_id = 1;
+ constexpr int c0_id = 1;
+ constexpr int e0_id = 1;
+
+ EXPECT_EQ(ContentLayerAt(0), content0);
+ EXPECT_EQ(t0_id, content0->transform_tree_index());
+ int c1_id = content0->clip_tree_index();
+ const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+ EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+ ASSERT_EQ(c0_id, cc_c1.parent_id);
+ int mask_isolation_0_id = content0->effect_tree_index();
+ const cc::EffectNode& mask_isolation_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
+ ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
+ EXPECT_EQ(t0_id, clip_mask0->transform_tree_index());
+ EXPECT_EQ(c1_id, clip_mask0->clip_tree_index());
+ int mask_effect_0_id = clip_mask0->effect_tree_index();
+ const cc::EffectNode& mask_effect_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_0_id);
+ ASSERT_EQ(mask_isolation_0_id, mask_effect_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_0.blend_mode);
+
+ EXPECT_EQ(ContentLayerAt(1), content1);
+ int t1_id = content1->transform_tree_index();
+ const cc::TransformNode& cc_t1 =
+ *GetPropertyTrees().transform_tree.Node(t1_id);
+ ASSERT_EQ(t0_id, cc_t1.parent_id);
+ EXPECT_EQ(c0_id, content1->clip_tree_index());
+ EXPECT_EQ(e0_id, content1->effect_tree_index());
+
+ EXPECT_EQ(ContentLayerAt(2), content2);
+ EXPECT_EQ(t0_id, content2->transform_tree_index());
+ EXPECT_EQ(c1_id, content2->clip_tree_index());
+ int mask_isolation_1_id = content2->effect_tree_index();
+ const cc::EffectNode& mask_isolation_1 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_1_id);
+ EXPECT_NE(mask_isolation_0_id, mask_isolation_1_id);
+ ASSERT_EQ(e0_id, mask_isolation_1.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_1.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(1), clip_mask1);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask1->bounds());
+ EXPECT_EQ(t0_id, clip_mask1->transform_tree_index());
+ EXPECT_EQ(c1_id, clip_mask1->clip_tree_index());
+ int mask_effect_1_id = clip_mask1->effect_tree_index();
+ const cc::EffectNode& mask_effect_1 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_1_id);
+ ASSERT_EQ(mask_isolation_1_id, mask_effect_1.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_1.blend_mode);
+}
+
+TEST_F(PaintArtifactCompositorTest, SynthesizedClipAcrossChildEffect) {
+ // This tests the case that an effect having the same output clip as the
+ // layers before and after it can share the synthesized mask.
+ FloatSize corner(5, 5);
+ FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
+ corner);
+ scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
+ c0(), t0(), rrect, nullptr, nullptr,
+ CompositingReason::kWillChangeCompositingHint);
+
+ scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create(
+ e0(), t0(), c1, ColorFilter(), CompositorFilterOperations(), 1,
+ SkBlendMode::kSrcOver, CompositingReason::kWillChangeCompositingHint);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t0(), c1, e1)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ // Expectation in effect stack diagram:
+ // l1 l3
+ // l0 [ e1 ] l2 [ mask_effect_0 ]
+ // [ mask_isolation_0 ]
+ // [ e0 ]
+ // Three content layers, one clip mask.
+ ASSERT_EQ(4u, RootLayer()->children().size());
+ ASSERT_EQ(3u, ContentLayerCount());
+ ASSERT_EQ(1u, SynthesizedClipLayerCount());
+
+ const cc::Layer* content0 = RootLayer()->children()[0].get();
+ const cc::Layer* content1 = RootLayer()->children()[1].get();
+ const cc::Layer* content2 = RootLayer()->children()[2].get();
+ const cc::Layer* clip_mask0 = RootLayer()->children()[3].get();
+
+ constexpr int c0_id = 1;
+ constexpr int e0_id = 1;
+
+ EXPECT_EQ(ContentLayerAt(0), content0);
+ int c1_id = content0->clip_tree_index();
+ const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+ EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+ ASSERT_EQ(c0_id, cc_c1.parent_id);
+ int mask_isolation_0_id = content0->effect_tree_index();
+ const cc::EffectNode& mask_isolation_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
+ ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(ContentLayerAt(1), content1);
+ EXPECT_EQ(c1_id, content1->clip_tree_index());
+ int e1_id = content1->effect_tree_index();
+ const cc::EffectNode& cc_e1 = *GetPropertyTrees().effect_tree.Node(e1_id);
+ ASSERT_EQ(mask_isolation_0_id, cc_e1.parent_id);
+
+ EXPECT_EQ(ContentLayerAt(2), content2);
+ EXPECT_EQ(c1_id, content2->clip_tree_index());
+ EXPECT_EQ(mask_isolation_0_id, content2->effect_tree_index());
+
+ EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
+ EXPECT_EQ(c1_id, clip_mask0->clip_tree_index());
+ int mask_effect_0_id = clip_mask0->effect_tree_index();
+ const cc::EffectNode& mask_effect_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_0_id);
+ ASSERT_EQ(mask_isolation_0_id, mask_effect_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_0.blend_mode);
+}
+
+TEST_F(PaintArtifactCompositorTest, SynthesizedClipRespectOutputClip) {
+ // This tests the case that a layer cannot share the synthesized mask despite
+ // having the same composited rounded clip if it's enclosed by an effect not
+ // clipped by the common clip.
+ FloatSize corner(5, 5);
+ FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
+ corner);
+ scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
+ c0(), t0(), rrect, nullptr, nullptr,
+ CompositingReason::kWillChangeCompositingHint);
+
+ CompositorFilterOperations non_trivial_filter;
+ non_trivial_filter.AppendBlurFilter(5);
+ scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create(
+ e0(), t0(), c0(), ColorFilter(), non_trivial_filter, 1,
+ SkBlendMode::kSrcOver, CompositingReason::kWillChangeCompositingHint);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t0(), c1, e1)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ // Expectation in effect stack diagram:
+ // l3
+ // l1 l2 [ mask_effect_1 ] l5
+ // l0 [ mask_effect_0 ][ mask_isolation_1 ] l4 [ mask_effect_2 ]
+ // [ mask_isolation_0 ][ e1 ][ mask_isolation_2 ]
+ // [ e0 ]
+ // Three content layers, three clip mask.
+ ASSERT_EQ(6u, RootLayer()->children().size());
+ ASSERT_EQ(3u, ContentLayerCount());
+ ASSERT_EQ(3u, SynthesizedClipLayerCount());
+
+ const cc::Layer* content0 = RootLayer()->children()[0].get();
+ const cc::Layer* clip_mask0 = RootLayer()->children()[1].get();
+ const cc::Layer* content1 = RootLayer()->children()[2].get();
+ const cc::Layer* clip_mask1 = RootLayer()->children()[3].get();
+ const cc::Layer* content2 = RootLayer()->children()[4].get();
+ const cc::Layer* clip_mask2 = RootLayer()->children()[5].get();
+
+ constexpr int c0_id = 1;
+ constexpr int e0_id = 1;
+
+ EXPECT_EQ(ContentLayerAt(0), content0);
+ int c1_id = content0->clip_tree_index();
+ const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+ EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+ ASSERT_EQ(c0_id, cc_c1.parent_id);
+ int mask_isolation_0_id = content0->effect_tree_index();
+ const cc::EffectNode& mask_isolation_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
+ ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
+ EXPECT_EQ(c1_id, clip_mask0->clip_tree_index());
+ int mask_effect_0_id = clip_mask0->effect_tree_index();
+ const cc::EffectNode& mask_effect_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_0_id);
+ ASSERT_EQ(mask_isolation_0_id, mask_effect_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_0.blend_mode);
+
+ EXPECT_EQ(ContentLayerAt(1), content1);
+ EXPECT_EQ(c1_id, content1->clip_tree_index());
+ int mask_isolation_1_id = content1->effect_tree_index();
+ const cc::EffectNode& mask_isolation_1 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_1_id);
+ EXPECT_NE(mask_isolation_0_id, mask_isolation_1_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_1.blend_mode);
+ int e1_id = mask_isolation_1.parent_id;
+ const cc::EffectNode& cc_e1 = *GetPropertyTrees().effect_tree.Node(e1_id);
+ ASSERT_EQ(e0_id, cc_e1.parent_id);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(1), clip_mask1);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask1->bounds());
+ EXPECT_EQ(c1_id, clip_mask1->clip_tree_index());
+ int mask_effect_1_id = clip_mask1->effect_tree_index();
+ const cc::EffectNode& mask_effect_1 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_1_id);
+ ASSERT_EQ(mask_isolation_1_id, mask_effect_1.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_1.blend_mode);
+
+ EXPECT_EQ(ContentLayerAt(2), content2);
+ EXPECT_EQ(c1_id, content2->clip_tree_index());
+ int mask_isolation_2_id = content2->effect_tree_index();
+ const cc::EffectNode& mask_isolation_2 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_2_id);
+ EXPECT_NE(mask_isolation_0_id, mask_isolation_2_id);
+ EXPECT_NE(mask_isolation_1_id, mask_isolation_2_id);
+ ASSERT_EQ(e0_id, mask_isolation_2.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_2.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(2), clip_mask2);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask2->bounds());
+ EXPECT_EQ(c1_id, clip_mask2->clip_tree_index());
+ int mask_effect_2_id = clip_mask2->effect_tree_index();
+ const cc::EffectNode& mask_effect_2 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_2_id);
+ ASSERT_EQ(mask_isolation_2_id, mask_effect_2.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_2.blend_mode);
+}
+
+TEST_F(PaintArtifactCompositorTest, SynthesizedClipDelegateBlending) {
+ // This tests the case that an effect with exotic blending cannot share
+ // the synthesized mask with its siblings because its blending has to be
+ // applied by the outermost mask.
+ FloatSize corner(5, 5);
+ FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
+ corner);
+ scoped_refptr<ClipPaintPropertyNode> c1 = ClipPaintPropertyNode::Create(
+ c0(), t0(), rrect, nullptr, nullptr,
+ CompositingReason::kWillChangeCompositingHint);
+
+ scoped_refptr<EffectPaintPropertyNode> e1 = EffectPaintPropertyNode::Create(
+ e0(), t0(), c1, ColorFilter(), CompositorFilterOperations(), 1,
+ SkBlendMode::kMultiply, CompositingReason::kWillChangeCompositingHint);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t0(), c1, e1)
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ artifact.Chunk(t0(), c1, e0())
+ .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ // Expectation in effect stack diagram:
+ // l1 l2 l3 l5
+ // l0 [ mask_effect_0 ][ e1 ][ mask_effect_1 ] l4 [ mask_effect_2 ]
+ // [ mask_isolation_0 ][ mask_isolation_1 ][ mask_isolation_2 ]
+ // [ e0 ]
+ // Three content layers, three clip mask.
+ ASSERT_EQ(6u, RootLayer()->children().size());
+ ASSERT_EQ(3u, ContentLayerCount());
+ ASSERT_EQ(3u, SynthesizedClipLayerCount());
+
+ const cc::Layer* content0 = RootLayer()->children()[0].get();
+ const cc::Layer* clip_mask0 = RootLayer()->children()[1].get();
+ const cc::Layer* content1 = RootLayer()->children()[2].get();
+ const cc::Layer* clip_mask1 = RootLayer()->children()[3].get();
+ const cc::Layer* content2 = RootLayer()->children()[4].get();
+ const cc::Layer* clip_mask2 = RootLayer()->children()[5].get();
+
+ constexpr int c0_id = 1;
+ constexpr int e0_id = 1;
+
+ EXPECT_EQ(ContentLayerAt(0), content0);
+ int c1_id = content0->clip_tree_index();
+ const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+ EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+ ASSERT_EQ(c0_id, cc_c1.parent_id);
+ int mask_isolation_0_id = content0->effect_tree_index();
+ const cc::EffectNode& mask_isolation_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
+ ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
+ EXPECT_EQ(c1_id, clip_mask0->clip_tree_index());
+ int mask_effect_0_id = clip_mask0->effect_tree_index();
+ const cc::EffectNode& mask_effect_0 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_0_id);
+ ASSERT_EQ(mask_isolation_0_id, mask_effect_0.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_0.blend_mode);
+
+ EXPECT_EQ(ContentLayerAt(1), content1);
+ EXPECT_EQ(c1_id, content1->clip_tree_index());
+ int e1_id = content1->effect_tree_index();
+ const cc::EffectNode& cc_e1 = *GetPropertyTrees().effect_tree.Node(e1_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, cc_e1.blend_mode);
+ int mask_isolation_1_id = cc_e1.parent_id;
+ const cc::EffectNode& mask_isolation_1 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_1_id);
+ EXPECT_NE(mask_isolation_0_id, mask_isolation_1_id);
+ ASSERT_EQ(e0_id, mask_isolation_1.parent_id);
+ EXPECT_EQ(SkBlendMode::kMultiply, mask_isolation_1.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(1), clip_mask1);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask1->bounds());
+ EXPECT_EQ(c1_id, clip_mask1->clip_tree_index());
+ int mask_effect_1_id = clip_mask1->effect_tree_index();
+ const cc::EffectNode& mask_effect_1 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_1_id);
+ ASSERT_EQ(mask_isolation_1_id, mask_effect_1.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_1.blend_mode);
+
+ EXPECT_EQ(ContentLayerAt(2), content2);
+ EXPECT_EQ(c1_id, content2->clip_tree_index());
+ int mask_isolation_2_id = content2->effect_tree_index();
+ const cc::EffectNode& mask_isolation_2 =
+ *GetPropertyTrees().effect_tree.Node(mask_isolation_2_id);
+ EXPECT_NE(mask_isolation_0_id, mask_isolation_2_id);
+ EXPECT_NE(mask_isolation_1_id, mask_isolation_2_id);
+ ASSERT_EQ(e0_id, mask_isolation_2.parent_id);
+ EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+
+ EXPECT_EQ(SynthesizedClipLayerAt(2), clip_mask2);
+ EXPECT_EQ(gfx::Size(300, 200), clip_mask2->bounds());
+ EXPECT_EQ(c1_id, clip_mask2->clip_tree_index());
+ int mask_effect_2_id = clip_mask2->effect_tree_index();
+ const cc::EffectNode& mask_effect_2 =
+ *GetPropertyTrees().effect_tree.Node(mask_effect_2_id);
+ ASSERT_EQ(mask_isolation_2_id, mask_effect_2.parent_id);
+ EXPECT_EQ(SkBlendMode::kDstIn, mask_effect_2.blend_mode);
+}
+
+TEST_F(PaintArtifactCompositorTest, WillBeRemovedFromFrame) {
+ scoped_refptr<EffectPaintPropertyNode> effect =
+ CreateSampleEffectNodeWithElementId();
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect.get())
+ .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
+ Update(artifact.Build());
+
+ ASSERT_EQ(1u, ContentLayerCount());
+ WillBeRemovedFromFrame();
+ // We would need a fake or mock LayerTreeHost to validate that we
+ // unregister all element ids, so just check layer count for now.
+ EXPECT_EQ(0u, ContentLayerCount());
+}
+
+TEST_F(PaintArtifactCompositorTest, ContentsNonOpaque) {
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack);
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ EXPECT_FALSE(ContentLayerAt(0)->contents_opaque());
+}
+
+TEST_F(PaintArtifactCompositorTest, ContentsOpaque) {
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
+ .KnownToBeOpaque();
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ EXPECT_TRUE(ContentLayerAt(0)->contents_opaque());
+}
+
+TEST_F(PaintArtifactCompositorTest, ContentsOpaqueSubpixel) {
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(100.5, 100.5, 200, 200), Color::kBlack)
+ .KnownToBeOpaque();
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ EXPECT_EQ(gfx::Size(201, 201), ContentLayerAt(0)->bounds());
+ EXPECT_FALSE(ContentLayerAt(0)->contents_opaque());
+}
+
+TEST_F(PaintArtifactCompositorTest, ContentsOpaqueUnitedNonOpaque) {
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
+ .KnownToBeOpaque()
+ .Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(200, 200, 200, 200), Color::kBlack)
+ .KnownToBeOpaque();
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ EXPECT_EQ(gfx::Size(300, 300), ContentLayerAt(0)->bounds());
+ EXPECT_FALSE(ContentLayerAt(0)->contents_opaque());
+}
+
+TEST_F(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque1) {
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(100, 100, 300, 300), Color::kBlack)
+ .KnownToBeOpaque()
+ .Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(200, 200, 200, 200), Color::kBlack)
+ .KnownToBeOpaque();
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ EXPECT_EQ(gfx::Size(300, 300), ContentLayerAt(0)->bounds());
+ EXPECT_TRUE(ContentLayerAt(0)->contents_opaque());
+}
+
+TEST_F(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque2) {
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
+ .KnownToBeOpaque()
+ .Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(100, 100, 300, 300), Color::kBlack)
+ .KnownToBeOpaque();
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ EXPECT_EQ(gfx::Size(300, 300), ContentLayerAt(0)->bounds());
+ // TODO(crbug.com/701991): Upgrade GeometryMapper to make this test pass with
+ // the following EXPECT_FALSE changed to EXPECT_TRUE.
+ EXPECT_FALSE(ContentLayerAt(0)->contents_opaque());
+}
+
+TEST_F(PaintArtifactCompositorTest, DecompositeEffectWithNoOutputClip) {
+ // This test verifies effect nodes with no output clip correctly decomposites
+ // if there is no compositing reasons.
+ scoped_refptr<ClipPaintPropertyNode> clip1 = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(75, 75, 100, 100));
+
+ scoped_refptr<EffectPaintPropertyNode> effect1 =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ nullptr, kColorFilterNone, CompositorFilterOperations(), 0.5,
+ SkBlendMode::kSrcOver);
+
+ TestPaintArtifact artifact;
+ artifact.Chunk(DefaultPaintChunkProperties())
+ .RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
+ artifact.Chunk(TransformPaintPropertyNode::Root(), clip1.get(), effect1.get())
+ .RectDrawing(FloatRect(100, 100, 100, 100), Color::kGray);
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(50.f, 50.f), layer->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(125, 125), layer->bounds());
+ EXPECT_EQ(1, layer->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, CompositedEffectWithNoOutputClip) {
+ // This test verifies effect nodes with no output clip but has compositing
+ // reason correctly squash children chunks and assign clip node.
+ scoped_refptr<ClipPaintPropertyNode> clip1 = ClipPaintPropertyNode::Create(
+ ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(75, 75, 100, 100));
+
+ scoped_refptr<EffectPaintPropertyNode> effect1 =
+ EffectPaintPropertyNode::Create(
+ EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
+ nullptr, kColorFilterNone, CompositorFilterOperations(), 0.5,
+ SkBlendMode::kSrcOver, CompositingReason::kAll);
+
+ TestPaintArtifact artifact;
+ artifact
+ .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
+ effect1.get())
+ .RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
+ artifact.Chunk(TransformPaintPropertyNode::Root(), clip1.get(), effect1.get())
+ .RectDrawing(FloatRect(100, 100, 100, 100), Color::kGray);
+ Update(artifact.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+
+ const cc::Layer* layer = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(50.f, 50.f), layer->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(125, 125), layer->bounds());
+ EXPECT_EQ(1, layer->clip_tree_index());
+ EXPECT_EQ(2, layer->effect_tree_index());
+}
+
+TEST_F(PaintArtifactCompositorTest, LayerRasterInvalidationWithClip) {
+ // The layer's painting is initially not clipped.
+ auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
+ TransformPaintPropertyNode::Root(),
+ FloatRoundedRect(10, 20, 300, 400));
+ TestPaintArtifact artifact1;
+ artifact1
+ .Chunk(TransformPaintPropertyNode::Root(), clip,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(FloatRect(50, 50, 200, 200), Color::kBlack);
+ Update(artifact1.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+
+ auto* layer = ContentLayerAt(0);
+ EXPECT_EQ(gfx::Vector2dF(50, 50), layer->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(200, 200), layer->bounds());
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 200, 200), Color::kBlack)));
+
+ // The layer's painting overflows the left, top, right edges of the clip .
+ TestPaintArtifact artifact2;
+ artifact2
+ .Chunk(artifact1.Client(0), TransformPaintPropertyNode::Root(), clip,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(artifact1.Client(1), FloatRect(0, 0, 400, 200),
+ Color::kBlack);
+ layer->ResetNeedsDisplayForTesting();
+ Update(artifact2.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ ASSERT_EQ(layer, ContentLayerAt(0));
+
+ // Invalidate the first chunk because its transform in layer changed.
+ EXPECT_EQ(gfx::Rect(0, 0, 300, 180), layer->update_rect());
+ EXPECT_EQ(gfx::Vector2dF(10, 20), layer->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(300, 180), layer->bounds());
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 390, 180), Color::kBlack)));
+
+ // The layer's painting overflows all edges of the clip.
+ TestPaintArtifact artifact3;
+ artifact3
+ .Chunk(artifact1.Client(0), TransformPaintPropertyNode::Root(), clip,
+ EffectPaintPropertyNode::Root())
+ .RectDrawing(artifact1.Client(1), FloatRect(-100, -200, 500, 800),
+ Color::kBlack);
+ layer->ResetNeedsDisplayForTesting();
+ Update(artifact3.Build());
+ ASSERT_EQ(1u, ContentLayerCount());
+ ASSERT_EQ(layer, ContentLayerAt(0));
+
+ // We should not invalidate the layer because the origin didn't change
+ // because of the clip.
+ EXPECT_EQ(gfx::Rect(), layer->update_rect());
+ EXPECT_EQ(gfx::Vector2dF(10, 20), layer->offset_to_transform_parent());
+ EXPECT_EQ(gfx::Size(300, 400), layer->bounds());
+ EXPECT_THAT(
+ layer->GetPicture(),
+ Pointee(DrawsRectangle(FloatRect(0, 0, 390, 580), Color::kBlack)));
+}
+
+} // namespace blink