blob: 374328afd1f60d099b2391d7ce874f5c88afc96d [file] [log] [blame]
// 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/memory/ptr_util.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "cc/input/main_thread_scrolling_reason.h"
#include "cc/layers/layer.h"
#include "cc/test/fake_impl_task_runner_provider.h"
#include "cc/test/fake_layer_tree_frame_sink.h"
#include "cc/test/fake_layer_tree_host_client.h"
#include "cc/test/fake_layer_tree_host_impl.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/renderer/platform/graphics/paint/effect_paint_property_node.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.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/layer_tree_host_embedder.h"
#include "third_party/blink/renderer/platform/testing/paint_property_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.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/wtf/allocator.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
using testing::Pointee;
PaintChunk::Id DefaultId() {
DEFINE_STATIC_LOCAL(FakeDisplayItemClient, fake_client, ());
return PaintChunk::Id(fake_client, DisplayItem::kDrawingFirst);
}
PaintChunk DefaultChunk() {
return PaintChunk(0, 1, DefaultId(), PropertyTreeState::Root());
}
gfx::Transform Translation(SkMScalar x, SkMScalar y) {
gfx::Transform transform;
transform.Translate(x, y);
return transform;
}
void SetTransform(PaintChunk& chunk,
const TransformPaintPropertyNode& transform) {
auto properties = chunk.properties.GetPropertyTreeState();
properties.SetTransform(transform);
chunk.properties = RefCountedPropertyTreeState(properties);
}
class FakeScrollClient {
DISALLOW_NEW();
public:
FakeScrollClient() : did_scroll_count(0) {}
void DidScroll(const gfx::ScrollOffset& offset, const CompositorElementId&) {
did_scroll_count++;
last_scroll_offset = offset;
}
gfx::ScrollOffset last_scroll_offset;
unsigned did_scroll_count;
};
class PaintArtifactCompositorTest : public testing::Test,
public PaintTestConfigurations {
protected:
PaintArtifactCompositorTest()
: task_runner_(base::MakeRefCounted<base::TestSimpleTaskRunner>()),
task_runner_handle_(task_runner_) {}
void SetUp() override {
// Delay constructing the compositor until after the feature is set.
paint_artifact_compositor_ =
std::make_unique<PaintArtifactCompositor>(WTF::BindRepeating(
&FakeScrollClient::DidScroll, WTF::Unretained(&scroll_client_)));
paint_artifact_compositor_->EnableExtraDataForTesting();
// Uses a LayerTreeHostClient that will make a LayerTreeFrameSink to allow
// the compositor to run and submit frames.
layer_tree_ = std::make_unique<LayerTreeHostEmbedder>(
&layer_tree_host_client_,
/*single_thread_client=*/nullptr);
layer_tree_host_client_.SetLayerTreeHost(layer_tree_->layer_tree_host());
layer_tree_->layer_tree_host()->SetRootLayer(
paint_artifact_compositor_->RootLayer());
}
void TearDown() override {
// Make sure we remove all child layers to satisfy destructor
// child layer element id DCHECK.
WillBeRemovedFromFrame();
}
cc::PropertyTrees& GetPropertyTrees() {
return *layer_tree_->layer_tree_host()->property_trees();
}
const cc::TransformNode& GetTransformNode(const cc::Layer* layer) {
auto* property_trees = layer_tree_->layer_tree_host()->property_trees();
return *property_trees->transform_tree.Node(layer->transform_tree_index());
}
const cc::LayerTreeHost& GetLayerTreeHost() {
return *layer_tree_->layer_tree_host();
}
int ElementIdToEffectNodeIndex(CompositorElementId element_id) {
auto* property_trees = layer_tree_->layer_tree_host()->property_trees();
return property_trees->element_id_to_effect_node_index[element_id];
}
int ElementIdToTransformNodeIndex(CompositorElementId element_id) {
auto* property_trees = layer_tree_->layer_tree_host()->property_trees();
return property_trees->element_id_to_transform_node_index[element_id];
}
int ElementIdToScrollNodeIndex(CompositorElementId element_id) {
auto* property_trees = layer_tree_->layer_tree_host()->property_trees();
return property_trees->element_id_to_scroll_node_index[element_id];
}
using ViewportProperties = PaintArtifactCompositor::ViewportProperties;
using Settings = PaintArtifactCompositor::Settings;
void Update(
scoped_refptr<const PaintArtifact> artifact,
const ViewportProperties& viewport_properties = ViewportProperties(),
const Settings& settings = Settings()) {
paint_artifact_compositor_->SetNeedsUpdate();
paint_artifact_compositor_->Update(artifact, viewport_properties, settings);
layer_tree_->layer_tree_host()->LayoutAndUpdateLayers();
}
void WillBeRemovedFromFrame() {
paint_artifact_compositor_->WillBeRemovedFromFrame();
}
cc::Layer* RootLayer() { return paint_artifact_compositor_->RootLayer(); }
// CompositeAfterPaint creates scroll hit test display items (which create
// scroll hit test layers in PaintArtifactCompositor) whereas in
// BlinkGenPropertyTrees, scrollable foreign layers are created in
// ScrollingCoordinator and passed to PaintArtifactCompositor. This function
// is used to create a chunk representing the scrollable layer in either of
// these modes.
void CreateScrollableChunk(TestPaintArtifact& artifact,
const TransformPaintPropertyNode& scroll_offset,
const ClipPaintPropertyNode& clip,
const EffectPaintPropertyNode& effect) {
if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
// Create a foreign layer for scrolling, roughly matching the layer
// created by ScrollingCoordinator.
const auto* scroll_node = scroll_offset.ScrollNode();
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
auto rect = scroll_node->ContainerRect();
layer->SetScrollable(gfx::Size(rect.Size()));
layer->SetBounds(gfx::Size(rect.Size()));
layer->SetElementId(scroll_node->GetCompositorElementId());
layer->set_did_scroll_callback(
paint_artifact_compositor_->scroll_callback_);
artifact.Chunk(scroll_offset, clip, effect)
.ForeignLayer(layer, FloatPoint(rect.Location()));
return;
}
// Scroll hit test layers are marked as scrollable for hit testing but are
// in the unscrolled transform space (scroll offset's parent).
artifact.Chunk(*scroll_offset.Parent(), clip, effect)
.ScrollHitTest(scroll_offset);
}
// Returns the |num|th scrollable layer. In CompositeAfterPaint, this will be
// a scroll hit test layer, whereas in BlinkGenPropertyTrees this will be a
// content layer.
cc::Layer* ScrollableLayerAt(size_t num) {
if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
for (size_t content_layer_index = 0;
content_layer_index < ContentLayerCount(); content_layer_index++) {
auto* content_layer = ContentLayerAt(content_layer_index);
if (content_layer->scrollable()) {
if (num == 0)
return content_layer;
num--;
}
}
return nullptr;
}
return paint_artifact_compositor_->GetExtraDataForTesting()
->scroll_hit_test_layers[num]
.get();
}
// Returns the |num|th non-scrollable layer. In CompositeAfterPaint, content
// layers are not scrollable so this is the |num|th content layer. In
// BlinkGenPropertyTrees, content layers are scrollable and non-scrollable, so
// this will return the |num|th content layer that is not scrollable.
cc::Layer* NonScrollableLayerAt(size_t num) {
for (size_t content_layer_index = 0;
content_layer_index < ContentLayerCount(); content_layer_index++) {
auto* content_layer = ContentLayerAt(content_layer_index);
if (!content_layer->scrollable()) {
if (num == 0)
return content_layer;
num--;
}
}
return nullptr;
}
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();
}
// 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().RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
}
void UpdateWithArtifactWithOpacity(float opacity,
bool include_preceding_chunk,
bool include_subsequent_chunk) {
TestPaintArtifact artifact;
if (include_preceding_chunk)
AddSimpleRectChunk(artifact);
auto effect = CreateOpacityEffect(e0(), opacity);
artifact.Chunk(t0(), c0(), *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_;
cc::FakeLayerTreeHostClient layer_tree_host_client_;
std::unique_ptr<LayerTreeHostEmbedder> layer_tree_;
};
INSTANTIATE_LAYER_LIST_TEST_SUITE_P(PaintArtifactCompositorTest);
const auto kNotScrollingOnMain =
cc::MainThreadScrollingReason::kNotScrollingOnMain;
TEST_P(PaintArtifactCompositorTest, EmptyPaintArtifact) {
Update(PaintArtifact::Empty());
EXPECT_TRUE(RootLayer()->children().empty());
}
TEST_P(PaintArtifactCompositorTest, OneChunkWithAnOffset) {
TestPaintArtifact artifact;
artifact.Chunk().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_P(PaintArtifactCompositorTest, OneTransform) {
// A 90 degree clockwise rotation about (100, 100).
auto transform = CreateTransform(t0(), TransformationMatrix().Rotate(90),
FloatPoint3D(100, 100, 0),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
artifact.Chunk(*transform, c0(), e0())
.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_P(PaintArtifactCompositorTest, OneTransformWithAlias) {
// A 90 degree clockwise rotation about (100, 100).
auto real_transform = CreateTransform(t0(), TransformationMatrix().Rotate(90),
FloatPoint3D(100, 100, 0),
CompositingReason::k3DTransform);
auto transform = TransformPaintPropertyNode::CreateAlias(*real_transform);
TestPaintArtifact artifact;
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
artifact.Chunk(*transform, c0(), e0())
.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_P(PaintArtifactCompositorTest, TransformCombining) {
// A translation by (5, 5) within a 2x scale about (10, 10).
auto transform1 =
CreateTransform(t0(), TransformationMatrix().Scale(2),
FloatPoint3D(10, 10, 0), CompositingReason::k3DTransform);
auto transform2 =
CreateTransform(*transform1, TransformationMatrix().Translate(5, 5),
FloatPoint3D(), CompositingReason::kWillChangeTransform);
TestPaintArtifact artifact;
artifact.Chunk(*transform1, c0(), e0())
.RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
artifact.Chunk(*transform2, c0(), e0())
.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_P(PaintArtifactCompositorTest, BackfaceVisibility) {
TransformPaintPropertyNode::State backface_hidden_state;
backface_hidden_state.backface_visibility =
TransformPaintPropertyNode::BackfaceVisibility::kHidden;
auto backface_hidden_transform = TransformPaintPropertyNode::Create(
t0(), std::move(backface_hidden_state));
auto backface_inherited_transform = TransformPaintPropertyNode::Create(
*backface_hidden_transform, TransformPaintPropertyNode::State{});
TransformPaintPropertyNode::State backface_visible_state;
backface_visible_state.backface_visibility =
TransformPaintPropertyNode::BackfaceVisibility::kVisible;
auto backface_visible_transform = TransformPaintPropertyNode::Create(
*backface_hidden_transform, std::move(backface_visible_state));
TestPaintArtifact artifact;
artifact.Chunk(*backface_hidden_transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
artifact.Chunk(*backface_inherited_transform, c0(), e0())
.RectDrawing(FloatRect(100, 100, 100, 100), Color::kBlack);
artifact.Chunk(*backface_visible_transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 300, 200), Color::kDarkGray);
Update(artifact.Build());
ASSERT_EQ(2u, ContentLayerCount());
EXPECT_THAT(
ContentLayerAt(0)->GetPicture(),
Pointee(DrawsRectangles(Vector<RectWithColor>{
RectWithColor(FloatRect(0, 0, 300, 200), Color::kWhite),
RectWithColor(FloatRect(100, 100, 100, 100), Color::kBlack)})));
EXPECT_THAT(
ContentLayerAt(1)->GetPicture(),
Pointee(DrawsRectangle(FloatRect(0, 0, 300, 200), Color::kDarkGray)));
}
TEST_P(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.
auto transform1 = CreateTransform(t0(), TransformationMatrix());
auto transform2 =
CreateTransform(*transform1, TransformationMatrix().Rotate3d(0, 45, 0));
TransformPaintPropertyNode::State transform3_state{
TransformationMatrix().Rotate3d(0, 45, 0)};
transform3_state.flattens_inherited_transform = transform_is_flattened;
auto transform3 = TransformPaintPropertyNode::Create(
*transform2, std::move(transform3_state));
TestPaintArtifact artifact;
artifact.Chunk(*transform3, c0(), e0())
.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_P(PaintArtifactCompositorTest, FlattensInheritedTransformWithAlias) {
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.
auto real_transform1 = CreateTransform(t0(), TransformationMatrix());
auto transform1 = TransformPaintPropertyNode::CreateAlias(*real_transform1);
auto real_transform2 =
CreateTransform(*transform1, TransformationMatrix().Rotate3d(0, 45, 0));
auto transform2 = TransformPaintPropertyNode::CreateAlias(*real_transform2);
TransformPaintPropertyNode::State transform3_state{
TransformationMatrix().Rotate3d(0, 45, 0)};
transform3_state.flattens_inherited_transform = transform_is_flattened;
auto real_transform3 = TransformPaintPropertyNode::Create(
*transform2, std::move(transform3_state));
auto transform3 = TransformPaintPropertyNode::CreateAlias(*real_transform3);
TestPaintArtifact artifact;
artifact.Chunk(*transform3, c0(), e0())
.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_P(PaintArtifactCompositorTest, SortingContextID) {
// Has no 3D rendering context.
auto transform1 = CreateTransform(t0(), TransformationMatrix());
// Establishes a 3D rendering context.
TransformPaintPropertyNode::State transform2_3_state;
transform2_3_state.rendering_context_id = 1;
transform2_3_state.direct_compositing_reasons =
CompositingReason::kWillChangeTransform;
auto transform2 = TransformPaintPropertyNode::Create(
*transform1, std::move(transform2_3_state));
// Extends the 3D rendering context of transform2.
auto transform3 = TransformPaintPropertyNode::Create(
*transform2, std::move(transform2_3_state));
// Establishes a 3D rendering context distinct from transform2.
TransformPaintPropertyNode::State transform4_state;
transform4_state.rendering_context_id = 2;
transform4_state.direct_compositing_reasons =
CompositingReason::kWillChangeTransform;
auto transform4 = TransformPaintPropertyNode::Create(
*transform2, std::move(transform4_state));
TestPaintArtifact artifact;
artifact.Chunk(*transform1, c0(), e0())
.RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
artifact.Chunk(*transform2, c0(), e0())
.RectDrawing(FloatRect(0, 0, 300, 200), Color::kLightGray);
artifact.Chunk(*transform3, c0(), e0())
.RectDrawing(FloatRect(0, 0, 300, 200), Color::kDarkGray);
artifact.Chunk(*transform4, c0(), e0())
.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_P(PaintArtifactCompositorTest, OneClip) {
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(100, 100, 300, 200));
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip, e0())
.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_P(PaintArtifactCompositorTest, OneClipWithAlias) {
auto real_clip = CreateClip(c0(), t0(), FloatRoundedRect(100, 100, 300, 200));
auto clip = ClipPaintPropertyNode::CreateAlias(*real_clip);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip, e0())
.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_P(PaintArtifactCompositorTest, NestedClips) {
auto clip1 = CreateClip(c0(), t0(), FloatRoundedRect(100, 100, 700, 700),
CompositingReason::kOverflowScrollingTouch);
auto clip2 = CreateClip(*clip1, t0(), FloatRoundedRect(200, 200, 700, 700),
CompositingReason::kOverflowScrollingTouch);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(FloatRect(300, 350, 100, 100), Color::kWhite);
artifact.Chunk(t0(), *clip2, e0())
.RectDrawing(FloatRect(300, 350, 100, 100), Color::kLightGray);
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(FloatRect(300, 350, 100, 100), Color::kDarkGray);
artifact.Chunk(t0(), *clip2, e0())
.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_P(PaintArtifactCompositorTest, NestedClipsWithAlias) {
auto real_clip1 = CreateClip(c0(), t0(), FloatRoundedRect(100, 100, 700, 700),
CompositingReason::kOverflowScrollingTouch);
auto clip1 = ClipPaintPropertyNode::CreateAlias(*real_clip1);
auto real_clip2 =
CreateClip(*clip1, t0(), FloatRoundedRect(200, 200, 700, 700),
CompositingReason::kOverflowScrollingTouch);
auto clip2 = ClipPaintPropertyNode::CreateAlias(*real_clip2);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(FloatRect(300, 350, 100, 100), Color::kWhite);
artifact.Chunk(t0(), *clip2, e0())
.RectDrawing(FloatRect(300, 350, 100, 100), Color::kLightGray);
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(FloatRect(300, 350, 100, 100), Color::kDarkGray);
artifact.Chunk(t0(), *clip2, e0())
.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_P(PaintArtifactCompositorTest, DeeplyNestedClips) {
Vector<scoped_refptr<ClipPaintPropertyNode>> clips;
for (unsigned i = 1; i <= 10; i++) {
clips.push_back(CreateClip(clips.IsEmpty() ? c0() : *clips.back(), t0(),
FloatRoundedRect(5 * i, 0, 100, 200 - 10 * i)));
}
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clips.back(), e0())
.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_P(PaintArtifactCompositorTest, SiblingClipsWithAlias) {
auto real_common_clip =
CreateClip(c0(), t0(), FloatRoundedRect(0, 0, 800, 600));
auto common_clip = ClipPaintPropertyNode::CreateAlias(*real_common_clip);
auto real_clip1 =
CreateClip(*common_clip, t0(), FloatRoundedRect(0, 0, 400, 600));
auto clip1 = ClipPaintPropertyNode::CreateAlias(*real_clip1);
auto real_clip2 =
CreateClip(*common_clip, t0(), FloatRoundedRect(400, 0, 400, 600));
auto clip2 = ClipPaintPropertyNode::CreateAlias(*real_clip2);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(FloatRect(0, 0, 640, 480), Color::kWhite);
artifact.Chunk(t0(), *clip2, e0())
.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_P(PaintArtifactCompositorTest, ForeignLayerPassesThrough) {
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
layer->SetIsDrawable(true);
layer->SetBounds(gfx::Size(400, 300));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk().ForeignLayer(layer, FloatPoint(50, 60));
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, EffectTreeConversionWithAlias) {
Update(TestPaintArtifact()
.Chunk()
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite)
.Build());
auto root_stable_id = GetPropertyTrees().effect_tree.Node(1)->stable_id;
auto real_effect1 =
CreateOpacityEffect(e0(), t0(), &c0(), 0.5, CompositingReason::kAll);
auto effect1 = EffectPaintPropertyNode::CreateAlias(*real_effect1);
auto real_effect2 =
CreateOpacityEffect(*effect1, 0.3, CompositingReason::kAll);
auto effect2 = EffectPaintPropertyNode::CreateAlias(*real_effect2);
auto real_effect3 = CreateOpacityEffect(e0(), 0.2, CompositingReason::kAll);
auto effect3 = EffectPaintPropertyNode::CreateAlias(*real_effect3);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect2)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk(t0(), c0(), *effect1)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk(t0(), c0(), *effect3)
.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 e0(),
// 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(root_stable_id, 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(real_effect1->GetCompositorElementId().GetInternalValue(),
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());
}
// Returns a ScrollPaintPropertyNode::State with some arbitrary values.
static ScrollPaintPropertyNode::State ScrollState1() {
ScrollPaintPropertyNode::State state;
state.container_rect = IntRect(3, 5, 11, 13);
state.contents_size = IntSize(27, 31);
state.user_scrollable_horizontal = true;
return state;
}
// Returns a ScrollPaintPropertyNode::State with another set arbitrary values.
static ScrollPaintPropertyNode::State ScrollState2() {
ScrollPaintPropertyNode::State state;
state.container_rect = IntRect(0, 0, 19, 23);
state.contents_size = IntSize(29, 31);
state.user_scrollable_horizontal = true;
return state;
}
static scoped_refptr<ScrollPaintPropertyNode> CreateScroll(
const ScrollPaintPropertyNode& parent,
const ScrollPaintPropertyNode::State& state_arg,
MainThreadScrollingReasons main_thread_scrolling_reasons =
cc::MainThreadScrollingReason::kNotScrollingOnMain,
CompositorElementId scroll_element_id = CompositorElementId()) {
ScrollPaintPropertyNode::State state = state_arg;
state.main_thread_scrolling_reasons = main_thread_scrolling_reasons;
state.compositor_element_id = scroll_element_id;
return ScrollPaintPropertyNode::Create(parent, std::move(state));
}
static void CheckCcScrollNode(const ScrollPaintPropertyNode& blink_scroll,
const cc::ScrollNode& cc_scroll) {
EXPECT_EQ(static_cast<gfx::Size>(blink_scroll.ContainerRect().Size()),
cc_scroll.container_bounds);
EXPECT_EQ(static_cast<gfx::Size>(blink_scroll.ContentsSize()),
cc_scroll.bounds);
EXPECT_EQ(blink_scroll.UserScrollableHorizontal(),
cc_scroll.user_scrollable_horizontal);
EXPECT_EQ(blink_scroll.UserScrollableVertical(),
cc_scroll.user_scrollable_vertical);
EXPECT_EQ(blink_scroll.GetCompositorElementId(), cc_scroll.element_id);
EXPECT_EQ(blink_scroll.GetMainThreadScrollingReasons(),
cc_scroll.main_thread_scrolling_reasons);
}
TEST_P(PaintArtifactCompositorTest, OneScrollNode) {
CompositorElementId scroll_element_id = ScrollElementId(2);
auto scroll = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
kNotScrollingOnMain, scroll_element_id);
auto scroll_translation = CreateScrollTranslation(t0(), 7, 9, *scroll);
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, *scroll_translation, c0(), e0());
artifact.Chunk(*scroll_translation, c0(), e0())
.RectDrawing(FloatRect(-110, 12, 170, 19), Color::kWhite);
// Scroll node ElementIds are referenced by scroll animations.
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);
CheckCcScrollNode(*scroll, scroll_node);
EXPECT_EQ(1, scroll_node.parent_id);
EXPECT_EQ(scroll_element_id, ScrollableLayerAt(0)->element_id());
EXPECT_EQ(scroll_node.id, ElementIdToScrollNodeIndex(scroll_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 = NonScrollableLayerAt(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, 19), layer->bounds());
EXPECT_EQ(gfx::Vector2dF(3, 12), layer->offset_to_transform_parent());
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangle(FloatRect(0, 0, 57, 19), Color::kWhite)));
auto* scroll_layer = ScrollableLayerAt(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(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_P(PaintArtifactCompositorTest, TransformUnderScrollNode) {
auto scroll = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1());
auto scroll_translation = CreateScrollTranslation(t0(), 7, 9, *scroll);
auto transform =
CreateTransform(*scroll_translation, TransformationMatrix(),
FloatPoint3D(), CompositingReason::kWillChangeTransform);
TestPaintArtifact artifact;
artifact.Chunk(*scroll_translation, c0(), e0())
.RectDrawing(FloatRect(-20, 4, 60, 8), Color::kBlack)
.Chunk(*transform, c0(), e0())
.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, 5), layer0->offset_to_transform_parent());
EXPECT_EQ(gfx::Size(27, 7), layer0->bounds());
EXPECT_THAT(layer0->GetPicture(),
Pointee(DrawsRectangle(FloatRect(0, 0, 37, 7), 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_P(PaintArtifactCompositorTest, NestedScrollNodes) {
auto effect = CreateOpacityEffect(e0(), 0.5);
CompositorElementId scroll_element_id_a = ScrollElementId(2);
auto scroll_a = CreateScroll(
ScrollPaintPropertyNode::Root(), ScrollState1(),
cc::MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
scroll_element_id_a);
auto scroll_translation_a = CreateScrollTranslation(
t0(), 11, 13, *scroll_a, CompositingReason::kLayerForScrollingContents);
CompositorElementId scroll_element_id_b = ScrollElementId(3);
auto scroll_b = CreateScroll(*scroll_a, ScrollState2(), kNotScrollingOnMain,
scroll_element_id_b);
auto scroll_translation_b =
CreateScrollTranslation(*scroll_translation_a, 37, 41, *scroll_b);
TestPaintArtifact artifact;
artifact.Chunk(*scroll_translation_a, c0(), *effect)
.RectDrawing(FloatRect(7, 11, 13, 17), Color::kWhite);
CreateScrollableChunk(artifact, *scroll_translation_a, c0(), *effect);
artifact.Chunk(*scroll_translation_b, c0(), *effect)
.RectDrawing(FloatRect(1, 2, 3, 5), Color::kWhite);
CreateScrollableChunk(artifact, *scroll_translation_b, c0(), *effect);
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);
CheckCcScrollNode(*scroll_a, scroll_node_a);
EXPECT_EQ(1, scroll_node_a.parent_id);
EXPECT_EQ(scroll_element_id_a, ScrollableLayerAt(0)->element_id());
EXPECT_EQ(scroll_node_a.id, ElementIdToScrollNodeIndex(scroll_element_id_a));
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);
CheckCcScrollNode(*scroll_b, scroll_node_b);
EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
EXPECT_EQ(scroll_element_id_b, ScrollableLayerAt(1)->element_id());
EXPECT_EQ(scroll_node_b.id, ElementIdToScrollNodeIndex(scroll_element_id_b));
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_P(PaintArtifactCompositorTest, ScrollHitTestLayerOrder) {
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(0, 0, 100, 100));
CompositorElementId scroll_element_id = ScrollElementId(2);
auto scroll = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
kNotScrollingOnMain, scroll_element_id);
auto scroll_translation = CreateScrollTranslation(
t0(), 7, 9, *scroll, CompositingReason::kWillChangeTransform);
auto transform = CreateTransform(
*scroll_translation, TransformationMatrix().Translate(5, 5),
FloatPoint3D(), CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk(*scroll_translation, *clip, e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
CreateScrollableChunk(artifact, *scroll_translation, *clip, e0());
artifact.Chunk(*transform, *clip, e0())
.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(), NonScrollableLayerAt(0)->element_id());
// The scroll layer should be after the first content layer (background).
EXPECT_LT(LayerIndex(NonScrollableLayerAt(0)),
LayerIndex(ScrollableLayerAt(0)));
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
auto* scroll_node =
scroll_tree.Node(ScrollableLayerAt(0)->scroll_tree_index());
ASSERT_EQ(scroll_element_id, scroll_node->element_id);
EXPECT_EQ(scroll_element_id, ScrollableLayerAt(0)->element_id());
// The second content layer should appear after the first.
EXPECT_LT(LayerIndex(ScrollableLayerAt(0)),
LayerIndex(NonScrollableLayerAt(1)));
EXPECT_EQ(CompositorElementId(), NonScrollableLayerAt(1)->element_id());
}
TEST_P(PaintArtifactCompositorTest, NestedScrollableLayerOrder) {
auto clip_1 = CreateClip(c0(), t0(), FloatRoundedRect(0, 0, 100, 100));
CompositorElementId scroll_1_element_id = ScrollElementId(1);
auto scroll_1 = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
kNotScrollingOnMain, scroll_1_element_id);
auto scroll_translation_1 = CreateScrollTranslation(
t0(), 7, 9, *scroll_1, CompositingReason::kWillChangeTransform);
auto clip_2 = CreateClip(*clip_1, *scroll_translation_1,
FloatRoundedRect(0, 0, 50, 50));
CompositorElementId scroll_2_element_id = ScrollElementId(2);
auto scroll_2 = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState2(),
kNotScrollingOnMain, scroll_2_element_id);
auto scroll_translation_2 = CreateScrollTranslation(
t0(), 0, 0, *scroll_2, CompositingReason::kWillChangeTransform);
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, *scroll_translation_1, *clip_1->Parent(),
e0());
CreateScrollableChunk(artifact, *scroll_translation_2, *clip_2->Parent(),
e0());
artifact.Chunk(*scroll_translation_2, *clip_2, e0())
.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(ScrollableLayerAt(0)->scroll_tree_index());
ASSERT_EQ(scroll_1_element_id, scroll_1_node->element_id);
auto* scroll_1_clip_node =
clip_tree.Node(ScrollableLayerAt(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(ScrollableLayerAt(1)->scroll_tree_index());
ASSERT_EQ(scroll_2_element_id, scroll_2_node->element_id);
auto* scroll_2_clip_node =
clip_tree.Node(ScrollableLayerAt(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(ScrollableLayerAt(0)), LayerIndex(ScrollableLayerAt(1)));
// The non-scrollable content layer should be after the second scroll layer.
EXPECT_LT(LayerIndex(ScrollableLayerAt(1)),
LayerIndex(NonScrollableLayerAt(0)));
}
// If a scroll node is encountered before its parent, ensure the parent scroll
// node is correctly created.
TEST_P(PaintArtifactCompositorTest, AncestorScrollNodes) {
CompositorElementId scroll_element_id_a = ScrollElementId(2);
auto scroll_a = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
kNotScrollingOnMain, scroll_element_id_a);
auto scroll_translation_a = CreateScrollTranslation(
t0(), 11, 13, *scroll_a, CompositingReason::kLayerForScrollingContents);
CompositorElementId scroll_element_id_b = ScrollElementId(3);
auto scroll_b = CreateScroll(*scroll_a, ScrollState2(), kNotScrollingOnMain,
scroll_element_id_b);
auto scroll_translation_b =
CreateScrollTranslation(*scroll_translation_a, 37, 41, *scroll_b);
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, *scroll_translation_b, c0(), e0());
CreateScrollableChunk(artifact, *scroll_translation_a, c0(), e0());
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 scrollable layer should be associated with the first scroll node
// (a).
EXPECT_EQ(scroll_element_id_a, ScrollableLayerAt(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 scrollable layer should be associated with the second scroll node
// (b).
EXPECT_EQ(scroll_element_id_b, ScrollableLayerAt(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_P(PaintArtifactCompositorTest, MergeSimpleChunks) {
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, MergeClip) {
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(10, 20, 50, 60));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, e0())
.RectDrawing(FloatRect(0, 0, 200, 300), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 300, 400), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, Merge2DTransform) {
auto transform =
CreateTransform(t0(), TransformationMatrix().Translate(50, 50),
FloatPoint3D(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, Merge2DTransformDirectAncestor) {
auto transform = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::k3DTransform);
auto transform2 =
CreateTransform(*transform, TransformationMatrix().Translate(50, 50),
FloatPoint3D(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk(*transform, c0(), e0())
.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, c0(), e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
auto 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_P(PaintArtifactCompositorTest, MergeTransformOrigin) {
auto transform = CreateTransform(t0(), TransformationMatrix().Rotate(45),
FloatPoint3D(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, MergeOpacity) {
float opacity = 2.0 / 255.0;
auto effect = CreateOpacityEffect(e0(), opacity);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, MergeOpacityWithAlias) {
float opacity = 2.0 / 255.0;
auto real_effect = CreateOpacityEffect(e0(), opacity);
auto effect = EffectPaintPropertyNode::CreateAlias(*real_effect);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, MergeNestedWithAlias) {
// Tests merging of an opacity effect, inside of a clip, inside of a
// transform.
auto real_transform =
CreateTransform(t0(), TransformationMatrix().Translate(50, 50),
FloatPoint3D(100, 100, 0));
auto transform = TransformPaintPropertyNode::CreateAlias(*real_transform);
auto real_clip =
CreateClip(c0(), *transform, FloatRoundedRect(10, 20, 50, 60));
auto clip = ClipPaintPropertyNode::CreateAlias(*real_clip);
float opacity = 2.0 / 255.0;
auto real_effect = CreateOpacityEffect(e0(), *transform, clip.get(), opacity);
auto effect = EffectPaintPropertyNode::CreateAlias(*real_effect);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, *clip, *effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(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.
auto transform =
CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
auto transform2 =
CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
auto clip = CreateClip(c0(), *transform2, FloatRoundedRect(10, 20, 50, 60));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, e0())
.RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, DISABLED_EffectPushedUp) {
// 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.
auto transform =
CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
auto transform2 =
CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
float opacity = 2.0 / 255.0;
auto effect = CreateOpacityEffect(e0(), *transform2, &c0(), opacity);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, DISABLED_EffectAndClipPushedUp) {
// 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.
auto transform =
CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
auto transform2 =
CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
auto clip = CreateClip(c0(), *transform, FloatRoundedRect(10, 20, 50, 60));
float opacity = 2.0 / 255.0;
auto effect = CreateOpacityEffect(e0(), *transform2, clip.get(), opacity);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, *effect)
.RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, ClipAndEffectNoTransform) {
// Tests merging of an element which has a clip and effect in the root
// transform space.
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(10, 20, 50, 60));
float opacity = 2.0 / 255.0;
auto effect = CreateOpacityEffect(e0(), t0(), clip.get(), opacity);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, *effect)
.RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, TwoClips) {
// Tests merging of an element which has two clips in the root
// transform space.
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(20, 30, 10, 20));
auto clip2 = CreateClip(*clip, t0(), FloatRoundedRect(10, 20, 50, 60));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip2, e0())
.RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, TwoTransformsClipBetween) {
auto transform =
CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(0, 0, 50, 60));
auto transform2 =
CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform2, *clip, e0())
.RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, OverlapTransform) {
auto transform = CreateTransform(
t0(), TransformationMatrix().Translate(50, 50), FloatPoint3D(100, 100, 0),
CompositingReason::k3DTransform);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
auto 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_P(PaintArtifactCompositorTest, MightOverlap) {
PaintChunk paint_chunk = DefaultChunk();
paint_chunk.bounds = IntRect(0, 0, 100, 100);
PendingLayer pending_layer(paint_chunk, 0, false);
PaintChunk paint_chunk2 = DefaultChunk();
paint_chunk2.bounds = IntRect(0, 0, 100, 100);
{
PendingLayer pending_layer2(paint_chunk2, 1, false);
EXPECT_TRUE(MightOverlap(pending_layer, pending_layer2));
}
auto transform = CreateTransform(
t0(), TransformationMatrix().Translate(99, 0), FloatPoint3D(100, 100, 0));
{
SetTransform(paint_chunk2, *transform);
PendingLayer pending_layer2(paint_chunk2, 1, false);
EXPECT_TRUE(MightOverlap(pending_layer, pending_layer2));
}
auto transform2 =
CreateTransform(t0(), TransformationMatrix().Translate(100, 0),
FloatPoint3D(100, 100, 0));
{
SetTransform(paint_chunk2, *transform2);
PendingLayer pending_layer2(paint_chunk2, 1, false);
EXPECT_FALSE(MightOverlap(pending_layer, pending_layer2));
}
}
TEST_P(PaintArtifactCompositorTest, PendingLayer) {
PaintChunk chunk1 = DefaultChunk();
chunk1.properties = PropertyTreeState::Root();
chunk1.known_to_be_opaque = true;
chunk1.bounds = IntRect(0, 0, 30, 40);
PendingLayer pending_layer(chunk1, 0, false);
EXPECT_EQ(FloatRect(0, 0, 30, 40), pending_layer.bounds);
EXPECT_EQ((Vector<wtf_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 = chunk1.properties;
chunk2.known_to_be_opaque = true;
chunk2.bounds = IntRect(10, 20, 30, 40);
pending_layer.Merge(PendingLayer(chunk2, 1, false));
// Bounds not equal to one PaintChunk.
EXPECT_EQ(FloatRect(0, 0, 40, 60), pending_layer.bounds);
EXPECT_EQ((Vector<wtf_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 = chunk1.properties;
chunk3.known_to_be_opaque = true;
chunk3.bounds = IntRect(-5, -25, 20, 20);
pending_layer.Merge(PendingLayer(chunk3, 2, false));
EXPECT_EQ(FloatRect(-5, -25, 45, 85), pending_layer.bounds);
EXPECT_EQ((Vector<wtf_size_t>{0, 1, 2}), pending_layer.paint_chunk_indices);
EXPECT_NE(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
}
TEST_P(PaintArtifactCompositorTest, PendingLayerWithGeometry) {
auto transform =
CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
FloatPoint3D(100, 100, 0));
PaintChunk chunk1 = DefaultChunk();
chunk1.properties = PropertyTreeState::Root();
chunk1.bounds = IntRect(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 = chunk1.properties;
SetTransform(chunk2, *transform);
chunk2.bounds = IntRect(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_P(PaintArtifactCompositorTest, DISABLED_PendingLayerKnownOpaque) {
PaintChunk chunk1 = DefaultChunk();
chunk1.properties = PropertyTreeState::Root();
chunk1.bounds = IntRect(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 = chunk1.properties;
chunk2.bounds = IntRect(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(FloatRect(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 = chunk1.properties;
chunk3.bounds = IntRect(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(FloatRect(chunk3.bounds), pending_layer.bounds);
EXPECT_EQ(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
}
scoped_refptr<EffectPaintPropertyNode> CreateSampleEffectNodeWithElementId() {
EffectPaintPropertyNode::State state;
state.local_transform_space = &t0();
state.output_clip = &c0();
state.opacity = 2.0 / 255.0;
state.direct_compositing_reasons = CompositingReason::kActiveOpacityAnimation;
state.compositor_element_id = CompositorElementIdFromUniqueObjectId(
2, CompositorElementIdNamespace::kPrimaryEffect);
return EffectPaintPropertyNode::Create(e0(), std::move(state));
}
scoped_refptr<TransformPaintPropertyNode>
CreateSampleTransformNodeWithElementId() {
TransformPaintPropertyNode::State state{TransformationMatrix().Rotate(90)};
state.direct_compositing_reasons = CompositingReason::k3DTransform;
state.compositor_element_id = CompositorElementIdFromUniqueObjectId(
3, CompositorElementIdNamespace::kPrimaryTransform);
return TransformPaintPropertyNode::Create(t0(), std::move(state));
}
TEST_P(PaintArtifactCompositorTest, TransformWithElementId) {
auto transform = CreateSampleTransformNodeWithElementId();
TestPaintArtifact artifact;
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
Update(artifact.Build());
EXPECT_EQ(2,
ElementIdToTransformNodeIndex(transform->GetCompositorElementId()));
}
TEST_P(PaintArtifactCompositorTest, EffectWithElementId) {
auto effect = CreateSampleEffectNodeWithElementId();
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
Update(artifact.Build());
EXPECT_EQ(2, ElementIdToEffectNodeIndex(effect->GetCompositorElementId()));
}
TEST_P(PaintArtifactCompositorTest, EffectWithElementIdWithAlias) {
auto real_effect = CreateSampleEffectNodeWithElementId();
auto effect = EffectPaintPropertyNode::CreateAlias(*real_effect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
Update(artifact.Build());
EXPECT_EQ(2,
ElementIdToEffectNodeIndex(real_effect->GetCompositorElementId()));
}
TEST_P(PaintArtifactCompositorTest, CompositedLuminanceMask) {
auto masked = CreateOpacityEffect(
e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.color_filter = kColorFilterLuminanceToAlpha;
masking_state.blend_mode = SkBlendMode::kDstIn;
masking_state.direct_compositing_reasons =
CompositingReason::kSquashingDisallowed;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(FloatRect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masking)
.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->HasRenderSurface());
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_FALSE(masking_group->HasRenderSurface());
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_P(PaintArtifactCompositorTest, CompositedLuminanceMaskTwoChildren) {
auto masked = CreateOpacityEffect(
e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.color_filter = kColorFilterLuminanceToAlpha;
masking_state.blend_mode = SkBlendMode::kDstIn;
masking_state.direct_compositing_reasons =
CompositingReason::kSquashingDisallowed;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
auto child_of_masked = CreateOpacityEffect(
*masking, 1.0, CompositingReason::kIsolateCompositedDescendants);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(FloatRect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *child_of_masked)
.RectDrawing(FloatRect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(3u, ContentLayerCount());
const cc::Layer* masking_layer = ContentLayerAt(2);
const cc::EffectNode* masking_group =
GetPropertyTrees().effect_tree.Node(masking_layer->effect_tree_index());
// There is a render surface because there are two children.
EXPECT_TRUE(masking_group->HasRenderSurface());
ASSERT_EQ(1u, masking_group->filters.size());
EXPECT_EQ(cc::FilterOperation::REFERENCE,
masking_group->filters.at(0).type());
}
TEST_P(PaintArtifactCompositorTest, CompositedExoticBlendMode) {
auto masked = CreateOpacityEffect(
e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kXor;
masking_state.direct_compositing_reasons =
CompositingReason::kSquashingDisallowed;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(FloatRect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, ContentLayerCount());
const cc::Layer* masking_layer = ContentLayerAt(1);
const cc::EffectNode* masking_group =
GetPropertyTrees().effect_tree.Node(masking_layer->effect_tree_index());
/// This requires a render surface.
EXPECT_TRUE(masking_group->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, UpdateProducesNewSequenceNumber) {
// A 90 degree clockwise rotation about (100, 100).
auto transform = CreateTransform(t0(), TransformationMatrix().Rotate(90),
FloatPoint3D(100, 100, 0),
CompositingReason::k3DTransform);
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(100, 100, 300, 200));
auto effect = CreateOpacityEffect(e0(), 0.5);
TestPaintArtifact test_artifact;
test_artifact.Chunk(*transform, *clip, *effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk().RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
auto artifact = test_artifact.Build();
Update(artifact);
// 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);
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);
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_P(PaintArtifactCompositorTest, DecompositeClip) {
// A clipped paint chunk that gets merged into a previous layer should
// only contribute clipped bounds to the layer bound.
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(75, 75, 100, 100));
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
artifact.Chunk(t0(), *clip, e0())
.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_P(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.
auto effect = CreateOpacityEffect(e0(), 0.5);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
artifact.Chunk().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_P(PaintArtifactCompositorTest, DirectlyCompositedEffect) {
// An effect node with direct compositing shall be composited.
auto effect = CreateOpacityEffect(e0(), 0.5f, CompositingReason::kAll);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
artifact.Chunk().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_P(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.
auto effect1 = CreateOpacityEffect(e0(), 0.1f);
auto effect2 = CreateOpacityEffect(*effect1, 0.2f, CompositingReason::kAll);
auto effect3 = CreateOpacityEffect(*effect2, 0.3f);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect3)
.RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
artifact.Chunk().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_P(PaintArtifactCompositorTest, IndirectlyCompositedEffect) {
// An effect node without direct compositing still needs to be composited
// for grouping, if some chunks need to be composited.
auto effect = CreateOpacityEffect(e0(), 0.5f);
auto transform = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
artifact.Chunk(*transform, c0(), *effect)
.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_P(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.
auto effect1 = CreateOpacityEffect(e0(), 0.1f);
auto effect2 = CreateOpacityEffect(e0(), 0.2f);
auto transform = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(0, 0, 50, 50), Color::kGray);
artifact.Chunk(t0(), c0(), *effect1)
.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, c0(), *effect1)
.RectDrawing(FloatRect(200, 0, 50, 50), Color::kGray);
artifact.Chunk(t0(), c0(), *effect2)
.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(t0(), c0(), *effect2)
.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_P(PaintArtifactCompositorTest, SkipChunkWithOpacityZero) {
UpdateWithArtifactWithOpacity(0, false, false);
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
EXPECT_EQ(0u, ContentLayerCount());
else
EXPECT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
SkipChunkWithOpacityZeroWithPrecedingChunk) {
UpdateWithArtifactWithOpacity(0, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest, SkipChunkWithOpacityZeroSubsequentChunk) {
UpdateWithArtifactWithOpacity(0, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
SkipChunkWithOpacityZeroWithPrecedingAndSubsequentChunk) {
UpdateWithArtifactWithOpacity(0, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest, SkipChunkWithTinyOpacity) {
UpdateWithArtifactWithOpacity(0.0003f, false, false);
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
EXPECT_EQ(0u, ContentLayerCount());
else
EXPECT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
SkipChunkWithTinyOpacityWithPrecedingChunk) {
UpdateWithArtifactWithOpacity(0.0003f, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest, SkipChunkWithTinyOpacitySubsequentChunk) {
UpdateWithArtifactWithOpacity(0.0003f, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
SkipChunkWithTinyOpacityWithPrecedingAndSubsequentChunk) {
UpdateWithArtifactWithOpacity(0.0003f, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest, DontSkipChunkWithMinimumOpacity) {
UpdateWithArtifactWithOpacity(0.0004f, false, false);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
DontSkipChunkWithMinimumOpacityWithPrecedingChunk) {
UpdateWithArtifactWithOpacity(0.0004f, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
DontSkipChunkWithMinimumOpacitySubsequentChunk) {
UpdateWithArtifactWithOpacity(0.0004f, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
DontSkipChunkWithMinimumOpacityWithPrecedingAndSubsequentChunk) {
UpdateWithArtifactWithOpacity(0.0004f, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest, DontSkipChunkWithAboveMinimumOpacity) {
UpdateWithArtifactWithOpacity(0.3f, false, false);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
DontSkipChunkWithAboveMinimumOpacityWithPrecedingChunk) {
UpdateWithArtifactWithOpacity(0.3f, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
DontSkipChunkWithAboveMinimumOpacitySubsequentChunk) {
UpdateWithArtifactWithOpacity(0.3f, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
DontSkipChunkWithAboveMinimumOpacityWithPrecedingAndSubsequentChunk) {
UpdateWithArtifactWithOpacity(0.3f, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
DontSkipChunkWithTinyOpacityAndDirectCompositingReason) {
auto effect = CreateOpacityEffect(e0(), 0.0001f, CompositingReason::kCanvas);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
SkipChunkWithTinyOpacityAndVisibleChildEffectNode) {
auto tiny_effect =
CreateOpacityEffect(e0(), 0.0001f, CompositingReason::kNone);
auto visible_effect =
CreateOpacityEffect(*tiny_effect, 0.5f, CompositingReason::kNone);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *visible_effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
EXPECT_EQ(0u, ContentLayerCount());
else
EXPECT_EQ(1u, ContentLayerCount());
}
TEST_P(
PaintArtifactCompositorTest,
DontSkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingParent) {
auto tiny_effect =
CreateOpacityEffect(e0(), 0.0001f, CompositingReason::kCanvas);
auto visible_effect = CreateOpacityEffect(*tiny_effect, 0.5f);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *visible_effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest,
SkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingChild) {
auto tiny_effect = CreateOpacityEffect(e0(), 0.0001f);
auto visible_effect =
CreateOpacityEffect(*tiny_effect, 0.5f, CompositingReason::kCanvas);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *visible_effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
EXPECT_EQ(0u, ContentLayerCount());
else
EXPECT_EQ(1u, ContentLayerCount());
}
TEST_P(PaintArtifactCompositorTest, UpdateManagesLayerElementIds) {
auto transform = CreateAnimatingTransform(t0());
CompositorElementId element_id = transform->GetCompositorElementId();
{
TestPaintArtifact artifact;
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
ASSERT_TRUE(GetLayerTreeHost().IsElementInPropertyTrees(
element_id, cc::ElementListType::ACTIVE));
}
{
TestPaintArtifact artifact;
ASSERT_TRUE(GetLayerTreeHost().IsElementInPropertyTrees(
element_id, cc::ElementListType::ACTIVE));
Update(artifact.Build());
ASSERT_EQ(0u, ContentLayerCount());
ASSERT_FALSE(GetLayerTreeHost().IsElementInPropertyTrees(
element_id, cc::ElementListType::ACTIVE));
}
}
TEST_P(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);
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
// Expectation in effect stack diagram:
// l0
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer.
ASSERT_EQ(1u, RootLayer()->children().size());
ASSERT_EQ(1u, ContentLayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = RootLayer()->children()[0].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_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
return;
}
// Expectation in effect stack diagram:
// l1
// l0 [ mask_effect_0 ]
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer.
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_TRUE(mask_isolation_0.HasRenderSurface());
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);
// The masks DrawsContent because it has content that it masks which also
// DrawsContent.
EXPECT_TRUE(clip_mask0->DrawsContent());
}
TEST_P(PaintArtifactCompositorTest,
SynthesizedClipSimpleFastBorderNotSupported1) {
// This tests the simplist case that a single layer needs to be clipped
// by a single composited rounded clip. Because the radius is too big,
// it falls back to a mask layer.
FloatSize corner(200, 200);
FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
corner);
auto c1 = CreateClip(c0(), t0(), rrect);
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.
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_TRUE(mask_isolation_0.HasRenderSurface());
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);
// The masks DrawsContent because it has content that it masks which also
// DrawsContent.
EXPECT_TRUE(clip_mask0->DrawsContent());
}
TEST_P(PaintArtifactCompositorTest,
SynthesizedClipSimpleFastBorderNotSupported2) {
// This tests the simplist case that a single layer needs to be clipped
// by a single composited rounded clip. Because the radius is unsymmetric,
// it falls back to a mask layer.
FloatSize corner(30, 40);
FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
corner);
auto c1 = CreateClip(c0(), t0(), rrect);
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.
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_TRUE(mask_isolation_0.HasRenderSurface());
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);
// The masks DrawsContent because it has content that it masks which also
// DrawsContent.
EXPECT_TRUE(clip_mask0->DrawsContent());
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClipNested) {
// This tests the simplist case that a single layer needs to be clipped
// by a single composited rounded clip.
if (!RuntimeEnabledFeatures::FastBorderRadiusEnabled())
return;
FloatSize corner(5, 5);
FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
corner);
auto c1 = CreateClip(c0(), t0(), rrect);
auto c2 = CreateClip(*c1, t0(), rrect);
auto c3 = CreateClip(*c2, t0(), rrect);
auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::kWillChangeTransform);
CompositorFilterOperations filter_operations;
filter_operations.AppendBlurFilter(5);
auto filter = CreateFilterEffect(e0(), t0(), c1.get(), filter_operations);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, *filter)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*t1, *c3, *filter)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// l0
// [ mask_isolation_2 ]
// l1 [ mask_isolation_1 ]
// [ filter ]
// [ mask_isolation_0 ]
// [ e0 ]
// Two content layers.
///
// mask_isolation_1 will have a render surface. mask_isolation_2 will not
// because non-leaf synthetic rounded clips must have a render surface.
// mask_isolation_0 will not because it is a leaf synthetic rounded clip
// in the render surface created by the filter.
ASSERT_EQ(2u, RootLayer()->children().size());
ASSERT_EQ(2u, ContentLayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(3u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_FALSE(SynthesizedClipLayerAt(1));
EXPECT_FALSE(SynthesizedClipLayerAt(2));
const cc::Layer* content0 = RootLayer()->children()[0].get();
EXPECT_EQ(ContentLayerAt(0), content0);
const cc::Layer* content1 = RootLayer()->children()[1].get();
EXPECT_EQ(ContentLayerAt(1), content1);
constexpr int c0_id = 1;
constexpr int c1_id = 2;
constexpr int e0_id = 1;
constexpr int e1_id = 2;
int c3_id = content1->clip_tree_index();
const cc::ClipNode& cc_c3 = *GetPropertyTrees().clip_tree.Node(c3_id);
EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c3.clip);
const cc::ClipNode& cc_c2 =
*GetPropertyTrees().clip_tree.Node(cc_c3.parent_id);
EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c2.clip);
ASSERT_EQ(c1_id, cc_c2.parent_id);
const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
EXPECT_EQ(c1_id, content0->clip_tree_index());
EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
ASSERT_EQ(c0_id, cc_c1.parent_id);
int mask_isolation_2_id = content1->effect_tree_index();
const cc::EffectNode& mask_isolation_2 =
*GetPropertyTrees().effect_tree.Node(mask_isolation_2_id);
const cc::EffectNode& mask_isolation_1 =
*GetPropertyTrees().effect_tree.Node(mask_isolation_2.parent_id);
const cc::EffectNode& cc_filter =
*GetPropertyTrees().effect_tree.Node(mask_isolation_1.parent_id);
const cc::EffectNode& mask_isolation_0 =
*GetPropertyTrees().effect_tree.Node(cc_filter.parent_id);
ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
ASSERT_EQ(e1_id, cc_filter.parent_id);
EXPECT_EQ(cc_filter.id, content0->effect_tree_index());
EXPECT_EQ(SkBlendMode::kSrcOver, cc_filter.blend_mode);
EXPECT_FALSE(cc_filter.is_fast_rounded_corner);
EXPECT_TRUE(cc_filter.HasRenderSurface());
EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_1.blend_mode);
EXPECT_TRUE(mask_isolation_1.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_1.rounded_corner_bounds);
EXPECT_TRUE(mask_isolation_1.HasRenderSurface());
EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_2.blend_mode);
EXPECT_TRUE(mask_isolation_2.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_2.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_2.HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClipIsNotDrawable) {
// 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);
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(FloatRect(0, 0, 0, 0), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// l1
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer, no clip mask (because layer doesn't draw content).
ASSERT_EQ(1u, RootLayer()->children().size());
ASSERT_EQ(1u, ContentLayerCount());
ASSERT_EQ(1u, SynthesizedClipLayerCount());
// There is a synthesized clip", but it has no layer backing.
ASSERT_EQ(nullptr, SynthesizedClipLayerAt(0));
const cc::Layer* content0 = RootLayer()->children()[0].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);
}
TEST_P(PaintArtifactCompositorTest, ReuseSyntheticClip) {
// 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);
auto c1 = CreateClip(c0(), t0(), rrect);
auto c2 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(FloatRect(0, 0, 0, 0), Color::kBlack);
Update(artifact.Build());
const cc::Layer* content0 = RootLayer()->children()[0].get();
uint64_t old_stable_id = GetPropertyTrees()
.effect_tree.Node(content0->effect_tree_index())
->stable_id;
TestPaintArtifact repeated_artifact;
repeated_artifact.Chunk(t0(), *c1, e0())
.RectDrawing(FloatRect(0, 0, 0, 0), Color::kBlack);
Update(repeated_artifact.Build());
const cc::Layer* content1 = RootLayer()->children()[0].get();
// Check that stable ids are reused across updates.
EXPECT_EQ(GetPropertyTrees()
.effect_tree.Node(content1->effect_tree_index())
->stable_id,
old_stable_id);
TestPaintArtifact changed_artifact;
changed_artifact.Chunk(t0(), *c2, e0())
.RectDrawing(FloatRect(0, 0, 0, 0), Color::kBlack);
Update(changed_artifact.Build());
const cc::Layer* content2 = RootLayer()->children()[0].get();
// The new artifact changed the clip node to c2, so the synthetic clip should
// not be reused.
EXPECT_NE(GetPropertyTrees()
.effect_tree.Node(content2->effect_tree_index())
->stable_id,
old_stable_id);
}
TEST_P(PaintArtifactCompositorTest,
SynthesizedClipIndirectlyCompositedClipPath) {
// This tests the case that a clip node needs to be synthesized due to
// applying clip path to a composited effect.
auto c1 = CreateClipPathClip(c0(), t0(), FloatRoundedRect(50, 50, 300, 200));
auto e1 = CreateOpacityEffect(e0(), t0(), c1.get(), 1,
CompositingReason::kWillChangeOpacity);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, *e1)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
// Expectation in effect stack diagram:
// l0 l1
// [ e1 ]
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer, one clip mask.
ASSERT_EQ(1u, RootLayer()->children().size());
ASSERT_EQ(1u, ContentLayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = RootLayer()->children()[0].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_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 0),
mask_isolation_0.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
return;
}
// 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_P(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.
auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::kWillChangeTransform);
FloatSize corner(5, 5);
FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
corner);
auto c1 = CreateClip(c0(), t0(), rrect);
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());
if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
// Expectation in effect stack diagram:
// l2
// l0 l1
// [ mask_isolation_0 ]
// [ e0 ]
// Two content layers, one clip mask.
ASSERT_EQ(2u, RootLayer()->children().size());
ASSERT_EQ(2u, ContentLayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = RootLayer()->children()[0].get();
const cc::Layer* content1 = RootLayer()->children()[1].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_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
return;
}
// 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_P(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.
auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::kWillChangeTransform);
FloatSize corner(5, 5);
FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
corner);
auto c1 = CreateClip(c0(), t0(), rrect);
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());
if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
// Expectation in effect stack diagram:
// l1 l4
// l0 l3
// [ mask_isolation_0 ] l2 [ mask_isolation_1 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, RootLayer()->children().size());
ASSERT_EQ(3u, ContentLayerCount());
// There are still "synthesized layers" but they're null.
ASSERT_EQ(2u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_FALSE(SynthesizedClipLayerAt(1));
const cc::Layer* content0 = RootLayer()->children()[0].get();
const cc::Layer* content1 = RootLayer()->children()[1].get();
const cc::Layer* content2 = 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_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
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_TRUE(mask_isolation_1.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_1.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_1.HasRenderSurface());
return;
}
// 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_TRUE(mask_isolation_0.HasRenderSurface());
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_TRUE(mask_isolation_1.HasRenderSurface());
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_P(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);
auto c1 = CreateClip(c0(), t0(), rrect);
auto e1 = CreateOpacityEffect(e0(), t0(), c1.get(), 1,
CompositingReason::kWillChangeOpacity);
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());
if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
// Expectation in effect stack diagram:
// l1 l3
// l0 [ e1 ] l2
// [ mask_isolation_0 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, RootLayer()->children().size());
ASSERT_EQ(3u, ContentLayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = RootLayer()->children()[0].get();
const cc::Layer* content1 = RootLayer()->children()[1].get();
const cc::Layer* content2 = RootLayer()->children()[2].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_FALSE(mask_isolation_0.HasRenderSurface());
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());
int e2_id = content2->effect_tree_index();
const cc::EffectNode& cc_e2 = *GetPropertyTrees().effect_tree.Node(e2_id);
EXPECT_TRUE(cc_e2.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.rounded_corner_bounds);
return;
}
// 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_TRUE(mask_isolation_0.HasRenderSurface());
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_P(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);
auto c1 = CreateClip(c0(), t0(), rrect);
CompositorFilterOperations non_trivial_filter;
non_trivial_filter.AppendBlurFilter(5);
auto e1 = CreateFilterEffect(e0(), non_trivial_filter, FloatPoint(),
CompositingReason::kActiveFilterAnimation);
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());
if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
// Expectation in effect stack diagram:
// l3
// l1 l2 l5
// l0 [ mask_isolation_1 ] l4
// [ mask_isolation_0 ][ e1 ][ mask_isolation_2 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, RootLayer()->children().size());
ASSERT_EQ(3u, ContentLayerCount());
// There are still "synthesized layers" but they're null.
ASSERT_EQ(3u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_FALSE(SynthesizedClipLayerAt(1));
EXPECT_FALSE(SynthesizedClipLayerAt(2));
const cc::Layer* content0 = RootLayer()->children()[0].get();
const cc::Layer* content1 = RootLayer()->children()[1].get();
const cc::Layer* content2 = RootLayer()->children()[2].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_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
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_TRUE(mask_isolation_1.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_1.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_1.HasRenderSurface());
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_TRUE(mask_isolation_2.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_2.rounded_corner_bounds);
EXPECT_FALSE(mask_isolation_2.HasRenderSurface());
return;
}
// 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_P(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);
auto c1 = CreateClip(c0(), t0(), rrect);
EffectPaintPropertyNode::State e1_state;
e1_state.local_transform_space = &t0();
e1_state.output_clip = c1.get();
e1_state.blend_mode = SkBlendMode::kMultiply;
e1_state.direct_compositing_reasons = CompositingReason::kWillChangeOpacity;
auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(e1_state));
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());
if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
// Expectation in effect stack diagram:
// l1 l2 l3 l5
// l0 [ e1 ] l4
// [ mask_isolation_0 ][ mask_isolation_1 ][ mask_isolation_2 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, RootLayer()->children().size());
ASSERT_EQ(3u, ContentLayerCount());
// There are still "synthesized layers" but they're null.
ASSERT_EQ(3u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_FALSE(SynthesizedClipLayerAt(1));
EXPECT_FALSE(SynthesizedClipLayerAt(2));
const cc::Layer* content0 = RootLayer()->children()[0].get();
const cc::Layer* content1 = RootLayer()->children()[1].get();
const cc::Layer* content2 = RootLayer()->children()[2].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_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.rounded_corner_bounds);
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_TRUE(mask_isolation_1.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_1.rounded_corner_bounds);
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_TRUE(mask_isolation_2.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_2.rounded_corner_bounds);
return;
}
// 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_P(PaintArtifactCompositorTest, WillBeRemovedFromFrame) {
auto effect = CreateSampleEffectNodeWithElementId();
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect)
.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_P(PaintArtifactCompositorTest, ContentsNonOpaque) {
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
EXPECT_FALSE(ContentLayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaque) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
.KnownToBeOpaque();
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
EXPECT_TRUE(ContentLayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedNonOpaque) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
.KnownToBeOpaque()
.Chunk()
.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_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque1) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(FloatRect(100, 100, 300, 300), Color::kBlack)
.KnownToBeOpaque()
.Chunk()
.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_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque2) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
.KnownToBeOpaque()
.Chunk()
.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_P(PaintArtifactCompositorTest, DecompositeEffectWithNoOutputClip) {
// This test verifies effect nodes with no output clip correctly decomposites
// if there is no compositing reasons.
auto clip1 = CreateClip(c0(), t0(), FloatRoundedRect(75, 75, 100, 100));
auto effect1 = CreateOpacityEffect(e0(), t0(), nullptr, 0.5);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
artifact.Chunk(t0(), *clip1, *effect1)
.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_P(PaintArtifactCompositorTest, CompositedEffectWithNoOutputClip) {
// This test verifies effect nodes with no output clip but has compositing
// reason correctly squash children chunks and assign clip node.
auto clip1 = CreateClip(c0(), t0(), FloatRoundedRect(75, 75, 100, 100));
auto effect1 =
CreateOpacityEffect(e0(), t0(), nullptr, 0.5, CompositingReason::kAll);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect1)
.RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
artifact.Chunk(t0(), *clip1, *effect1)
.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_P(PaintArtifactCompositorTest, LayerRasterInvalidationWithClip) {
cc::FakeImplTaskRunnerProvider task_runner_provider_;
cc::TestTaskGraphRunner task_graph_runner_;
cc::FakeLayerTreeHostImpl host_impl(&task_runner_provider_,
&task_graph_runner_);
// The layer's painting is initially not clipped.
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(10, 20, 300, 400));
TestPaintArtifact artifact1;
artifact1.Chunk(t0(), *clip, e0())
.RectDrawing(FloatRect(50, 50, 200, 200), Color::kBlack);
artifact1.Client(0).Validate();
artifact1.Client(1).Validate();
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.
auto artifact2 = TestPaintArtifact()
.Chunk(artifact1.Client(0))
.Properties(t0(), *clip, e0())
.RectDrawing(artifact1.Client(1),
FloatRect(0, 0, 400, 200), Color::kBlack)
.Build();
// Simluate commit to the compositor thread.
layer->PushPropertiesTo(
layer->CreateLayerImpl(host_impl.active_tree()).get());
Update(artifact2);
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.
auto artifact3 =
TestPaintArtifact()
.Chunk(artifact1.Client(0))
.Properties(t0(), *clip, e0())
.RectDrawing(artifact1.Client(1), FloatRect(-100, -200, 500, 800),
Color::kBlack)
.Build();
// Simluate commit to the compositor thread.
layer->PushPropertiesTo(
layer->CreateLayerImpl(host_impl.active_tree()).get());
Update(artifact3);
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)));
}
// Test that PaintArtifactCompositor creates the correct nodes for the visual
// viewport's page scale and scroll layers to support pinch-zooming.
TEST_P(PaintArtifactCompositorTest, CreatesViewportNodes) {
TransformationMatrix matrix;
matrix.Scale(2);
TransformPaintPropertyNode::State transform_state{matrix};
transform_state.in_subtree_of_page_scale = false;
transform_state.compositor_element_id =
CompositorElementIdFromUniqueObjectId(1);
auto scale_transform_node = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), std::move(transform_state));
TestPaintArtifact artifact;
ViewportProperties viewport_properties;
viewport_properties.page_scale = scale_transform_node.get();
Update(artifact.Build(), viewport_properties);
cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree;
cc::TransformNode* cc_transform_node = transform_tree.FindNodeFromElementId(
transform_state.compositor_element_id);
EXPECT_TRUE(cc_transform_node);
EXPECT_EQ(TransformationMatrix::ToTransform(matrix),
cc_transform_node->post_local);
EXPECT_TRUE(cc_transform_node->local.IsIdentity());
EXPECT_TRUE(cc_transform_node->pre_local.IsIdentity());
}
// Test that |cc::TransformNode::in_subtree_of_page_scale_layer| is not set on
// the page scale transform node or ancestors, and is set on descendants.
TEST_P(PaintArtifactCompositorTest, InSubtreeOfPageScale) {
TransformPaintPropertyNode::State ancestor_transform_state;
ancestor_transform_state.in_subtree_of_page_scale = false;
auto ancestor_transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), std::move(ancestor_transform_state));
TransformPaintPropertyNode::State page_scale_transform_state;
page_scale_transform_state.in_subtree_of_page_scale = false;
page_scale_transform_state.compositor_element_id =
CompositorElementIdFromUniqueObjectId(1);
auto page_scale_transform = TransformPaintPropertyNode::Create(
*ancestor_transform, std::move(page_scale_transform_state));
TransformPaintPropertyNode::State descendant_transform_state;
descendant_transform_state.compositor_element_id =
CompositorElementIdFromUniqueObjectId(2);
descendant_transform_state.in_subtree_of_page_scale = true;
descendant_transform_state.direct_compositing_reasons =
CompositingReason::kWillChangeTransform;
auto descendant_transform = TransformPaintPropertyNode::Create(
*page_scale_transform, std::move(descendant_transform_state));
TestPaintArtifact artifact;
artifact.Chunk(*descendant_transform, c0(), e0())
.RectDrawing(FloatRect(0, 0, 10, 10), Color::kBlack);
ViewportProperties viewport_properties;
viewport_properties.page_scale = page_scale_transform.get();
Update(artifact.Build(), viewport_properties);
cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree;
const auto* cc_page_scale_transform = transform_tree.FindNodeFromElementId(
page_scale_transform_state.compositor_element_id);
// The page scale node is not in a subtree of the page scale layer.
EXPECT_FALSE(cc_page_scale_transform->in_subtree_of_page_scale_layer);
// Ancestors of the page scale node are not in a page scale subtree.
auto cc_ancestor_id = cc_page_scale_transform->parent_id;
while (cc_ancestor_id != cc::TransformTree::kInvalidNodeId) {
const auto* ancestor = transform_tree.Node(cc_ancestor_id);
EXPECT_FALSE(ancestor->in_subtree_of_page_scale_layer);
cc_ancestor_id = ancestor->parent_id;
}
// Descendants of the page scale node should be in the page scale subtree.
const auto* cc_descendant_transform = transform_tree.FindNodeFromElementId(
descendant_transform_state.compositor_element_id);
EXPECT_TRUE(cc_descendant_transform->in_subtree_of_page_scale_layer);
}
// Test that PaintArtifactCompositor pushes page scale to the transform tree.
TEST_P(PaintArtifactCompositorTest, ViewportPageScale) {
// Create a page scale transform node with a page scale factor of 2.0.
TransformationMatrix matrix;
matrix.Scale(2);
TransformPaintPropertyNode::State transform_state{matrix};
transform_state.in_subtree_of_page_scale = false;
transform_state.compositor_element_id =
CompositorElementIdFromUniqueObjectId(1);
auto scale_transform_node = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), std::move(transform_state));
// Create a viewport scroll node with container size 20x10 and contents size
// 27x32.
ScrollPaintPropertyNode::State scroll_state;
scroll_state.container_rect = IntRect(5, 5, 20, 10);
scroll_state.contents_size = IntSize(27, 32);
scroll_state.user_scrollable_vertical = true;
scroll_state.max_scroll_offset_affected_by_page_scale = true;
scroll_state.compositor_element_id = ScrollElementId(2);
auto scroll =
CreateScroll(ScrollPaintPropertyNode::Root(), scroll_state,
kNotScrollingOnMain, scroll_state.compositor_element_id);
auto scroll_translation =
CreateScrollTranslation(*scale_transform_node, 0, 0, *scroll);
TestPaintArtifact artifact;
artifact.Chunk(*scroll_translation, c0(), e0())
.RectDrawing(FloatRect(0, 0, 10, 10), Color::kBlack);
ViewportProperties viewport_properties;
viewport_properties.page_scale = scale_transform_node.get();
Update(artifact.Build(), viewport_properties);
cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
cc::ScrollNode* cc_scroll_node =
scroll_tree.FindNodeFromElementId(scroll_state.compositor_element_id);
auto max_scroll_offset = scroll_tree.MaxScrollOffset(cc_scroll_node->id);
// The max scroll offset should be scaled by the page scale factor (see:
// |ScrollTree::MaxScrollOffset|). This adjustment scales the contents from
// 27x32 to 54x64 so the max scroll offset becomes (54-20)/2 x (64-10)/2.
EXPECT_EQ(gfx::ScrollOffset(17, 27), max_scroll_offset);
}
enum {
kNoRenderSurface = 0,
kHasRenderSurface = 1 << 0,
};
#define EXPECT_OPACITY(effect_id, expected_opacity, expected_flags) \
do { \
const auto* effect = GetPropertyTrees().effect_tree.Node(effect_id); \
EXPECT_EQ(expected_opacity, effect->opacity); \
EXPECT_EQ(!!((expected_flags)&kHasRenderSurface), \
effect->HasRenderSurface()); \
} while (false)
TEST_P(PaintArtifactCompositorTest, OpacityRenderSurfaces) {
// e
// / | \
// a b c -- L4
// / \ / \ \
// aa ab L2 L3 ca (L = layer)
// | | |
// L0 L1 L5
auto e = CreateOpacityEffect(e0(), 0.1f);
auto a = CreateOpacityEffect(*e, 0.2f);
auto b = CreateOpacityEffect(*e, 0.3f, CompositingReason::kWillChangeOpacity);
auto c = CreateOpacityEffect(*e, 0.4f, CompositingReason::kWillChangeOpacity);
auto aa =
CreateOpacityEffect(*a, 0.5f, CompositingReason::kWillChangeOpacity);
auto ab =
CreateOpacityEffect(*a, 0.6f, CompositingReason::kWillChangeOpacity);
auto ca =
CreateOpacityEffect(*c, 0.7f, CompositingReason::kWillChangeOpacity);
auto t = CreateTransform(t0(), TransformationMatrix().Rotate(90),
FloatPoint3D(), CompositingReason::k3DTransform);
TestPaintArtifact artifact;
FloatRect r(150, 150, 100, 100);
artifact.Chunk(t0(), c0(), *aa).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *ab).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *b).RectDrawing(r, Color::kWhite);
artifact.Chunk(*t, c0(), *b).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *c).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *ca).RectDrawing(r, Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(6u, ContentLayerCount());
int effect_ids[6];
for (size_t i = 0; i < ContentLayerCount(); i++)
effect_ids[i] = ContentLayerAt(i)->effect_tree_index();
// Effects of layer 0, 1, 5 each has one compositing layer, so don't have
// render surface.
EXPECT_OPACITY(effect_ids[0], 0.5f, kNoRenderSurface);
EXPECT_OPACITY(effect_ids[1], 0.6f, kNoRenderSurface);
EXPECT_OPACITY(effect_ids[5], 0.7f, kNoRenderSurface);
// Layer 2 and 3 have the same effect state. The effect has render surface
// because it has two compositing layers.
EXPECT_EQ(effect_ids[2], effect_ids[3]);
EXPECT_OPACITY(effect_ids[2], 0.3f, kHasRenderSurface);
// Effect |a| has two indirect compositing layers, so has render surface.
const auto& effect_tree = GetPropertyTrees().effect_tree;
int id_a = effect_tree.Node(effect_ids[0])->parent_id;
EXPECT_EQ(id_a, effect_tree.Node(effect_ids[1])->parent_id);
EXPECT_OPACITY(id_a, 0.2f, kHasRenderSurface);
// Effect |c| has one direct and one indirect compositing layers, so has
// render surface.
EXPECT_OPACITY(effect_ids[4], 0.4f, kHasRenderSurface);
// |e| has render surface because it has 3 child render surfaces.
EXPECT_OPACITY(effect_tree.Node(effect_ids[4])->parent_id, 0.1f,
kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, OpacityRenderSurfacesWithFilterChildren) {
auto opacity = CreateOpacityEffect(e0(), 0.1f);
CompositorFilterOperations filter;
filter.AppendBlurFilter(5);
auto filter1 = CreateFilterEffect(*opacity, filter, FloatPoint(),
CompositingReason::kActiveFilterAnimation);
auto filter2 = CreateFilterEffect(*opacity, filter, FloatPoint(),
CompositingReason::kActiveFilterAnimation);
FloatRect r(150, 150, 100, 100);
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *filter1)
.RectDrawing(r, Color::kWhite)
.Chunk(t0(), c0(), *filter2)
.RectDrawing(r, Color::kWhite)
.Build());
ASSERT_EQ(2u, ContentLayerCount());
auto filter_id1 = ContentLayerAt(0)->effect_tree_index();
auto filter_id2 = ContentLayerAt(1)->effect_tree_index();
EXPECT_OPACITY(filter_id1, 1.f, kHasRenderSurface);
EXPECT_OPACITY(filter_id2, 1.f, kHasRenderSurface);
EXPECT_OPACITY(GetPropertyTrees().effect_tree.Node(filter_id1)->parent_id,
0.1f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, OpacityAnimationRenderSurfaces) {
// The topologies of the effect tree and layer tree are the same as
// OpacityRencerSurfaces, except that the layers all have 1.f opacity and
// active opacity animations.
// e
// / | \
// a b c -- L4
// / \ / \ \
// aa ab L2 L3 ca (L = layer)
// | | |
// L0 L1 L5
auto e = CreateAnimatingOpacityEffect(e0());
auto a = CreateAnimatingOpacityEffect(*e);
auto b = CreateAnimatingOpacityEffect(*e);
auto c = CreateAnimatingOpacityEffect(*e);
auto aa = CreateAnimatingOpacityEffect(*a);
auto ab = CreateAnimatingOpacityEffect(*a);
auto ca = CreateAnimatingOpacityEffect(*c);
auto t = CreateTransform(t0(), TransformationMatrix().Rotate(90),
FloatPoint3D(), CompositingReason::k3DTransform);
TestPaintArtifact artifact;
FloatRect r(150, 150, 100, 100);
artifact.Chunk(t0(), c0(), *aa).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *ab).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *b).RectDrawing(r, Color::kWhite);
artifact.Chunk(*t, c0(), *b).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *c).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *ca).RectDrawing(r, Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(6u, ContentLayerCount());
int effect_ids[6];
for (size_t i = 0; i < ContentLayerCount(); i++)
effect_ids[i] = ContentLayerAt(i)->effect_tree_index();
// Effects of layer 0, 1, 5 each has one compositing layer, so don't have
// render surface.
EXPECT_OPACITY(effect_ids[0], 1.f, kNoRenderSurface);
EXPECT_OPACITY(effect_ids[1], 1.f, kNoRenderSurface);
EXPECT_OPACITY(effect_ids[5], 1.f, kNoRenderSurface);
// Layer 2 and 3 have the same effect state. The effect has render surface
// because it has two compositing layers.
EXPECT_EQ(effect_ids[2], effect_ids[3]);
EXPECT_OPACITY(effect_ids[2], 1.f, kHasRenderSurface);
// TODO(crbug.com/937573): It's an invalid case that an animating effect
// doesn't have a layer, but we still keep the case in this test case because
// it does occur in CompositeAfterPaint mode before the bug is fixed.
const auto& effect_tree = GetPropertyTrees().effect_tree;
int id_a = effect_tree.Node(effect_ids[0])->parent_id;
EXPECT_EQ(id_a, effect_tree.Node(effect_ids[1])->parent_id);
EXPECT_OPACITY(id_a, 1.f, kNoRenderSurface);
// Effect |c| has one direct and one indirect compositing layers, so has
// render surface.
EXPECT_OPACITY(effect_ids[4], 1.f, kHasRenderSurface);
// TODO(crbug.com/937573): Same as |a|.
EXPECT_OPACITY(effect_tree.Node(effect_ids[4])->parent_id, 1.f,
kNoRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, OpacityRenderSurfacesWithBackdropChildren) {
// Opacity effect with a single compositing backdrop-filter child. Normally
// the opacity effect would not get a render surface. However, because
// backdrop-filter needs to only filter up to the backdrop root, it always
// gets a render surface.
auto e = CreateOpacityEffect(e0(), 0.4f);
auto a = CreateOpacityEffect(*e, 0.5f);
CompositorFilterOperations blur_filter;
blur_filter.AppendBlurFilter(5);
auto bd = CreateBackdropFilterEffect(
*a, blur_filter, FloatPoint(),
CompositingReason::kActiveBackdropFilterAnimation);
TestPaintArtifact artifact;
FloatRect r(150, 150, 100, 100);
artifact.Chunk(t0(), c0(), *a).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), c0(), *bd).RectDrawing(r, Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, ContentLayerCount());
EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 0.5,
kHasRenderSurface);
EXPECT_OPACITY(ContentLayerAt(1)->effect_tree_index(), 1.0,
kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest,
DirectTransformAnimationCausesRenderSurfaceFor2dAxisMisalignedClip) {
// When a clip is affected by an animated transform, we should get a render
// surface for the effect node.
auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::kActiveTransformAnimation);
auto e1 = CreateOpacityEffect(e0(), *t1, nullptr, 1.f);
auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(50, 50, 50, 50));
TestPaintArtifact artifact;
FloatRect r(150, 150, 100, 100);
artifact.Chunk(t0(), c0(), e0()).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), *c1, *e1).RectDrawing(r, Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, ContentLayerCount());
const auto* effect = GetPropertyTrees().effect_tree.Node(
ContentLayerAt(1)->effect_tree_index());
EXPECT_TRUE(effect->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest,
IndirectTransformAnimationCausesRenderSurfaceFor2dAxisMisalignedClip) {
// When a clip is affected by an animated transform, we should get a render
// surface for the effect node.
auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
CompositingReason::kActiveTransformAnimation);
auto t2 = Create2DTranslation(*t1, 10, 20);
auto e1 = CreateOpacityEffect(e0(), *t2, nullptr, 1.f);
auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(50, 50, 50, 50));
TestPaintArtifact artifact;
FloatRect r(150, 150, 100, 100);
artifact.Chunk(t0(), c0(), e0()).RectDrawing(r, Color::kWhite);
artifact.Chunk(t0(), *c1, *e1).RectDrawing(r, Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, ContentLayerCount());
const auto* effect = GetPropertyTrees().effect_tree.Node(
ContentLayerAt(1)->effect_tree_index());
EXPECT_TRUE(effect->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, OpacityIndirectlyAffectingTwoLayers) {
auto opacity = CreateOpacityEffect(e0(), 0.5f);
auto child_composited_effect =
CreateOpacityEffect(*opacity, 1.f, CompositingReason::kWillChangeOpacity);
auto grandchild_composited_effect = CreateOpacityEffect(
*child_composited_effect, 1.f, CompositingReason::kWillChangeOpacity);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *child_composited_effect)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite);
artifact.Chunk(t0(), c0(), *grandchild_composited_effect)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(2u, ContentLayerCount());
const auto& effect_tree = GetPropertyTrees().effect_tree;
int layer0_effect_id = ContentLayerAt(0)->effect_tree_index();
EXPECT_OPACITY(layer0_effect_id, 1.f, kNoRenderSurface);
int layer1_effect_id = ContentLayerAt(1)->effect_tree_index();
EXPECT_OPACITY(layer1_effect_id, 1.f, kNoRenderSurface);
// |opacity| affects both layer0 and layer1 which don't have render surfaces,
// so it should have a render surface.
int opacity_id = effect_tree.Node(layer0_effect_id)->parent_id;
EXPECT_OPACITY(opacity_id, 0.5f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest,
OpacityIndirectlyAffectingTwoLayersWithOpacityAnimations) {
auto opacity = CreateAnimatingOpacityEffect(e0());
auto child_composited_effect = CreateAnimatingOpacityEffect(*opacity);
auto grandchild_composited_effect =
CreateAnimatingOpacityEffect(*child_composited_effect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *child_composited_effect)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite);
artifact.Chunk(t0(), c0(), *grandchild_composited_effect)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(2u, ContentLayerCount());
const auto& effect_tree = GetPropertyTrees().effect_tree;
// layer0's opacity animation needs a render surfafce because it affects
// both layer0 and layer1.
int layer0_effect_id = ContentLayerAt(0)->effect_tree_index();
EXPECT_OPACITY(layer0_effect_id, 1.f, kHasRenderSurface);
// layer1's opacity animation doesn't need a render surface because it
// affects layer1 only.
int layer1_effect_id = ContentLayerAt(1)->effect_tree_index();
EXPECT_OPACITY(layer1_effect_id, 1.f, kNoRenderSurface);
// Though |opacity| affects both layer0 and layer1, layer0's effect has
// render surface, so |opacity| doesn't need a render surface.
int opacity_id = effect_tree.Node(layer0_effect_id)->parent_id;
EXPECT_OPACITY(opacity_id, 1.f, kNoRenderSurface);
}
TEST_P(PaintArtifactCompositorTest,
ActiveAnimationCompositingReasonWithoutActiveAnimationFlag) {
// TODO(crbug.com/900241): This test tests no render surface should be created
// for an effect node with kActiveFilterAnimation compositing reason without
// active animation flag. This simulates the extra effect node created for
// filter animation, which should not create render surface.
// Remove this test when we fix the bug.
EffectPaintPropertyNode::State state;
state.local_transform_space = &t0();
state.direct_compositing_reasons = CompositingReason::kActiveFilterAnimation;
auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(state));
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, ContentLayerCount());
EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f, kNoRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, FilterCreatesRenderSurface) {
CompositorFilterOperations filter;
filter.AppendBlurFilter(5);
auto e1 = CreateFilterEffect(e0(), filter, FloatPoint(),
CompositingReason::kActiveFilterAnimation);
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, ContentLayerCount());
EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, FilterAnimationCreatesRenderSurface) {
auto e1 = CreateAnimatingFilterEffect(e0());
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, ContentLayerCount());
EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, BackdropFilterCreatesRenderSurface) {
CompositorFilterOperations filter;
filter.AppendBlurFilter(5);
auto e1 = CreateBackdropFilterEffect(e0(), filter, FloatPoint(),
CompositingReason::kBackdropFilter);
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, ContentLayerCount());
EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest,
BackdropFilterAnimationCreatesRenderSurface) {
auto e1 = CreateAnimatingBackdropFilterEffect(e0());
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, ContentLayerCount());
EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, Non2dAxisAlignedClip) {
auto rotate = CreateTransform(t0(), TransformationMatrix().Rotate(45));
auto clip = CreateClip(c0(), *rotate, FloatRoundedRect(50, 50, 50, 50));
auto opacity = CreateOpacityEffect(
e0(), 0.5f, CompositingReason::kActiveOpacityAnimation);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip, *opacity)
.RectDrawing(FloatRect(50, 50, 50, 50), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
// We should create a synthetic effect node for the non-2d-axis-aligned clip.
int clip_id = ContentLayerAt(0)->clip_tree_index();
const auto* cc_clip = GetPropertyTrees().clip_tree.Node(clip_id);
int effect_id = ContentLayerAt(0)->effect_tree_index();
const auto* cc_effect = GetPropertyTrees().effect_tree.Node(effect_id);
EXPECT_OPACITY(effect_id, 1.f, kHasRenderSurface);
EXPECT_OPACITY(cc_effect->parent_id, 0.5f, kNoRenderSurface);
EXPECT_EQ(cc_effect->clip_id, cc_clip->parent_id);
}
TEST_P(PaintArtifactCompositorTest, Non2dAxisAlignedRoundedRectClip) {
auto rotate = CreateTransform(t0(), TransformationMatrix().Rotate(45));
FloatSize corner(5, 5);
FloatRoundedRect rounded_clip(FloatRect(50, 50, 50, 50), corner, corner,
corner, corner);
auto clip = CreateClip(c0(), *rotate, rounded_clip);
auto opacity = CreateOpacityEffect(
e0(), 0.5f, CompositingReason::kActiveOpacityAnimation);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip, *opacity)
.RectDrawing(FloatRect(50, 50, 50, 50), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
// We should create a synthetic effect node for the non-2d-axis-aligned clip.
int clip_id = ContentLayerAt(0)->clip_tree_index();
const auto* cc_clip = GetPropertyTrees().clip_tree.Node(clip_id);
int effect_id = ContentLayerAt(0)->effect_tree_index();
const auto* cc_effect = GetPropertyTrees().effect_tree.Node(effect_id);
EXPECT_OPACITY(effect_id, 1.f, kHasRenderSurface);
EXPECT_OPACITY(cc_effect->parent_id, 0.5f, kNoRenderSurface);
EXPECT_EQ(cc_effect->clip_id, cc_clip->id);
}
TEST_P(PaintArtifactCompositorTest,
Non2dAxisAlignedClipUnderLaterRenderSurface) {
auto rotate1 =
CreateTransform(t0(), TransformationMatrix().Rotate(45), FloatPoint3D(),
CompositingReason::k3DTransform);
auto rotate2 =
CreateTransform(*rotate1, TransformationMatrix().Rotate(-45),
FloatPoint3D(), CompositingReason::k3DTransform);
auto clip = CreateClip(c0(), *rotate2, FloatRoundedRect(50, 50, 50, 50));
auto opacity = CreateOpacityEffect(
e0(), *rotate1, &c0(), 0.5f, CompositingReason::kActiveOpacityAnimation);
// This assert ensures the test actually tests the situation. If it fails
// due to floating-point errors, we should choose other transformation values
// to make it succeed.
ASSERT_TRUE(GeometryMapper::SourceToDestinationProjection(t0(), *rotate2)
.Matrix()
.Preserves2dAxisAlignment());
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *opacity)
.RectDrawing(FloatRect(50, 50, 50, 50), Color::kWhite);
artifact.Chunk(*rotate1, c0(), *opacity)
.RectDrawing(FloatRect(50, 50, 50, 50), Color::kWhite);
artifact.Chunk(*rotate2, *clip, *opacity)
.RectDrawing(FloatRect(50, 50, 50, 50), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(3u, ContentLayerCount());
// We should create a synthetic effect node for the non-2d-axis-aligned clip,
// though the accumulated transform to the known render surface was identity
// when the cc clip node was created.
int clip_id = ContentLayerAt(2)->clip_tree_index();
const auto* cc_clip = GetPropertyTrees().clip_tree.Node(clip_id);
int effect_id = ContentLayerAt(2)->effect_tree_index();
const auto* cc_effect = GetPropertyTrees().effect_tree.Node(effect_id);
EXPECT_OPACITY(effect_id, 1.f, kHasRenderSurface);
EXPECT_OPACITY(cc_effect->parent_id, 0.5f, kHasRenderSurface);
EXPECT_EQ(cc_effect->clip_id, cc_clip->parent_id);
}
} // namespace blink