blob: 3a50fdc8c469e554effec24f9ded86753fb04fc2 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// 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/task/single_thread_task_runner.h"
#include "base/test/test_simple_task_runner.h"
#include "build/build_config.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/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/property_tree.h"
#include "cc/trees/scroll_node.h"
#include "cc/trees/transform_node.h"
#include "cc/view_transition/view_transition_request.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/allocator.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "ui/gfx/geometry/test/geometry_util.h"
namespace blink {
using testing::ElementsAre;
using testing::Pointee;
gfx::Transform Translation(SkScalar x, SkScalar 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 = RefCountedPropertyTreeStateOrAlias(properties);
}
class MockScrollCallbacks : public CompositorScrollCallbacks {
public:
MOCK_METHOD3(DidCompositorScroll,
void(CompositorElementId,
const gfx::PointF&,
const absl::optional<cc::TargetSnapAreaElementIds>&));
MOCK_METHOD2(DidChangeScrollbarsHidden, void(CompositorElementId, bool));
base::WeakPtr<MockScrollCallbacks> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
base::WeakPtrFactory<MockScrollCallbacks> weak_ptr_factory_{this};
};
class PaintArtifactCompositorTest : public testing::Test,
public PaintTestConfigurations {
protected:
PaintArtifactCompositorTest()
: task_runner_(base::MakeRefCounted<base::TestSimpleTaskRunner>()),
task_runner_current_default_handle_(task_runner_) {}
void SetUp() override {
// Delay constructing the compositor until after the feature is set.
paint_artifact_compositor_ = std::make_unique<PaintArtifactCompositor>(
scroll_callbacks_.GetWeakPtr());
// Prefer lcd-text by default for tests.
paint_artifact_compositor_->SetLCDTextPreference(
LCDTextPreference::kStronglyPreferred);
// 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) {
return *GetPropertyTrees().transform_tree().Node(
layer->transform_tree_index());
}
const cc::EffectNode& GetEffectNode(const cc::Layer* layer) {
return *GetPropertyTrees().effect_tree().Node(layer->effect_tree_index());
}
cc::LayerTreeHost& GetLayerTreeHost() {
return *layer_tree_->layer_tree_host();
}
int ElementIdToEffectNodeIndex(CompositorElementId element_id) {
auto* property_trees = layer_tree_->layer_tree_host()->property_trees();
const auto* node =
property_trees->effect_tree().FindNodeFromElementId(element_id);
return node ? node->id : -1;
}
int ElementIdToTransformNodeIndex(CompositorElementId element_id) {
auto* property_trees = layer_tree_->layer_tree_host()->property_trees();
const auto* node =
property_trees->transform_tree().FindNodeFromElementId(element_id);
return node ? node->id : -1;
}
int ElementIdToScrollNodeIndex(CompositorElementId element_id) {
auto* property_trees = layer_tree_->layer_tree_host()->property_trees();
const auto* node =
property_trees->scroll_tree().FindNodeFromElementId(element_id);
return node ? node->id : -1;
}
using ViewportProperties = PaintArtifactCompositor::ViewportProperties;
void Update(
scoped_refptr<const PaintArtifact> artifact,
const ViewportProperties& viewport_properties = ViewportProperties(),
const WTF::Vector<const TransformPaintPropertyNode*>&
scroll_translation_nodes = {}) {
paint_artifact_compositor_->SetNeedsUpdate(
PaintArtifactCompositorUpdateReason::kTest);
paint_artifact_compositor_->Update(artifact, viewport_properties,
scroll_translation_nodes, {}, {});
layer_tree_->layer_tree_host()->LayoutAndUpdateLayers();
}
void ClearPropertyTreeChangedState() {
paint_artifact_compositor_->ClearPropertyTreeChangedState();
}
void WillBeRemovedFromFrame() {
paint_artifact_compositor_->WillBeRemovedFromFrame();
}
cc::Layer* RootLayer() { return paint_artifact_compositor_->RootLayer(); }
void CreateScrollableChunk(TestPaintArtifact& artifact,
const RefCountedPropertyTreeState& scroll_state) {
artifact
.Chunk(*scroll_state.Transform().Parent(),
*scroll_state.Clip().Parent(), scroll_state.Effect())
.ScrollHitTest(scroll_state.Transform().ScrollNode()->ContainerRect(),
&scroll_state.Transform());
}
// Returns the |num|th scroll hit test layer.
cc::Layer* ScrollableLayerAt(size_t num) {
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree();
for (auto& layer : RootLayer()->children()) {
if (scroll_tree.FindNodeFromElementId(layer->element_id())) {
if (num == 0)
return layer.get();
num--;
}
}
return nullptr;
}
// Returns the |num|th non-scrollable content layer.
cc::Layer* NonScrollableLayerAt(size_t num) {
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree();
for (auto& layer : RootLayer()->children()) {
if (!scroll_tree.FindNodeFromElementId(layer->element_id())) {
if (num == 0)
return layer.get();
num--;
}
}
return nullptr;
}
size_t LayerCount() { return RootLayer()->children().size(); }
cc::Layer* LayerAt(unsigned index) {
return RootLayer()->children()[index].get();
}
size_t SynthesizedClipLayerCount() {
return paint_artifact_compositor_->SynthesizedClipLayersForTesting().size();
}
cc::Layer* SynthesizedClipLayerAt(unsigned index) {
return paint_artifact_compositor_->SynthesizedClipLayersForTesting()[index];
}
// Return the index of |layer| in the root layer list, or -1 if not found.
int LayerIndex(const cc::Layer* layer) {
int i = 0;
for (auto& child : RootLayer()->children()) {
if (child.get() == layer)
return i;
i++;
}
return -1;
}
void UpdateWithEffectivelyInvisibleChunk(bool include_preceding_chunk,
bool include_subsequent_chunk) {
TestPaintArtifact artifact;
if (include_preceding_chunk)
artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 10, 10), Color::kBlack);
artifact.Chunk().EffectivelyInvisible().RectDrawing(
gfx::Rect(10, 0, 10, 10), Color(255, 0, 0));
if (include_subsequent_chunk)
artifact.Chunk().RectDrawing(gfx::Rect(0, 10, 10, 10), Color::kWhite);
Update(artifact.Build());
}
MockScrollCallbacks& ScrollCallbacks() { return scroll_callbacks_; }
PaintArtifactCompositor& GetPaintArtifactCompositor() {
return *paint_artifact_compositor_;
}
private:
MockScrollCallbacks scroll_callbacks_;
std::unique_ptr<PaintArtifactCompositor> paint_artifact_compositor_;
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
base::SingleThreadTaskRunner::CurrentDefaultHandle
task_runner_current_default_handle_;
cc::FakeLayerTreeHostClient layer_tree_host_client_;
std::unique_ptr<LayerTreeHostEmbedder> layer_tree_;
};
INSTANTIATE_PAINT_TEST_SUITE_P(PaintArtifactCompositorTest);
const auto kNotScrollingOnMain =
cc::MainThreadScrollingReason::kNotScrollingOnMain;
TEST_P(PaintArtifactCompositorTest, EmptyPaintArtifact) {
Update(base::MakeRefCounted<PaintArtifact>());
EXPECT_TRUE(RootLayer()->children().empty());
}
TEST_P(PaintArtifactCompositorTest, OneChunkWithAnOffset) {
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(gfx::Rect(50, -50, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* child = LayerAt(0);
EXPECT_THAT(
child->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 100, 100), Color::kWhite)));
EXPECT_EQ(Translation(50, -50), child->ScreenSpaceTransform());
EXPECT_EQ(gfx::Size(100, 100), child->bounds());
EXPECT_FALSE(GetTransformNode(child).transform_changed);
}
TEST_P(PaintArtifactCompositorTest, OneTransform) {
// A 90 degree clockwise rotation about (100, 100).
auto transform =
CreateTransform(t0(), MakeRotationMatrix(90), gfx::Point3F(100, 100, 0),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kGray);
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(100, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
{
const cc::Layer* layer = LayerAt(0);
EXPECT_TRUE(GetTransformNode(layer).transform_changed);
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
rects_with_color.push_back(
RectWithColor(gfx::RectF(100, 0, 100, 100), Color::kBlack));
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
EXPECT_EQ(
gfx::RectF(100, 0, 100, 100),
layer->ScreenSpaceTransform().MapRect(gfx::RectF(0, 0, 100, 100)));
}
{
const cc::Layer* layer = LayerAt(1);
EXPECT_FALSE(GetTransformNode(layer).transform_changed);
EXPECT_THAT(
layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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(), MakeRotationMatrix(90), gfx::Point3F(100, 100, 0),
CompositingReason::k3DTransform);
auto transform = TransformPaintPropertyNodeAlias::Create(*real_transform);
TestPaintArtifact artifact;
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kGray);
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(100, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
{
const cc::Layer* layer = LayerAt(0);
EXPECT_TRUE(GetTransformNode(layer).transform_changed);
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
rects_with_color.push_back(
RectWithColor(gfx::RectF(100, 0, 100, 100), Color::kBlack));
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
EXPECT_EQ(
gfx::RectF(100, 0, 100, 100),
layer->ScreenSpaceTransform().MapRect(gfx::RectF(0, 0, 100, 100)));
}
{
const cc::Layer* layer = LayerAt(1);
EXPECT_FALSE(GetTransformNode(layer).transform_changed);
EXPECT_THAT(
layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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(), MakeScaleMatrix(2), gfx::Point3F(10, 10, 0),
CompositingReason::k3DTransform);
auto transform2 =
CreateTransform(*transform1, MakeTranslationMatrix(5, 5), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
TestPaintArtifact artifact;
artifact.Chunk(*transform1, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kWhite);
artifact.Chunk(*transform2, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
{
const cc::Layer* layer = LayerAt(0);
EXPECT_TRUE(GetTransformNode(layer).transform_changed);
EXPECT_THAT(
layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 300, 200), Color::kWhite)));
EXPECT_EQ(
gfx::RectF(-10, -10, 600, 400),
layer->ScreenSpaceTransform().MapRect(gfx::RectF(0, 0, 300, 200)));
}
{
const cc::Layer* layer = LayerAt(1);
EXPECT_TRUE(GetTransformNode(layer).transform_changed);
EXPECT_THAT(
layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 300, 200), Color::kBlack)));
EXPECT_EQ(gfx::RectF(0, 0, 600, 400), layer->ScreenSpaceTransform().MapRect(
gfx::RectF(0, 0, 300, 200)));
}
EXPECT_NE(LayerAt(0)->transform_tree_index(),
LayerAt(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(gfx::Rect(0, 0, 300, 200), Color::kWhite);
artifact.Chunk(*backface_inherited_transform, c0(), e0())
.RectDrawing(gfx::Rect(100, 100, 100, 100), Color::kBlack);
artifact.Chunk(*backface_visible_transform, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kDarkGray);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
EXPECT_THAT(
LayerAt(0)->GetPicture(),
Pointee(DrawsRectangles(Vector<RectWithColor>{
RectWithColor(gfx::RectF(0, 0, 300, 200), Color::kWhite),
RectWithColor(gfx::RectF(100, 100, 100, 100), Color::kBlack)})));
EXPECT_THAT(
LayerAt(1)->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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(), gfx::Transform());
auto transform2 =
CreateTransform(*transform1, MakeRotationMatrix(0, 45, 0));
TransformPaintPropertyNode::State transform3_state{
{MakeRotationMatrix(0, 45, 0)}};
transform3_state.flags.flattens_inherited_transform =
transform_is_flattened;
auto transform3 = TransformPaintPropertyNode::Create(
*transform2, std::move(transform3_state));
TestPaintArtifact artifact;
artifact.Chunk(*transform3, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(
layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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 =
layer->ScreenSpaceTransform().MapRect(gfx::RectF(0, 0, 100, 100));
if (transform_is_flattened)
EXPECT_RECTF_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(), gfx::Transform());
auto transform1 = TransformPaintPropertyNodeAlias::Create(*real_transform1);
auto real_transform2 =
CreateTransform(*transform1, MakeRotationMatrix(0, 45, 0));
auto transform2 = TransformPaintPropertyNodeAlias::Create(*real_transform2);
TransformPaintPropertyNode::State transform3_state{
{MakeRotationMatrix(0, 45, 0)}};
transform3_state.flags.flattens_inherited_transform =
transform_is_flattened;
auto real_transform3 = TransformPaintPropertyNode::Create(
*transform2, std::move(transform3_state));
auto transform3 = TransformPaintPropertyNodeAlias::Create(*real_transform3);
TestPaintArtifact artifact;
artifact.Chunk(*transform3, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(
layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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 =
layer->ScreenSpaceTransform().MapRect(gfx::RectF(0, 0, 100, 100));
if (transform_is_flattened)
EXPECT_RECTF_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(), gfx::Transform());
// Establishes a 3D rendering context.
TransformPaintPropertyNode::State transform2_state;
transform2_state.rendering_context_id = 1;
transform2_state.direct_compositing_reasons =
CompositingReason::kWillChangeTransform;
auto transform2 = TransformPaintPropertyNode::Create(
*transform1, std::move(transform2_state));
// Extends the 3D rendering context of transform2.
TransformPaintPropertyNode::State transform3_state;
transform3_state.rendering_context_id = 1;
transform3_state.direct_compositing_reasons =
CompositingReason::kWillChangeTransform;
auto transform3 = TransformPaintPropertyNode::Create(
*transform2, std::move(transform3_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(gfx::Rect(0, 0, 300, 200), Color::kWhite);
artifact.Chunk(*transform2, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kLightGray);
artifact.Chunk(*transform3, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kDarkGray);
artifact.Chunk(*transform4, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 300, 200), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(4u, LayerCount());
// The white layer is not 3D sorted.
const cc::Layer* white_layer = LayerAt(0);
EXPECT_THAT(
white_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 300, 200), Color::kWhite)));
int white_sorting_context_id =
GetTransformNode(white_layer).sorting_context_id;
EXPECT_EQ(0, white_sorting_context_id);
// The light gray layer is 3D sorted.
const cc::Layer* light_gray_layer = LayerAt(1);
EXPECT_THAT(
light_gray_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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 = LayerAt(2);
EXPECT_THAT(
dark_gray_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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 = LayerAt(3);
EXPECT_THAT(
black_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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(gfx::Rect(220, 80, 300, 200), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(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(gfx::RectF(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_TRUE(clip_node->AppliesLocalClip());
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 = ClipPaintPropertyNodeAlias::Create(*real_clip);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip, e0())
.RectDrawing(gfx::Rect(220, 80, 300, 200), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(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(gfx::RectF(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_TRUE(clip_node->AppliesLocalClip());
EXPECT_EQ(gfx::RectF(100, 100, 300, 200), clip_node->clip);
}
TEST_P(PaintArtifactCompositorTest, NestedClips) {
auto transform1 = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
auto clip1 =
CreateClip(c0(), *transform1, FloatRoundedRect(100, 100, 700, 700));
auto transform2 =
CreateTransform(*transform1, gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
auto clip2 =
CreateClip(*clip1, *transform2, FloatRoundedRect(200, 200, 700, 700));
TestPaintArtifact artifact;
artifact.Chunk(*transform1, *clip1, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kWhite);
artifact.Chunk(*transform2, *clip2, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kLightGray);
artifact.Chunk(*transform1, *clip1, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kDarkGray);
artifact.Chunk(*transform2, *clip2, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(4u, LayerCount());
const cc::Layer* white_layer = LayerAt(0);
EXPECT_THAT(
white_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 100, 100), Color::kWhite)));
EXPECT_EQ(Translation(300, 350), white_layer->ScreenSpaceTransform());
const cc::Layer* light_gray_layer = LayerAt(1);
EXPECT_THAT(
light_gray_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 100, 100), Color::kLightGray)));
EXPECT_EQ(Translation(300, 350), light_gray_layer->ScreenSpaceTransform());
const cc::Layer* dark_gray_layer = LayerAt(2);
EXPECT_THAT(
dark_gray_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 100, 100), Color::kDarkGray)));
EXPECT_EQ(Translation(300, 350), dark_gray_layer->ScreenSpaceTransform());
const cc::Layer* black_layer = LayerAt(3);
EXPECT_THAT(
black_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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_TRUE(outer_clip->AppliesLocalClip());
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_TRUE(inner_clip->AppliesLocalClip());
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 transform1 = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
auto real_clip1 =
CreateClip(c0(), *transform1, FloatRoundedRect(100, 100, 700, 700));
auto clip1 = ClipPaintPropertyNodeAlias::Create(*real_clip1);
auto transform2 =
CreateTransform(*transform1, gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
auto real_clip2 =
CreateClip(*clip1, *transform2, FloatRoundedRect(200, 200, 700, 700));
auto clip2 = ClipPaintPropertyNodeAlias::Create(*real_clip2);
TestPaintArtifact artifact;
artifact.Chunk(*transform1, *clip1, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kWhite);
artifact.Chunk(*transform2, *clip2, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kLightGray);
artifact.Chunk(*transform1, *clip1, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kDarkGray);
artifact.Chunk(*transform2, *clip2, e0())
.RectDrawing(gfx::Rect(300, 350, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(4u, LayerCount());
const cc::Layer* white_layer = LayerAt(0);
EXPECT_THAT(
white_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 100, 100), Color::kWhite)));
EXPECT_EQ(Translation(300, 350), white_layer->ScreenSpaceTransform());
const cc::Layer* light_gray_layer = LayerAt(1);
EXPECT_THAT(
light_gray_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 100, 100), Color::kLightGray)));
EXPECT_EQ(Translation(300, 350), light_gray_layer->ScreenSpaceTransform());
const cc::Layer* dark_gray_layer = LayerAt(2);
EXPECT_THAT(
dark_gray_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(0, 0, 100, 100), Color::kDarkGray)));
EXPECT_EQ(Translation(300, 350), dark_gray_layer->ScreenSpaceTransform());
const cc::Layer* black_layer = LayerAt(3);
EXPECT_THAT(
black_layer->GetPicture(),
Pointee(DrawsRectangle(gfx::RectF(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_TRUE(outer_clip->AppliesLocalClip());
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_TRUE(inner_clip->AppliesLocalClip());
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.empty() ? c0() : *clips.back(), t0(),
FloatRoundedRect(5 * i, 0, 100, 200 - 10 * i)));
}
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clips.back(), e0())
.RectDrawing(gfx::Rect(0, 0, 200, 200), Color::kWhite);
Update(artifact.Build());
// Check the drawing layer. It's clipped.
ASSERT_EQ(1u, LayerCount());
const cc::Layer* drawing_layer = LayerAt(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(gfx::RectF(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_TRUE(clip_node->AppliesLocalClip());
EXPECT_EQ(paint_clip_node->PaintClipRect().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, 80, 60));
auto common_clip = ClipPaintPropertyNodeAlias::Create(*real_common_clip);
auto real_clip1 =
CreateClip(*common_clip, t0(), FloatRoundedRect(0, 0, 30, 20));
auto clip1 = ClipPaintPropertyNodeAlias::Create(*real_clip1);
auto real_clip2 =
CreateClip(*common_clip, t0(), FloatRoundedRect(40, 0, 40, 60));
auto clip2 = ClipPaintPropertyNodeAlias::Create(*real_clip2);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(gfx::Rect(0, 0, 11, 22), Color::kWhite);
artifact.Chunk(t0(), *clip2, e0())
.RectDrawing(gfx::Rect(33, 44, 55, 66), Color::kBlack);
Update(artifact.Build());
// The two chunks are merged together.
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(Vector<RectWithColor>{
// This is the first RectDrawing with real_clip1 applied.
RectWithColor(gfx::RectF(0, 0, 11, 20), Color::kWhite),
// This is the second RectDrawing with real_clip2 applied.
RectWithColor(gfx::RectF(40, 44, 40, 16), Color::kBlack)})));
EXPECT_EQ(gfx::Transform(), layer->ScreenSpaceTransform());
const cc::ClipNode* clip_node =
GetPropertyTrees().clip_tree().Node(layer->clip_tree_index());
EXPECT_TRUE(clip_node->AppliesLocalClip());
ASSERT_EQ(gfx::RectF(0, 0, 80, 60), clip_node->clip);
}
TEST_P(PaintArtifactCompositorTest, SiblingClipsWithCompositedTransform) {
auto t1 = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
auto t2 = CreateTransform(*t1, MakeTranslationMatrix(1, 2));
auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0, 0, 400, 600));
auto c2 = CreateClip(c0(), *t2, FloatRoundedRect(400, 0, 400, 600));
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 640, 480), Color::kWhite);
artifact.Chunk(t0(), *c2, e0())
.RectDrawing(gfx::Rect(0, 0, 640, 480), Color::kBlack);
Update(artifact.Build());
// We can't merge the two chunks because their clips have unmergeable
// transforms.
ASSERT_EQ(2u, LayerCount());
}
TEST_P(PaintArtifactCompositorTest, SiblingTransformsWithAlias) {
auto real_common_transform =
CreateTransform(t0(), MakeTranslationMatrix(5, 6));
auto common_transform =
TransformPaintPropertyNodeAlias::Create(*real_common_transform);
auto real_transform1 = CreateTransform(*common_transform, MakeScaleMatrix(2));
auto transform1 = TransformPaintPropertyNodeAlias::Create(*real_transform1);
auto real_transform2 =
CreateTransform(*common_transform, MakeScaleMatrix(0.5));
auto transform2 = TransformPaintPropertyNodeAlias::Create(*real_transform2);
TestPaintArtifact artifact;
artifact.Chunk(*transform1, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 111, 222), Color::kWhite);
artifact.Chunk(*transform2, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 333, 444), Color::kBlack);
Update(artifact.Build());
// The two chunks are merged together.
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(
layer->GetPicture(),
Pointee(DrawsRectangles(Vector<RectWithColor>{
RectWithColor(gfx::RectF(0, 0, 222, 444), Color::kWhite),
RectWithColor(gfx::RectF(0, 0, 166.5, 222), Color::kBlack)})));
gfx::Transform expected_transform;
expected_transform.Translate(5, 6);
EXPECT_EQ(expected_transform, layer->ScreenSpaceTransform());
}
TEST_P(PaintArtifactCompositorTest, SiblingTransformsWithComposited) {
auto t1 = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
auto t2 = CreateTransform(*t1, MakeTranslationMatrix(1, 2));
auto t3 = CreateTransform(t0(), MakeTranslationMatrix(3, 4));
TestPaintArtifact artifact;
artifact.Chunk(*t2, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 640, 480), Color::kWhite);
artifact.Chunk(*t3, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 640, 480), Color::kBlack);
Update(artifact.Build());
// We can't merge the two chunks because their transforms are not mergeable.
ASSERT_EQ(2u, LayerCount());
}
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(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk().ForeignLayer(layer, gfx::Point(50, 60));
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(3u, LayerCount());
EXPECT_EQ(layer, LayerAt(1));
EXPECT_EQ(gfx::Size(400, 300), layer->bounds());
EXPECT_EQ(Translation(50, 60), layer->ScreenSpaceTransform());
}
TEST_P(PaintArtifactCompositorTest, EffectTreeConversionWithAlias) {
Update(TestPaintArtifact()
.Chunk()
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite)
.Build());
auto root_element_id = GetPropertyTrees().effect_tree().Node(1)->element_id;
auto real_effect1 =
CreateOpacityEffect(e0(), t0(), &c0(), 0.5, CompositingReason::kAll);
auto effect1 = EffectPaintPropertyNodeAlias::Create(*real_effect1);
auto real_effect2 =
CreateOpacityEffect(*effect1, 0.3, CompositingReason::kAll);
auto effect2 = EffectPaintPropertyNodeAlias::Create(*real_effect2);
auto real_effect3 = CreateOpacityEffect(e0(), 0.2, CompositingReason::kAll);
auto effect3 = EffectPaintPropertyNodeAlias::Create(*real_effect3);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect2)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk(t0(), c0(), *effect1)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
artifact.Chunk(t0(), c0(), *effect3)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
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_element_id, converted_root_effect.element_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(),
converted_effect1.element_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, LayerAt(0)->effect_tree_index());
EXPECT_EQ(converted_effect1.id, LayerAt(1)->effect_tree_index());
EXPECT_EQ(converted_effect3.id, LayerAt(2)->effect_tree_index());
}
// Returns a RefCountedPropertyTreeState for composited scrolling paint
// properties with some arbitrary values.
static RefCountedPropertyTreeState ScrollState1(
const PropertyTreeState& parent_state = PropertyTreeState::Root(),
CompositingReasons compositing_reasons =
CompositingReason::kOverflowScrolling,
MainThreadScrollingReasons main_thread_reasons = kNotScrollingOnMain) {
return CreateScrollTranslationState(
parent_state, 7, 9, gfx::Rect(3, 5, 11, 13), gfx::Size(27, 31),
compositing_reasons, main_thread_reasons);
}
// Returns a RefCountedPropertyTreeState for composited scrolling paint
// properties with another set of arbitrary values.
static RefCountedPropertyTreeState ScrollState2(
const PropertyTreeState& parent_state = PropertyTreeState::Root(),
CompositingReasons compositing_reasons =
CompositingReason::kOverflowScrolling,
MainThreadScrollingReasons main_thread_reasons = kNotScrollingOnMain) {
return CreateScrollTranslationState(
parent_state, 39, 31, gfx::Rect(0, 0, 19, 23), gfx::Size(27, 31),
compositing_reasons, main_thread_reasons);
}
static void CheckCcScrollNode(const ScrollPaintPropertyNode& blink_scroll,
const cc::ScrollNode& cc_scroll) {
EXPECT_TRUE(cc_scroll.scrollable);
EXPECT_EQ(blink_scroll.ContainerRect().size(), cc_scroll.container_bounds);
EXPECT_EQ(blink_scroll.ContentsRect().size(), 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, OneScrollNodeComposited) {
auto scroll_state = ScrollState1();
auto& scroll = *scroll_state.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state);
artifact.Chunk(scroll_state)
.RectDrawing(gfx::Rect(-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_node.element_id, ScrollableLayerAt(0)->element_id());
EXPECT_EQ(scroll_node.id, ElementIdToScrollNodeIndex(scroll_node.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::PointF(-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(gfx::RectF(0, 0, 57, 19), Color::kWhite)));
auto* scroll_layer = ScrollableLayerAt(0);
// 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);
absl::optional<cc::TargetSnapAreaElementIds> targets;
EXPECT_CALL(
ScrollCallbacks(),
DidCompositorScroll(scroll_node.element_id, gfx::PointF(1, 2), targets));
GetPropertyTrees().scroll_tree_mutable().NotifyDidCompositorScroll(
scroll_node.element_id, gfx::PointF(1, 2), targets);
EXPECT_CALL(ScrollCallbacks(),
DidChangeScrollbarsHidden(scroll_node.element_id, true));
GetPropertyTrees().scroll_tree_mutable().NotifyDidChangeScrollbarsHidden(
scroll_node.element_id, true);
}
TEST_P(PaintArtifactCompositorTest, OneScrollNodeNonComposited) {
auto scroll_state =
ScrollState1(PropertyTreeState::Root(), CompositingReason::kNone);
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state);
artifact.Chunk(scroll_state)
.RectDrawing(gfx::Rect(-110, 12, 170, 19), Color::kWhite);
Update(artifact.Build());
// Blink scroll nodes not referenced by composited transforms don't create
// cc scroll nodes.
if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
// ScrollUnification also creates transform and scroll nodes for
// non-composited scrollers.
EXPECT_EQ(3u, GetPropertyTrees().scroll_tree().size());
EXPECT_EQ(3u, GetPropertyTrees().transform_tree().size());
} else {
EXPECT_EQ(2u, GetPropertyTrees().scroll_tree().size());
EXPECT_EQ(2u, GetPropertyTrees().transform_tree().size());
}
EXPECT_EQ(1u, LayerCount());
}
TEST_P(PaintArtifactCompositorTest, TransformUnderScrollNode) {
auto scroll_state = ScrollState1();
auto transform =
CreateTransform(scroll_state.Transform(), gfx::Transform(),
gfx::Point3F(), CompositingReason::kWillChangeTransform);
TestPaintArtifact artifact;
artifact.Chunk(scroll_state)
.RectDrawing(gfx::Rect(-20, 4, 60, 8), Color::kBlack)
.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(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 = LayerAt(0);
const auto* layer1 = LayerAt(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(gfx::RectF(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(gfx::RectF(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);
auto scroll_state_a = ScrollState1(
PropertyTreeState(t0(), c0(), *effect),
cc::MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
auto& scroll_a = *scroll_state_a.Transform().ScrollNode();
auto scroll_state_b = ScrollState2(scroll_state_a.GetPropertyTreeState());
auto& scroll_b = *scroll_state_b.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state_a);
artifact.Chunk(scroll_state_a)
.RectDrawing(gfx::Rect(7, 11, 13, 17), Color::kWhite);
CreateScrollableChunk(artifact, scroll_state_b);
artifact.Chunk(scroll_state_b)
.RectDrawing(gfx::Rect(1, 2, 3, 5), 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(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_node_a.element_id, ScrollableLayerAt(0)->element_id());
EXPECT_EQ(scroll_node_a.id,
ElementIdToScrollNodeIndex(scroll_node_a.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::PointF(-7, -9), 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_node_b.element_id, ScrollableLayerAt(1)->element_id());
EXPECT_EQ(scroll_node_b.id,
ElementIdToScrollNodeIndex(scroll_node_b.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::PointF(-39, -31), transform_node_b.scroll_offset);
}
TEST_P(PaintArtifactCompositorTest, ScrollHitTestLayerOrder) {
auto scroll_state = ScrollState1();
auto& scroll = *scroll_state.Transform().ScrollNode();
auto transform =
CreateTransform(scroll_state.Transform(), MakeTranslationMatrix(5, 5),
gfx::Point3F(), CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk(scroll_state)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
CreateScrollableChunk(artifact, scroll_state);
artifact.Chunk(*transform, scroll_state.Clip(), scroll_state.Effect())
.RectDrawing(gfx::Rect(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.GetCompositorElementId(), scroll_node->element_id);
EXPECT_EQ(scroll.GetCompositorElementId(),
ScrollableLayerAt(0)->element_id());
EXPECT_TRUE(ScrollableLayerAt(0)->HitTestable());
// 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 scroll_state_1 = ScrollState1();
auto& scroll_1 = *scroll_state_1.Transform().ScrollNode();
auto scroll_state_2 = ScrollState2(scroll_state_1.GetPropertyTreeState());
auto& scroll_2 = *scroll_state_2.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state_1);
CreateScrollableChunk(artifact, scroll_state_2);
artifact.Chunk(scroll_state_2)
.RectDrawing(gfx::Rect(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.GetCompositorElementId(), 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.GetCompositorElementId(), 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(3, 5, 11, 13), 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)));
EXPECT_TRUE(ScrollableLayerAt(0)->HitTestable());
EXPECT_TRUE(ScrollableLayerAt(1)->HitTestable());
}
TEST_P(PaintArtifactCompositorTest, AncestorScrollNodes) {
auto scroll_state_a = ScrollState1();
auto& scroll_a = *scroll_state_a.Transform().ScrollNode();
auto scroll_state_b = ScrollState2(
scroll_state_a.GetPropertyTreeState(), CompositingReason::kNone,
cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText);
auto& scroll_b = *scroll_state_b.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state_a);
CreateScrollableChunk(artifact, scroll_state_b);
Update(artifact.Build());
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree();
const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree();
// Node #0 reserved for null; #1 for root render surface. #2 is for scroll_a.
if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
// ScrollUnification also creates transform and scroll nodes for
// non-composited scrollers.
ASSERT_EQ(4u, scroll_tree.size());
ASSERT_EQ(4u, transform_tree.size());
} else {
// We don't need to create cc scroll node for scroll_b which doesn't use
// composited scrolling.
ASSERT_EQ(3u, scroll_tree.size());
ASSERT_EQ(3u, transform_tree.size());
}
const cc::ScrollNode& scroll_node_a = *scroll_tree.Node(2);
EXPECT_EQ(1, scroll_node_a.parent_id);
EXPECT_EQ(scroll_a.GetCompositorElementId(), scroll_node_a.element_id);
EXPECT_EQ(scroll_node_a.id,
ElementIdToScrollNodeIndex(scroll_node_a.element_id));
// The first scrollable layer should be associated with scroll_a.
EXPECT_EQ(scroll_node_a.element_id, ScrollableLayerAt(0)->element_id());
EXPECT_TRUE(scroll_node_a.is_composited);
const cc::TransformNode& transform_node_a =
*transform_tree.Node(scroll_node_a.transform_id);
EXPECT_TRUE(transform_node_a.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-7, -9), transform_node_a.scroll_offset);
EXPECT_EQ(gfx::PointF(-7, -9),
scroll_tree.current_scroll_offset(scroll_node_a.element_id));
if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
const cc::ScrollNode& scroll_node_b = *scroll_tree.Node(3);
EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
EXPECT_EQ(scroll_b.GetCompositorElementId(), scroll_node_b.element_id);
EXPECT_EQ(scroll_node_b.id,
ElementIdToScrollNodeIndex(scroll_node_b.element_id));
EXPECT_FALSE(scroll_node_b.is_composited);
const cc::TransformNode& transform_node_b =
*transform_tree.Node(scroll_node_b.transform_id);
EXPECT_TRUE(transform_node_b.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-39, -31), transform_node_b.scroll_offset);
EXPECT_EQ(gfx::PointF(-39, -31),
scroll_tree.current_scroll_offset(scroll_node_b.element_id));
}
}
TEST_P(PaintArtifactCompositorTest, AncestorNonCompositedScrollNode) {
auto scroll_state_a =
ScrollState1(PropertyTreeState::Root(), CompositingReason::kNone,
cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText);
auto& scroll_a = *scroll_state_a.Transform().ScrollNode();
auto scroll_state_b = ScrollState2(scroll_state_a.GetPropertyTreeState());
auto& scroll_b = *scroll_state_b.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state_a);
CreateScrollableChunk(artifact, scroll_state_b);
Update(artifact.Build());
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree();
const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree();
// Node #0 reserved for null; #1 for root render surface. #2 is for scroll_a
// #3 for scroll_b.
ASSERT_EQ(4u, scroll_tree.size());
ASSERT_EQ(4u, transform_tree.size());
const cc::ScrollNode& scroll_node_a = *scroll_tree.Node(2);
EXPECT_EQ(1, scroll_node_a.parent_id);
EXPECT_EQ(scroll_a.GetCompositorElementId(), scroll_node_a.element_id);
EXPECT_EQ(scroll_node_a.id,
ElementIdToScrollNodeIndex(scroll_node_a.element_id));
EXPECT_FALSE(scroll_node_a.is_composited);
const cc::TransformNode& transform_node_a =
*transform_tree.Node(scroll_node_a.transform_id);
EXPECT_TRUE(transform_node_a.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-7, -9), transform_node_a.scroll_offset);
EXPECT_EQ(gfx::PointF(-7, -9),
scroll_tree.current_scroll_offset(scroll_node_a.element_id));
const cc::ScrollNode& scroll_node_b = *scroll_tree.Node(3);
EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
EXPECT_EQ(scroll_b.GetCompositorElementId(), scroll_node_b.element_id);
EXPECT_EQ(scroll_node_b.id,
ElementIdToScrollNodeIndex(scroll_node_b.element_id));
// The first scrollable layer should be associated with scroll_b.
EXPECT_EQ(scroll_node_b.element_id, ScrollableLayerAt(0)->element_id());
EXPECT_TRUE(scroll_node_b.is_composited);
const cc::TransformNode& transform_node_b =
*transform_tree.Node(scroll_node_b.transform_id);
EXPECT_TRUE(transform_node_b.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-39, -31), transform_node_b.scroll_offset);
EXPECT_EQ(gfx::PointF(-39, -31),
scroll_tree.current_scroll_offset(scroll_node_b.element_id));
}
// If a scroll node is encountered before its parent, ensure the parent scroll
// node is correctly created.
TEST_P(PaintArtifactCompositorTest, AncestorScrollNodesInversedOrder) {
auto scroll_state_a = ScrollState1();
auto& scroll_a = *scroll_state_a.Transform().ScrollNode();
auto scroll_state_b = ScrollState2(scroll_state_a.GetPropertyTreeState());
auto& scroll_b = *scroll_state_b.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state_b);
CreateScrollableChunk(artifact, scroll_state_a);
Update(artifact.Build());
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree();
const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree();
// Node #0 reserved for null; #1 for root render surface. #2 is for scroll_a.
// #3 is for scroll_b.
ASSERT_EQ(4u, scroll_tree.size());
ASSERT_EQ(4u, transform_tree.size());
const cc::ScrollNode& scroll_node_a = *scroll_tree.Node(2);
EXPECT_EQ(1, scroll_node_a.parent_id);
EXPECT_EQ(scroll_a.GetCompositorElementId(), scroll_node_a.element_id);
EXPECT_EQ(scroll_node_a.id,
ElementIdToScrollNodeIndex(scroll_node_a.element_id));
// The second scrollable layer should be associated with scroll_a.
EXPECT_EQ(scroll_node_a.element_id, ScrollableLayerAt(1)->element_id());
EXPECT_TRUE(scroll_node_a.is_composited);
const cc::TransformNode& transform_node_a =
*transform_tree.Node(scroll_node_a.transform_id);
EXPECT_TRUE(transform_node_a.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-7, -9), transform_node_a.scroll_offset);
EXPECT_EQ(gfx::PointF(-7, -9),
scroll_tree.current_scroll_offset(scroll_node_a.element_id));
const cc::ScrollNode& scroll_node_b = *scroll_tree.Node(3);
EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
EXPECT_EQ(scroll_b.GetCompositorElementId(), scroll_node_b.element_id);
EXPECT_EQ(scroll_node_b.id,
ElementIdToScrollNodeIndex(scroll_node_b.element_id));
// The first scrollable layer should be associated with scroll_b.
EXPECT_EQ(scroll_node_b.element_id, ScrollableLayerAt(0)->element_id());
EXPECT_TRUE(scroll_node_b.is_composited);
const cc::TransformNode& transform_node_b =
*transform_tree.Node(scroll_node_b.transform_id);
EXPECT_TRUE(transform_node_b.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-39, -31), transform_node_b.scroll_offset);
EXPECT_EQ(gfx::PointF(-39, -31),
scroll_tree.current_scroll_offset(scroll_node_b.element_id));
}
TEST_P(PaintArtifactCompositorTest,
DifferentTransformTreeAndScrollTreeHierarchy) {
auto scroll_state_a = ScrollState1();
auto& scroll_a = *scroll_state_a.Transform().ScrollNode();
auto scroll_state_b = ScrollState2(scroll_state_a.GetPropertyTreeState());
auto& scroll_b = *scroll_state_b.Transform().ScrollNode();
// scroll_state_c's has root transform space, while the scroll parent is
// scroll_b.
auto scroll_state_c = CreateCompositedScrollTranslationState(
PropertyTreeState::Root(), scroll_b, 11, 22, gfx::Rect(0, 0, 10, 20),
gfx::Size(50, 60));
auto& scroll_c = *scroll_state_c.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state_a);
CreateScrollableChunk(artifact, scroll_state_b);
CreateScrollableChunk(artifact, scroll_state_c);
Update(artifact.Build());
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree();
const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree();
// Node #0 reserved for null; #1 for root render surface. #2 is for scroll_a.
// #3 is for scroll_b. #4 is for scroll_c.
ASSERT_EQ(5u, scroll_tree.size());
ASSERT_EQ(5u, transform_tree.size());
const cc::ScrollNode& scroll_node_a = *scroll_tree.Node(2);
EXPECT_EQ(1, scroll_node_a.parent_id);
EXPECT_EQ(scroll_a.GetCompositorElementId(), scroll_node_a.element_id);
EXPECT_EQ(scroll_node_a.id,
ElementIdToScrollNodeIndex(scroll_node_a.element_id));
EXPECT_EQ(scroll_node_a.element_id, ScrollableLayerAt(0)->element_id());
const cc::TransformNode& transform_node_a =
*transform_tree.Node(scroll_node_a.transform_id);
EXPECT_TRUE(transform_node_a.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-7, -9), transform_node_a.scroll_offset);
EXPECT_EQ(gfx::PointF(-7, -9),
scroll_tree.current_scroll_offset(scroll_node_a.element_id));
const cc::ScrollNode& scroll_node_b = *scroll_tree.Node(3);
EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
EXPECT_EQ(scroll_b.GetCompositorElementId(), scroll_node_b.element_id);
EXPECT_EQ(scroll_node_b.id,
ElementIdToScrollNodeIndex(scroll_node_b.element_id));
EXPECT_EQ(scroll_node_b.element_id, ScrollableLayerAt(1)->element_id());
const cc::TransformNode& transform_node_b =
*transform_tree.Node(scroll_node_b.transform_id);
EXPECT_TRUE(transform_node_b.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-39, -31), transform_node_b.scroll_offset);
EXPECT_EQ(gfx::PointF(-39, -31),
scroll_tree.current_scroll_offset(scroll_node_b.element_id));
const cc::ScrollNode& scroll_node_c = *scroll_tree.Node(4);
EXPECT_EQ(scroll_node_b.id, scroll_node_c.parent_id);
EXPECT_EQ(scroll_c.GetCompositorElementId(), scroll_node_c.element_id);
EXPECT_EQ(scroll_node_c.id,
ElementIdToScrollNodeIndex(scroll_node_c.element_id));
EXPECT_EQ(scroll_node_c.element_id, ScrollableLayerAt(2)->element_id());
const cc::TransformNode& transform_node_c =
*transform_tree.Node(scroll_node_c.transform_id);
EXPECT_EQ(1, transform_node_c.parent_id);
EXPECT_TRUE(transform_node_c.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-11, -22), transform_node_c.scroll_offset);
EXPECT_EQ(gfx::PointF(-11, -22),
scroll_tree.current_scroll_offset(scroll_node_c.element_id));
}
TEST_P(PaintArtifactCompositorTest,
DifferentTransformTreeAndScrollTreeHierarchyInversedOrder) {
auto scroll_state_a = ScrollState1();
auto& scroll_a = *scroll_state_a.Transform().ScrollNode();
auto scroll_state_b = ScrollState2(scroll_state_a.GetPropertyTreeState());
auto& scroll_b = *scroll_state_b.Transform().ScrollNode();
// scroll_state_c's has root transform space, while the scroll parent is
// scroll_b.
auto scroll_state_c = CreateCompositedScrollTranslationState(
PropertyTreeState::Root(), scroll_b, 11, 22, gfx::Rect(0, 0, 10, 20),
gfx::Size(50, 60));
auto& scroll_c = *scroll_state_c.Transform().ScrollNode();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state_c);
CreateScrollableChunk(artifact, scroll_state_b);
CreateScrollableChunk(artifact, scroll_state_a);
Update(artifact.Build());
const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree();
const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree();
// Node #0 reserved for null; #1 for root render surface. #2 is for scroll_a.
// #3 is for scroll_b. #4 is for scroll_c.
ASSERT_EQ(5u, scroll_tree.size());
ASSERT_EQ(5u, transform_tree.size());
const cc::ScrollNode& scroll_node_a = *scroll_tree.Node(2);
EXPECT_EQ(1, scroll_node_a.parent_id);
EXPECT_EQ(scroll_a.GetCompositorElementId(), scroll_node_a.element_id);
EXPECT_EQ(scroll_node_a.id,
ElementIdToScrollNodeIndex(scroll_node_a.element_id));
EXPECT_EQ(scroll_node_a.element_id, ScrollableLayerAt(2)->element_id());
const cc::TransformNode& transform_node_a =
*transform_tree.Node(scroll_node_a.transform_id);
EXPECT_TRUE(transform_node_a.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-7, -9), transform_node_a.scroll_offset);
EXPECT_EQ(gfx::PointF(-7, -9),
scroll_tree.current_scroll_offset(scroll_node_a.element_id));
const cc::ScrollNode& scroll_node_b = *scroll_tree.Node(3);
EXPECT_EQ(scroll_node_a.id, scroll_node_b.parent_id);
EXPECT_EQ(scroll_b.GetCompositorElementId(), scroll_node_b.element_id);
EXPECT_EQ(scroll_node_b.id,
ElementIdToScrollNodeIndex(scroll_node_b.element_id));
EXPECT_EQ(scroll_node_b.element_id, ScrollableLayerAt(1)->element_id());
const cc::TransformNode& transform_node_b =
*transform_tree.Node(scroll_node_b.transform_id);
EXPECT_TRUE(transform_node_b.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-39, -31), transform_node_b.scroll_offset);
EXPECT_EQ(gfx::PointF(-39, -31),
scroll_tree.current_scroll_offset(scroll_node_b.element_id));
const cc::ScrollNode& scroll_node_c = *scroll_tree.Node(4);
EXPECT_EQ(scroll_node_b.id, scroll_node_c.parent_id);
EXPECT_EQ(scroll_c.GetCompositorElementId(), scroll_node_c.element_id);
EXPECT_EQ(scroll_node_c.id,
ElementIdToScrollNodeIndex(scroll_node_c.element_id));
EXPECT_EQ(scroll_node_c.element_id, ScrollableLayerAt(0)->element_id());
const cc::TransformNode& transform_node_c =
*transform_tree.Node(scroll_node_c.transform_id);
EXPECT_EQ(1, transform_node_c.parent_id);
EXPECT_TRUE(transform_node_c.local.IsIdentity());
EXPECT_EQ(gfx::PointF(-11, -22), transform_node_c.scroll_offset);
EXPECT_EQ(gfx::PointF(-11, -22),
scroll_tree.current_scroll_offset(scroll_node_c.element_id));
}
TEST_P(PaintArtifactCompositorTest, MergeSimpleChunks) {
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(2u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(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(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, e0())
.RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 300, 400), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// Clip is applied to this PaintChunk.
rects_with_color.push_back(
RectWithColor(gfx::RectF(10, 20, 50, 60), Color::kBlack));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 300, 400), Color::kGray));
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
}
}
TEST_P(PaintArtifactCompositorTest, Merge2DTransform) {
auto transform = CreateTransform(t0(), MakeTranslationMatrix(50, 50),
gfx::Point3F(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// Transform is applied to this PaintChunk.
rects_with_color.push_back(
RectWithColor(gfx::RectF(50, 50, 100, 100), Color::kBlack));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
}
}
TEST_P(PaintArtifactCompositorTest, Merge2DTransformDirectAncestor) {
auto transform = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
CompositingReason::k3DTransform);
auto transform2 = CreateTransform(*transform, MakeTranslationMatrix(50, 50),
gfx::Point3F(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(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(gfx::Rect(0, 0, 100, 100), Color::kBlack);
auto artifact = test_artifact.Build();
ASSERT_EQ(2u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// Transform is applied to this PaintChunk.
rects_with_color.push_back(
RectWithColor(gfx::RectF(50, 50, 100, 100), Color::kBlack));
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
}
}
TEST_P(PaintArtifactCompositorTest, MergeTransformOrigin) {
auto transform =
CreateTransform(t0(), MakeRotationMatrix(45), gfx::Point3F(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 42, 100, 100), Color::kWhite));
// Transform is applied to this PaintChunk.
rects_with_color.push_back(RectWithColor(
gfx::RectF(29.2893, 0.578644, 141.421, 141.421), Color::kBlack));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 42, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(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(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// Transform is applied to this PaintChunk.
Color semi_transparent_black = Color::FromSkColor4f({0, 0, 0, opacity});
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), semi_transparent_black));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(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 = EffectPaintPropertyNodeAlias::Create(*real_effect);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// Transform is applied to this PaintChunk.
Color semi_transparent_black = Color::FromSkColor4f({0, 0, 0, opacity});
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), semi_transparent_black));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(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(), MakeTranslationMatrix(50, 50),
gfx::Point3F(100, 100, 0));
auto transform = TransformPaintPropertyNodeAlias::Create(*real_transform);
auto real_clip =
CreateClip(c0(), *transform, FloatRoundedRect(10, 20, 50, 60));
auto clip = ClipPaintPropertyNodeAlias::Create(*real_clip);
float opacity = 2.0 / 255.0;
auto real_effect = CreateOpacityEffect(e0(), *transform, clip.get(), opacity);
auto effect = EffectPaintPropertyNodeAlias::Create(*real_effect);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, *clip, *effect)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// Transform is applied to this PaintChunk.
Color semi_transparent_black = Color::FromSkColor4f({0, 0, 0, opacity});
rects_with_color.push_back(
RectWithColor(gfx::RectF(60, 70, 50, 60), semi_transparent_black));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(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(), MakeTranslationMatrix(20, 25),
gfx::Point3F(100, 100, 0));
auto transform2 = CreateTransform(*transform, MakeTranslationMatrix(20, 25),
gfx::Point3F(100, 100, 0));
auto clip = CreateClip(c0(), *transform2, FloatRoundedRect(10, 20, 50, 60));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, e0())
.RectDrawing(gfx::Rect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(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(gfx::RectF(50, 70, 50, 60), Color(Color::kBlack)));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
}
}
TEST_P(PaintArtifactCompositorTest, 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(), MakeTranslationMatrix(20, 25),
gfx::Point3F(100, 100, 0));
auto transform2 = CreateTransform(*transform, MakeTranslationMatrix(20, 25),
gfx::Point3F(100, 100, 0));
float opacity = 2.0 / 255.0;
auto effect = CreateOpacityEffect(e0(), *transform2, &c0(), opacity);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
Color semi_transparent_black = Color::FromSkColor4f({0, 0, 0, opacity});
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 300, 400), semi_transparent_black));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
}
}
TEST_P(PaintArtifactCompositorTest, 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(), MakeTranslationMatrix(20, 25),
gfx::Point3F(100, 100, 0));
auto transform2 = CreateTransform(*transform, MakeTranslationMatrix(20, 25),
gfx::Point3F(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(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, *effect)
.RectDrawing(gfx::Rect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// The clip is under |transform| but not |transform2|, so only an adjustment
// of (20, 25) occurs.
Color semi_transparent_black = Color::FromSkColor4f({0, 0, 0, opacity});
rects_with_color.push_back(
RectWithColor(gfx::RectF(30, 45, 50, 60), semi_transparent_black));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(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(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip, *effect)
.RectDrawing(gfx::Rect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
Color semi_transparent_black = Color::FromSkColor4f({0, 0, 0, opacity});
rects_with_color.push_back(
RectWithColor(gfx::RectF(10, 20, 50, 60), semi_transparent_black));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(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(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(t0(), *clip2, e0())
.RectDrawing(gfx::Rect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
// The interesction of the two clips is (20, 30, 10, 20).
rects_with_color.push_back(
RectWithColor(gfx::RectF(20, 30, 10, 20), Color(Color::kBlack)));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
}
}
TEST_P(PaintArtifactCompositorTest, TwoTransformsClipBetween) {
auto transform = CreateTransform(t0(), MakeTranslationMatrix(20, 25),
gfx::Point3F(100, 100, 0));
auto clip = CreateClip(c0(), t0(), FloatRoundedRect(0, 0, 50, 60));
auto transform2 = CreateTransform(*transform, MakeTranslationMatrix(20, 25),
gfx::Point3F(100, 100, 0));
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform2, *clip, e0())
.RectDrawing(gfx::Rect(0, 0, 300, 400), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 200, 300), Color::kGray);
auto artifact = test_artifact.Build();
ASSERT_EQ(3u, artifact->PaintChunks().size());
Update(artifact);
ASSERT_EQ(1u, LayerCount());
{
Vector<RectWithColor> rects_with_color;
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 100, 100), Color::kWhite));
rects_with_color.push_back(
RectWithColor(gfx::RectF(40, 50, 10, 10), Color(Color::kBlack)));
rects_with_color.push_back(
RectWithColor(gfx::RectF(0, 0, 200, 300), Color::kGray));
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(layer->GetPicture(),
Pointee(DrawsRectangles(rects_with_color)));
}
}
TEST_P(PaintArtifactCompositorTest, OverlapTransform) {
auto transform = CreateTransform(t0(), MakeTranslationMatrix(50, 50),
gfx::Point3F(100, 100, 0),
CompositingReason::k3DTransform);
TestPaintArtifact test_artifact;
test_artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
test_artifact.Chunk().RectDrawing(gfx::Rect(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, LayerCount());
}
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{{MakeRotationMatrix(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(gfx::Rect(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(gfx::Rect(100, 100, 200, 100), Color::kBlack);
Update(artifact.Build());
EXPECT_EQ(2, ElementIdToEffectNodeIndex(effect->GetCompositorElementId()));
}
TEST_P(PaintArtifactCompositorTest, NonContiguousEffectWithElementId) {
auto effect = CreateSampleEffectNodeWithElementId();
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Chunk(t0(), c0(), e0())
.RectDrawing(gfx::Rect(100, 100, 300, 100), Color::kBlack)
.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(100, 100, 400, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
EXPECT_EQ(2, LayerAt(0)->effect_tree_index());
cc::ElementId element_id = effect->GetCompositorElementId();
EXPECT_EQ(element_id, GetPropertyTrees()
.effect_tree()
.Node(LayerAt(0)->effect_tree_index())
->element_id);
EXPECT_EQ(1, LayerAt(1)->effect_tree_index());
EXPECT_EQ(3, LayerAt(2)->effect_tree_index());
EXPECT_NE(element_id, GetPropertyTrees()
.effect_tree()
.Node(LayerAt(2)->effect_tree_index())
->element_id);
}
TEST_P(PaintArtifactCompositorTest, EffectWithElementIdWithAlias) {
auto real_effect = CreateSampleEffectNodeWithElementId();
auto effect = EffectPaintPropertyNodeAlias::Create(*real_effect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(100, 100, 200, 100), Color::kBlack);
Update(artifact.Build());
EXPECT_EQ(2,
ElementIdToEffectNodeIndex(real_effect->GetCompositorElementId()));
}
TEST_P(PaintArtifactCompositorTest, NonCompositedSimpleMask) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kDstIn;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(0);
EXPECT_THAT(*layer->GetPicture(),
DrawsRectangles(Vector<RectWithColor>{
RectWithColor(gfx::RectF(0, 0, 200, 200), Color::kGray),
RectWithColor(gfx::RectF(50, 50, 100, 100), Color::kWhite)}));
EXPECT_EQ(Translation(100, 100), layer->ScreenSpaceTransform());
EXPECT_EQ(gfx::Size(200, 200), layer->bounds());
const cc::EffectNode* masked_group =
GetPropertyTrees().effect_tree().Node(layer->effect_tree_index());
EXPECT_FALSE(masked_group->HasRenderSurface());
EXPECT_EQ(SkBlendMode::kSrcOver, masked_group->blend_mode);
EXPECT_TRUE(masked_group->filters.IsEmpty());
// It's the last effect node. |masking| has been decomposited.
EXPECT_EQ(masked_group, GetPropertyTrees().effect_tree().back());
}
TEST_P(PaintArtifactCompositorTest, CompositedMaskOneChild) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kDstIn;
masking_state.direct_compositing_reasons =
CompositingReason::kWillChangeOpacity;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 200, 200))
.HasText()
.TextKnownToBeOnOpaqueBackground();
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
// Composited mask doesn't affect opaqueness of the masked layer.
const cc::Layer* masked_layer = LayerAt(0);
EXPECT_TRUE(masked_layer->contents_opaque());
EXPECT_TRUE(masked_layer->contents_opaque_for_text());
const cc::Layer* masking_layer = LayerAt(1);
const cc::EffectNode* masking_group =
GetPropertyTrees().effect_tree().Node(masking_layer->effect_tree_index());
// Render surface is not needed for one child.
EXPECT_FALSE(masking_group->HasRenderSurface());
EXPECT_EQ(SkBlendMode::kDstIn, masking_group->blend_mode);
// The parent also has a render surface to define the scope of the backdrop
// of the kDstIn blend mode.
EXPECT_TRUE(GetPropertyTrees()
.effect_tree()
.parent(masking_group)
->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, NonCompositedMaskClearsOpaqueness) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kDstIn;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 200, 200))
.HasText()
.TextKnownToBeOnOpaqueBackground();
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
// Non-composited mask clears opaqueness status of the masked layer.
const cc::Layer* layer = LayerAt(0);
EXPECT_EQ(gfx::Size(200, 200), layer->bounds());
EXPECT_FALSE(layer->contents_opaque());
EXPECT_FALSE(layer->contents_opaque_for_text());
}
TEST_P(PaintArtifactCompositorTest, CompositedMaskTwoChildren) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kDstIn;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
auto child_of_masked =
CreateOpacityEffect(*masking, 1.0, CompositingReason::kWillChangeOpacity);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *child_of_masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
const cc::Layer* masking_layer = LayerAt(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());
EXPECT_EQ(SkBlendMode::kDstIn, masking_group->blend_mode);
// The parent also has a render surface to define the scope of the backdrop
// of the kDstIn blend mode.
EXPECT_TRUE(GetPropertyTrees()
.effect_tree()
.parent(masking_group)
->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, NonCompositedSimpleExoticBlendMode) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kXor;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(0);
const cc::EffectNode* group =
GetPropertyTrees().effect_tree().Node(layer->effect_tree_index());
EXPECT_FALSE(group->HasRenderSurface());
EXPECT_EQ(SkBlendMode::kSrcOver, group->blend_mode);
// It's the last effect node. |masking| has been decomposited.
EXPECT_EQ(group, GetPropertyTrees().effect_tree().back());
}
TEST_P(PaintArtifactCompositorTest, ForcedCompositedExoticBlendMode) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
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::kOverlap;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
const cc::Layer* masking_layer = LayerAt(1);
const cc::EffectNode* masking_group =
GetPropertyTrees().effect_tree().Node(masking_layer->effect_tree_index());
EXPECT_EQ(SkBlendMode::kXor, masking_group->blend_mode);
// This requires a render surface.
EXPECT_TRUE(masking_group->HasRenderSurface());
// The parent also requires a render surface to define the backdrop scope of
// the blend mode.
EXPECT_TRUE(GetPropertyTrees()
.effect_tree()
.parent(masking_group)
->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest,
CompositedExoticBlendModeOnTwoOpacityAnimationLayers) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
auto masked_child1 = CreateOpacityEffect(
*masked, 1.0, CompositingReason::kActiveOpacityAnimation);
auto masked_child2 = CreateOpacityEffect(
*masked, 1.0, CompositingReason::kActiveOpacityAnimation);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kXor;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *masked_child1)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(t0(), c0(), *masked_child2)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kBlack);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
const cc::Layer* masking_layer = LayerAt(2);
const cc::EffectNode* masking_group =
GetPropertyTrees().effect_tree().Node(masking_layer->effect_tree_index());
EXPECT_EQ(SkBlendMode::kXor, masking_group->blend_mode);
// This requires a render surface.
EXPECT_TRUE(masking_group->HasRenderSurface());
// The parent also requires a render surface to define the backdrop scope of
// the blend mode.
EXPECT_TRUE(GetPropertyTrees()
.effect_tree()
.parent(masking_group)
->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest,
CompositedExoticBlendModeOnTwo3DTransformLayers) {
auto masked =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
auto transform1 = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
CompositingReason::k3DTransform);
auto transform2 = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
CompositingReason::k3DTransform);
EffectPaintPropertyNode::State masking_state;
masking_state.local_transform_space = &t0();
masking_state.output_clip = &c0();
masking_state.blend_mode = SkBlendMode::kXor;
auto masking =
EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
TestPaintArtifact artifact;
artifact.Chunk(*transform1, c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray);
artifact.Chunk(*transform2, c0(), *masked)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kBlack);
artifact.Chunk(t0(), c0(), *masking)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
const cc::Layer* masking_layer = LayerAt(2);
const cc::EffectNode* masking_group =
GetPropertyTrees().effect_tree().Node(masking_layer->effect_tree_index());
EXPECT_EQ(SkBlendMode::kXor, masking_group->blend_mode);
// This requires a render surface.
EXPECT_TRUE(masking_group->HasRenderSurface());
// The parent also requires a render surface to define the backdrop scope of
// the blend mode.
EXPECT_TRUE(GetPropertyTrees()
.effect_tree()
.parent(masking_group)
->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, DecompositeExoticBlendModeWithoutBackdrop) {
auto parent_effect =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
EffectPaintPropertyNode::State blend_state1;
blend_state1.local_transform_space = &t0();
blend_state1.blend_mode = SkBlendMode::kScreen;
auto blend_effect1 =
EffectPaintPropertyNode::Create(*parent_effect, std::move(blend_state1));
EffectPaintPropertyNode::State blend_state2;
blend_state2.local_transform_space = &t0();
blend_state2.blend_mode = SkBlendMode::kScreen;
auto blend_effect2 =
EffectPaintPropertyNode::Create(*parent_effect, std::move(blend_state2));
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *blend_effect1)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray)
.Chunk(t0(), c0(), *blend_effect2)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
const auto* effect =
GetPropertyTrees().effect_tree().Node(LayerAt(0)->effect_tree_index());
EXPECT_EQ(1.0f, effect->opacity);
EXPECT_EQ(SkBlendMode::kSrcOver, effect->blend_mode);
// Don't need a render surface because all blend effects are decomposited.
EXPECT_FALSE(effect->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest,
DecompositeExoticBlendModeWithNonDrawingLayer) {
auto parent_effect =
CreateOpacityEffect(e0(), 1.0, CompositingReason::kWillChangeOpacity);
EffectPaintPropertyNode::State blend_state1;
blend_state1.local_transform_space = &t0();
blend_state1.blend_mode = SkBlendMode::kScreen;
auto blend_effect1 =
EffectPaintPropertyNode::Create(*parent_effect, std::move(blend_state1));
EffectPaintPropertyNode::State blend_state2;
blend_state2.local_transform_space = &t0();
blend_state2.blend_mode = SkBlendMode::kScreen;
auto blend_effect2 =
EffectPaintPropertyNode::Create(*parent_effect, std::move(blend_state2));
auto transform = CreateAnimatingTransform(t0());
Update(TestPaintArtifact()
.Chunk(*transform, c0(), *parent_effect)
.Bounds(gfx::Rect(0, 0, 33, 44))
.Chunk(t0(), c0(), *blend_effect1)
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kGray)
.Chunk(t0(), c0(), *blend_effect2)
.RectDrawing(gfx::Rect(200, 200, 200, 200), Color::kBlack)
.Build());
ASSERT_EQ(2u, LayerCount());
// This is the empty layer forced by |transform|.
EXPECT_EQ(gfx::Size(33, 44), LayerAt(0)->bounds());
EXPECT_FALSE(LayerAt(0)->draws_content());
// This is the layer containing the paint chunks with |blend_effect1| and
// |blend_effect2| decomposited.
EXPECT_EQ(gfx::Size(300, 300), LayerAt(1)->bounds());
const auto* effect =
GetPropertyTrees().effect_tree().Node(LayerAt(1)->effect_tree_index());
EXPECT_EQ(1.0f, effect->opacity);
EXPECT_EQ(SkBlendMode::kSrcOver, effect->blend_mode);
// Don't need a render surface because all blend effects are decomposited.
EXPECT_FALSE(effect->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, UpdateProducesNewSequenceNumber) {
// A 90 degree clockwise rotation about (100, 100).
auto transform =
CreateTransform(t0(), MakeRotationMatrix(90), gfx::Point3F(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(gfx::Rect(0, 0, 100, 100), Color::kWhite);
test_artifact.Chunk().RectDrawing(gfx::Rect(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(gfx::Rect(50, 50, 100, 100), Color::kGray);
artifact.Chunk(t0(), *clip, e0())
.RectDrawing(gfx::Rect(100, 100, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(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(gfx::Rect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(25, 75, 100, 100), Color::kGray);
artifact.Chunk().RectDrawing(gfx::Rect(75, 75, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(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(gfx::Rect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(25, 75, 100, 100), Color::kGray);
artifact.Chunk().RectDrawing(gfx::Rect(75, 75, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
const cc::Layer* layer1 = LayerAt(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 = LayerAt(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 = LayerAt(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(gfx::Rect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect3)
.RectDrawing(gfx::Rect(25, 75, 100, 100), Color::kGray);
artifact.Chunk().RectDrawing(gfx::Rect(75, 75, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
const cc::Layer* layer1 = LayerAt(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 = LayerAt(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 = LayerAt(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(), gfx::Transform(), gfx::Point3F(),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(gfx::Rect(50, 25, 100, 100), Color::kGray);
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(25, 75, 100, 100), Color::kGray);
artifact.Chunk(*transform, c0(), *effect)
.RectDrawing(gfx::Rect(75, 75, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
const cc::Layer* layer1 = LayerAt(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 = LayerAt(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 = LayerAt(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(), gfx::Transform(), gfx::Point3F(),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(gfx::Rect(0, 0, 40, 40), Color::kGray);
artifact.Chunk(t0(), c0(), *effect1)
.RectDrawing(gfx::Rect(50, 0, 40, 40), Color::kGray);
// This chunk has a transform that must be composited, thus causing effect1
// to be composited too.
artifact.Chunk(*transform, c0(), *effect1)
.RectDrawing(gfx::Rect(100, 0, 40, 40), Color::kGray);
artifact.Chunk(t0(), c0(), *effect2)
.RectDrawing(gfx::Rect(100, 50, 40, 40), 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(gfx::Rect(50, 0, 40, 40), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(4u, LayerCount());
const cc::Layer* layer1 = LayerAt(0);
EXPECT_EQ(gfx::Vector2dF(0.f, 0.f), layer1->offset_to_transform_parent());
EXPECT_EQ(gfx::Size(40, 40), layer1->bounds());
EXPECT_EQ(1, layer1->effect_tree_index());
const cc::Layer* layer2 = LayerAt(1);
EXPECT_EQ(gfx::Vector2dF(50.f, 0.f), layer2->offset_to_transform_parent());
EXPECT_EQ(gfx::Size(40, 40), 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 = LayerAt(2);
EXPECT_EQ(gfx::Vector2dF(100.f, 0.f), layer3->offset_to_transform_parent());
EXPECT_EQ(gfx::Size(40, 40), layer3->bounds());
EXPECT_EQ(effect_node->id, layer3->effect_tree_index());
const cc::Layer* layer4 = LayerAt(3);
EXPECT_EQ(gfx::Vector2dF(50.f, 0.f), layer4->offset_to_transform_parent());
EXPECT_EQ(gfx::Size(90, 90), layer4->bounds());
EXPECT_EQ(1, layer4->effect_tree_index());
}
TEST_P(PaintArtifactCompositorTest, EffectivelyInvisibleChunk) {
UpdateWithEffectivelyInvisibleChunk(false, false);
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(10, 10), LayerAt(0)->bounds());
EXPECT_FALSE(LayerAt(0)->draws_content());
EXPECT_FALSE(LayerAt(0)->GetPicture());
}
TEST_P(PaintArtifactCompositorTest, EffectivelyInvisibleSolidColorChunk) {
TestPaintArtifact artifact;
artifact.Chunk()
.EffectivelyInvisible()
.RectDrawing(gfx::Rect(10, 0, 10, 10), Color(255, 0, 0))
.IsSolidColor();
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(10, 10), LayerAt(0)->bounds());
if (RuntimeEnabledFeatures::SolidColorLayersEnabled()) {
EXPECT_TRUE(LayerAt(0)->IsSolidColorLayerForTesting());
}
EXPECT_FALSE(LayerAt(0)->draws_content());
EXPECT_FALSE(LayerAt(0)->GetPicture());
}
TEST_P(PaintArtifactCompositorTest,
EffectivelyInvisibleChunkWithPrecedingChunk) {
UpdateWithEffectivelyInvisibleChunk(true, false);
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(20, 10), LayerAt(0)->bounds());
EXPECT_TRUE(LayerAt(0)->draws_content());
EXPECT_THAT(LayerAt(0)->GetPicture(),
Pointee(DrawsRectangles(
{RectWithColor(gfx::RectF(0, 0, 10, 10), Color::kBlack)})));
}
TEST_P(PaintArtifactCompositorTest,
EffectivelyInvisibleChunkWithSubsequentChunk) {
UpdateWithEffectivelyInvisibleChunk(false, true);
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(20, 20), LayerAt(0)->bounds());
EXPECT_TRUE(LayerAt(0)->draws_content());
EXPECT_THAT(LayerAt(0)->GetPicture(),
Pointee(DrawsRectangles(
{RectWithColor(gfx::RectF(0, 10, 10, 10), Color::kWhite)})));
}
TEST_P(PaintArtifactCompositorTest,
EffectivelyInvisibleChunkWithPrecedingAndSubsequentChunks) {
UpdateWithEffectivelyInvisibleChunk(true, true);
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(20, 20), LayerAt(0)->bounds());
EXPECT_TRUE(LayerAt(0)->draws_content());
EXPECT_THAT(LayerAt(0)->GetPicture(),
Pointee(DrawsRectangles(
{RectWithColor(gfx::RectF(0, 0, 10, 10), Color::kBlack),
RectWithColor(gfx::RectF(0, 10, 10, 10), Color::kWhite)})));
}
TEST_P(PaintArtifactCompositorTest, UpdateManagesLayerElementIds) {
auto transform = CreateAnimatingTransform(t0());
CompositorElementId element_id = transform->GetCompositorElementId();
{
TestPaintArtifact artifact;
artifact.Chunk(*transform, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
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, LayerCount());
ASSERT_FALSE(GetLayerTreeHost().IsElementInPropertyTrees(
element_id, cc::ElementListType::ACTIVE));
}
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClipSimple) {
// This tests the simplest case that a single layer needs to be clipped
// by a single composited rounded clip.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content0
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer.
ASSERT_EQ(1u, LayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = LayerAt(0);
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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClipRotatedNotSupported) {
// Synthesized clips are not currently supported when rotated (or any
// transform that is not 2D axis-aligned).
auto transform =
CreateTransform(t0(), MakeRotationMatrix(45), gfx::Point3F(100, 100, 0),
CompositingReason::k3DTransform);
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), *transform, rrect);
TestPaintArtifact artifact;
artifact.Chunk(*transform, *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// clip_mask0
// content0 [ mask_effect_0 ]
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer.
ASSERT_EQ(2u, LayerCount());
ASSERT_EQ(1u, SynthesizedClipLayerCount());
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* clip_mask0 = LayerAt(1);
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_TRUE(clip_mask0->draws_content());
EXPECT_TRUE(clip_mask0->HitTestable());
EXPECT_EQ(gfx::Size(306, 206), clip_mask0->bounds());
EXPECT_EQ(gfx::Vector2dF(47, 47), clip_mask0->offset_to_transform_parent());
// c1 should be applied in the clip mask layer.
EXPECT_EQ(c0_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);
// Render surface is not needed for DstIn controlling only one layer.
EXPECT_FALSE(mask_effect_0.HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClip90DegRotationSupported) {
// 90-degree rotations are axis-aligned, and so the synthetic clip is
// supported.
auto transform =
CreateTransform(t0(), MakeRotationMatrix(90), gfx::Point3F(100, 100, 0),
CompositingReason::k3DTransform);
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), *transform, rrect);
TestPaintArtifact artifact;
artifact.Chunk(*transform, *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content0
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer.
ASSERT_EQ(1u, LayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = LayerAt(0);
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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest,
SynthesizedClipShaderBasedBorderRadiusNotSupported2) {
// This tests the simplest 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.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 30, 40);
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// clip_mask0
// content0 [ mask_effect_0 ]
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer.
ASSERT_EQ(2u, LayerCount());
ASSERT_EQ(1u, SynthesizedClipLayerCount());
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* clip_mask0 = LayerAt(1);
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_TRUE(clip_mask0->draws_content());
EXPECT_TRUE(clip_mask0->HitTestable());
EXPECT_EQ(gfx::Size(304, 204), clip_mask0->bounds());
EXPECT_EQ(gfx::Vector2dF(48, 48), clip_mask0->offset_to_transform_parent());
EXPECT_EQ(c0_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->draws_content());
}
TEST_P(
PaintArtifactCompositorTest,
SynthesizedClipSimpleShaderBasedBorderRadiusNotSupportedMacNonEqualCorners) {
// Tests that on Mac, we fall back to a mask layer if the corners are not all
// the same radii.
gfx::SizeF corner(30, 30);
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), corner, corner, corner,
gfx::SizeF());
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
#if BUILDFLAG(IS_MAC)
ASSERT_EQ(2u, LayerCount());
#else
ASSERT_EQ(1u, LayerCount());
#endif
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClipNested) {
// This tests the simplest case that a single layer needs to be clipped
// by a single composited rounded clip.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
auto c2 = CreateClip(*c1, t0(), rrect);
auto c3 = CreateClip(*c2, t0(), rrect);
auto t1 = CreateTransform(t0(), gfx::Transform(), gfx::Point3F(),
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(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*t1, *c3, *filter)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content1
// [ mask_isolation_2 ]
// content0 [ 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, LayerCount());
// 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 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
constexpr int c1_id = 2;
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.mask_filter_info.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.mask_filter_info.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.mask_filter_info.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.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 0, 0), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content0
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer, no clip mask (because layer doesn't draw content).
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(1u, SynthesizedClipLayerCount());
// There is a synthesized clip", but it has no layer backing.
ASSERT_EQ(nullptr, SynthesizedClipLayerAt(0));
const cc::Layer* content0 = LayerAt(0);
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.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
auto c2 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 0, 0), Color::kBlack);
Update(artifact.Build());
const cc::Layer* content0 = LayerAt(0);
cc::ElementId old_element_id = GetPropertyTrees()
.effect_tree()
.Node(content0->effect_tree_index())
->element_id;
TestPaintArtifact repeated_artifact;
repeated_artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 0, 0), Color::kBlack);
Update(repeated_artifact.Build());
const cc::Layer* content1 = LayerAt(0);
// Check that stable ids are reused across updates.
EXPECT_EQ(GetPropertyTrees()
.effect_tree()
.Node(content1->effect_tree_index())
->element_id,
old_element_id);
TestPaintArtifact changed_artifact;
changed_artifact.Chunk(t0(), *c2, e0())
.RectDrawing(gfx::Rect(0, 0, 0, 0), Color::kBlack);
Update(changed_artifact.Build());
const cc::Layer* content2 = LayerAt(0);
// 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())
->element_id,
old_element_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(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content0 clip_mask0
// [ e1 ][ mask_effect_0 ]
// [ mask_isolation_0 ]
// [ e0 ]
// One content layer, one clip mask.
ASSERT_EQ(2u, LayerCount());
ASSERT_EQ(1u, SynthesizedClipLayerCount());
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* clip_mask0 = LayerAt(1);
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(c0_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(304, 204), clip_mask0->bounds());
EXPECT_EQ(gfx::Vector2dF(48, 48), clip_mask0->offset_to_transform_parent());
EXPECT_EQ(c0_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(c0_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(), gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*t1, *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content0 content1
// [ mask_isolation_0 ]
// [ e0 ]
// Two content layers, one clip mask.
ASSERT_EQ(2u, LayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
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);
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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
}
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(), gfx::Transform(), gfx::Point3F(),
CompositingReason::kWillChangeTransform);
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*t1, c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content0 content2
// [ mask_isolation_0 ] content1 [ mask_isolation_1 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, LayerCount());
// There are still "synthesized layers" but they're null because they use
// fast rounded corners.
ASSERT_EQ(2u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_FALSE(SynthesizedClipLayerAt(1));
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
const cc::Layer* content2 = LayerAt(2);
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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
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(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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_1.HasRenderSurface());
}
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.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
auto e1 = CreateOpacityEffect(e0(), t0(), c1.get(), 1,
CompositingReason::kWillChangeOpacity);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, *e1)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content1
// content0 [ e1 ] content2
// [ mask_isolation_0 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, LayerCount());
// There is still a "synthesized layer" but it's null.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
const cc::Layer* content2 = LayerAt(2);
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(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(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.mask_filter_info.rounded_corner_bounds());
}
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.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
CompositorFilterOperations non_trivial_filter;
non_trivial_filter.AppendBlurFilter(5);
auto e1 = CreateFilterEffect(e0(), non_trivial_filter,
CompositingReason::kActiveFilterAnimation);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, *e1)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content1
// content0 [ mask_isolation_1 ] content2
// [ mask_isolation_0 ][ e1 ][ mask_isolation_2 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, LayerCount());
// There are still "synthesized layers" but they're null because they use
// fast rounded corners.
ASSERT_EQ(3u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_FALSE(SynthesizedClipLayerAt(1));
EXPECT_FALSE(SynthesizedClipLayerAt(2));
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
const cc::Layer* content2 = LayerAt(2);
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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_1.HasRenderSurface());
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.mask_filter_info.rounded_corner_bounds());
EXPECT_FALSE(mask_isolation_2.HasRenderSurface());
}
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.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
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(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, *e1)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content1
// content0 [ e1 ] content2
// [ mask_isolation_0 ][ mask_isolation_1 ][ mask_isolation_2 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, LayerCount());
// There are still "synthesized layers" but they're null because they use
// fast rounded corners.
ASSERT_EQ(3u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_FALSE(SynthesizedClipLayerAt(1));
EXPECT_FALSE(SynthesizedClipLayerAt(2));
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
const cc::Layer* content2 = LayerAt(2);
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.mask_filter_info.rounded_corner_bounds());
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.mask_filter_info.rounded_corner_bounds());
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.mask_filter_info.rounded_corner_bounds());
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClipDelegateBackdropFilter) {
// This tests the case that an effect with backdrop filter cannot share
// the synthesized mask with its siblings because its backdrop filter has to
// be applied by the outermost mask in the correct transform space.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
auto c2 = CreateClip(*c1, t0(), FloatRoundedRect(60, 60, 200, 100));
auto t1 = Create2DTranslation(t0(), 10, 20);
CompositorFilterOperations blur_filter;
blur_filter.AppendBlurFilter(5);
auto e1 = CreateBackdropFilterEffect(e0(), *t1, c2.get(), blur_filter, 0.5f);
TestPaintArtifact artifact;
artifact.Chunk(*t1, *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*t1, *c2, *e1)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*t1, *c1, e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content1
// content0 [ e1 ] clip_mask1 content2
// [ mask_isolation_0 ][ mask_isolation_1 ][ mask_isolation_2 ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(4u, LayerCount());
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
const cc::Layer* clip_mask1 = LayerAt(2);
const cc::Layer* content2 = LayerAt(3);
// Three synthesized layers, two of which are null because they use fast
// rounded corners. One real synthesized layer is needed because the rounded
// clip and the backdrop filter are in different transform spaces.
ASSERT_EQ(3u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
EXPECT_EQ(clip_mask1, SynthesizedClipLayerAt(1));
EXPECT_FALSE(SynthesizedClipLayerAt(2));
int t1_id = content0->transform_tree_index();
EXPECT_EQ(t0_id, GetPropertyTrees().transform_tree().Node(t1_id)->parent_id);
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);
EXPECT_EQ(t0_id, cc_c1.transform_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(t0_id, mask_isolation_0.transform_id);
EXPECT_EQ(c0_id, mask_isolation_0.clip_id);
EXPECT_TRUE(mask_isolation_0.backdrop_filters.IsEmpty());
EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
EXPECT_EQ(1.0f, mask_isolation_0.opacity);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_0.mask_filter_info.rounded_corner_bounds());
EXPECT_EQ(t1_id, content1->transform_tree_index());
int c2_id = content1->clip_tree_index();
const cc::ClipNode& cc_c2 = *GetPropertyTrees().clip_tree().Node(c2_id);
EXPECT_EQ(gfx::RectF(60, 60, 200, 100), cc_c2.clip);
EXPECT_EQ(c1_id, cc_c2.parent_id);
EXPECT_EQ(t0_id, cc_c2.transform_id);
int e1_id = content1->effect_tree_index();
const cc::EffectNode& cc_e1 = *GetPropertyTrees().effect_tree().Node(e1_id);
EXPECT_TRUE(cc_e1.backdrop_filters.IsEmpty());
EXPECT_EQ(1.0f, cc_e1.opacity);
EXPECT_EQ(t1_id, cc_e1.transform_id);
EXPECT_EQ(c2_id, cc_e1.clip_id);
EXPECT_FALSE(cc_e1.backdrop_mask_element_id);
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(t1_id, mask_isolation_1.transform_id);
EXPECT_EQ(c2_id, mask_isolation_1.clip_id);
EXPECT_FALSE(mask_isolation_1.backdrop_filters.IsEmpty());
EXPECT_FALSE(mask_isolation_1.is_fast_rounded_corner);
// Opacity should also be moved to mask_isolation_1.
EXPECT_EQ(0.5f, mask_isolation_1.opacity);
EXPECT_EQ(gfx::RRectF(),
mask_isolation_1.mask_filter_info.rounded_corner_bounds());
EXPECT_EQ(t1_id, clip_mask1->transform_tree_index());
EXPECT_EQ(c2_id, clip_mask1->clip_tree_index());
const cc::EffectNode& mask =
*GetPropertyTrees().effect_tree().Node(clip_mask1->effect_tree_index());
ASSERT_EQ(mask_isolation_1_id, mask.parent_id);
EXPECT_EQ(SkBlendMode::kDstIn, mask.blend_mode);
EXPECT_TRUE(static_cast<const cc::PictureLayer*>(clip_mask1)
->is_backdrop_filter_mask());
EXPECT_TRUE(clip_mask1->element_id());
EXPECT_EQ(clip_mask1->element_id(),
mask_isolation_1.backdrop_mask_element_id);
EXPECT_EQ(t1_id, content2->transform_tree_index());
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(t0_id, mask_isolation_2.transform_id);
EXPECT_EQ(c0_id, mask_isolation_2.clip_id);
EXPECT_TRUE(mask_isolation_2.backdrop_filters.IsEmpty());
EXPECT_TRUE(mask_isolation_2.is_fast_rounded_corner);
EXPECT_EQ(1.0f, mask_isolation_2.opacity);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation_2.mask_filter_info.rounded_corner_bounds());
}
TEST_P(PaintArtifactCompositorTest, SynthesizedClipMultipleNonBackdropEffects) {
// This tests the case that multiple non-backdrop effects can share the
// synthesized mask.
FloatRoundedRect rrect(gfx::RectF(50, 50, 300, 200), 5);
auto c1 = CreateClip(c0(), t0(), rrect);
auto c2 = CreateClip(*c1, t0(), FloatRoundedRect(60, 60, 200, 100));
auto e1 = CreateOpacityEffect(e0(), t0(), c2.get(), 0.5,
CompositingReason::kWillChangeOpacity);
auto e2 = CreateOpacityEffect(e0(), t0(), c1.get(), 0.75,
CompositingReason::kWillChangeOpacity);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *c2, *e1)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), *c1, *e2)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(t0(), c0(), e0())
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
// Expectation in effect stack diagram:
// content0 content1 content2
// [ e1 ][ e2 ]
// [ mask_isolation ]
// [ e0 ]
// Three content layers.
ASSERT_EQ(3u, LayerCount());
const cc::Layer* content0 = LayerAt(0);
const cc::Layer* content1 = LayerAt(1);
const cc::Layer* content2 = LayerAt(2);
// One synthesized layer, which is null because it uses fast rounded corners.
ASSERT_EQ(1u, SynthesizedClipLayerCount());
EXPECT_FALSE(SynthesizedClipLayerAt(0));
int c2_id = content0->clip_tree_index();
const cc::ClipNode& cc_c2 = *GetPropertyTrees().clip_tree().Node(c2_id);
int e1_id = content0->effect_tree_index();
const cc::EffectNode& cc_e1 = *GetPropertyTrees().effect_tree().Node(e1_id);
int c1_id = content1->clip_tree_index();
const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree().Node(c1_id);
int e2_id = content1->effect_tree_index();
const cc::EffectNode& cc_e2 = *GetPropertyTrees().effect_tree().Node(e2_id);
int mask_isolation_id = cc_e1.parent_id;
const cc::EffectNode& mask_isolation =
*GetPropertyTrees().effect_tree().Node(mask_isolation_id);
EXPECT_EQ(c2_id, cc_e1.clip_id);
EXPECT_EQ(0.5f, cc_e1.opacity);
EXPECT_EQ(gfx::RectF(60, 60, 200, 100), cc_c2.clip);
ASSERT_EQ(c1_id, cc_c2.parent_id);
EXPECT_EQ(c1_id, cc_e2.clip_id);
EXPECT_EQ(mask_isolation_id, cc_e2.parent_id);
EXPECT_EQ(0.75f, cc_e2.opacity);
EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
ASSERT_EQ(c0_id, cc_c1.parent_id);
ASSERT_EQ(e0_id, mask_isolation.parent_id);
EXPECT_EQ(c0_id, mask_isolation.clip_id);
EXPECT_TRUE(mask_isolation.is_fast_rounded_corner);
EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
mask_isolation.mask_filter_info.rounded_corner_bounds());
EXPECT_EQ(c0_id, content2->clip_tree_index());
EXPECT_EQ(e0_id, content2->effect_tree_index());
}
TEST_P(PaintArtifactCompositorTest, WillBeRemovedFromFrame) {
auto effect = CreateSampleEffectNodeWithElementId();
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *effect)
.RectDrawing(gfx::Rect(100, 100, 200, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
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, LayerCount());
}
TEST_P(PaintArtifactCompositorTest, SolidColor) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(gfx::Rect(100, 200, 300, 400), Color::kBlack)
.IsSolidColor();
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
auto* layer = LayerAt(0);
EXPECT_EQ(gfx::Vector2dF(100, 200), layer->offset_to_transform_parent());
EXPECT_EQ(gfx::Size(300, 400), layer->bounds());
EXPECT_TRUE(layer->draws_content());
if (RuntimeEnabledFeatures::SolidColorLayersEnabled()) {
EXPECT_TRUE(LayerAt(0)->IsSolidColorLayerForTesting());
}
EXPECT_EQ(SkColors::kBlack, layer->background_color());
}
TEST_P(PaintArtifactCompositorTest, ContentsNonOpaque) {
TestPaintArtifact artifact;
artifact.Chunk().RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_FALSE(LayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaque) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 200, 200));
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_TRUE(LayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedNonOpaque) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(gfx::Rect(100, 100, 210, 210), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 210, 210))
.Chunk()
.RectDrawing(gfx::Rect(200, 200, 200, 200), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(200, 200, 200, 200));
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(300, 300), LayerAt(0)->bounds());
EXPECT_FALSE(LayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedClippedToOpaque) {
// Almost the same as ContentsOpaqueUnitedNonOpaque, but with a clip which
// removes the non-opaque part of the layer, making the layer opaque.
auto clip1 = CreateClip(c0(), t0(), FloatRoundedRect(175, 175, 100, 100));
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(gfx::Rect(100, 100, 250, 250), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 210, 210))
.Chunk(t0(), *clip1, e0())
.RectDrawing(gfx::Rect(200, 200, 300, 300), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(200, 200, 200, 200));
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(100, 100), LayerAt(0)->bounds());
EXPECT_TRUE(LayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque1) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(gfx::Rect(100, 100, 300, 300), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 300, 300))
.Chunk()
.RectDrawing(gfx::Rect(200, 200, 200, 200), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(200, 200, 200, 200));
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(300, 300), LayerAt(0)->bounds());
EXPECT_TRUE(LayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedWithRoundedClip) {
// Almost the same as ContentsOpaqueUnitedOpaque1, but the first layer has a
// rounded clip.
auto clip1 = CreateClip(c0(), t0(),
FloatRoundedRect(gfx::RectF(175, 175, 100, 100), 5));
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip1, e0())
.RectDrawing(gfx::Rect(100, 100, 210, 210), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 210, 210))
.Chunk(t0(), c0(), e0())
.RectDrawing(gfx::Rect(200, 200, 100, 100), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(200, 200, 100, 100));
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(125, 125), LayerAt(0)->bounds());
EXPECT_FALSE(LayerAt(0)->contents_opaque());
}
TEST_P(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque2) {
TestPaintArtifact artifact;
artifact.Chunk()
.RectDrawing(gfx::Rect(100, 100, 200, 200), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 200, 200))
.Chunk()
.RectDrawing(gfx::Rect(100, 100, 300, 300), Color::kBlack)
.RectKnownToBeOpaque(gfx::Rect(100, 100, 300, 300));
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_EQ(gfx::Size(300, 300), LayerAt(0)->bounds());
EXPECT_TRUE(LayerAt(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(gfx::Rect(50, 50, 100, 100), Color::kGray);
artifact.Chunk(t0(), *clip1, *effect1)
.RectDrawing(gfx::Rect(100, 100, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(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(gfx::Rect(50, 50, 100, 100), Color::kGray);
artifact.Chunk(t0(), *clip1, *effect1)
.RectDrawing(gfx::Rect(100, 100, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
const cc::Layer* layer = LayerAt(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(gfx::Rect(50, 50, 200, 200), Color::kBlack);
artifact1.Client(0).Validate();
artifact1.Client(1).Validate();
Update(artifact1.Build());
ASSERT_EQ(1u, LayerCount());
auto* layer = LayerAt(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(gfx::RectF(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),
gfx::Rect(0, 0, 400, 200), Color::kBlack)
.Build();
// Simluate commit to the compositor thread.
// When doing a full commit, we would call
// layer_tree_host_->ActivateCommitState() and the second argument would come
// from layer_tree_host_->active_commit_state(); we use pending_commit_state()
// just to keep the test code simple.
layer->PushPropertiesTo(
layer->CreateLayerImpl(host_impl.active_tree()).get(),
*const_cast<const cc::LayerTreeHost&>(GetLayerTreeHost())
.pending_commit_state(),
const_cast<const cc::LayerTreeHost&>(GetLayerTreeHost())
.thread_unsafe_commit_state());
Update(artifact2);
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(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(gfx::RectF(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), gfx::Rect(-100, -200, 500, 800),
Color::kBlack)
.Build();
// Simluate commit to the compositor thread.
layer->PushPropertiesTo(
layer->CreateLayerImpl(host_impl.active_tree()).get(),
*const_cast<const cc::LayerTreeHost&>(GetLayerTreeHost())
.pending_commit_state(),
const_cast<const cc::LayerTreeHost&>(GetLayerTreeHost())
.thread_unsafe_commit_state());
Update(artifact3);
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(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(gfx::RectF(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) {
auto matrix = MakeScaleMatrix(2);
TransformPaintPropertyNode::State transform_state{{matrix}};
transform_state.flags.in_subtree_of_page_scale = false;
const CompositorElementId compositor_element_id =
CompositorElementIdFromUniqueObjectId(1);
transform_state.compositor_element_id = compositor_element_id;
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);
const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree();
const cc::TransformNode* cc_transform_node =
transform_tree.FindNodeFromElementId(compositor_element_id);
EXPECT_TRUE(cc_transform_node);
EXPECT_EQ(matrix, cc_transform_node->local);
EXPECT_EQ(gfx::Point3F(), cc_transform_node->origin);
}
// 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.flags.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.flags.in_subtree_of_page_scale = false;
const CompositorElementId page_scale_compositor_element_id =
CompositorElementIdFromUniqueObjectId(1);
page_scale_transform_state.compositor_element_id =
page_scale_compositor_element_id;
auto page_scale_transform = TransformPaintPropertyNode::Create(
*ancestor_transform, std::move(page_scale_transform_state));
TransformPaintPropertyNode::State descendant_transform_state;
const CompositorElementId descendant_compositor_element_id =
CompositorElementIdFromUniqueObjectId(2);
descendant_transform_state.compositor_element_id =
descendant_compositor_element_id;
descendant_transform_state.flags.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(gfx::Rect(0, 0, 10, 10), Color::kBlack);
ViewportProperties viewport_properties;
viewport_properties.page_scale = page_scale_transform.get();
Update(artifact.Build(), viewport_properties);
const cc::TransformTree& transform_tree = GetPropertyTrees().transform_tree();
const auto* cc_page_scale_transform =
transform_tree.FindNodeFromElementId(page_scale_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::kInvalidPropertyNodeId) {
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_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.
TransformPaintPropertyNode::State transform_state{{MakeScaleMatrix(2)}};
transform_state.flags.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 = gfx::Rect(5, 5, 20, 10);
scroll_state.contents_size = gfx::Size(27, 32);
scroll_state.user_scrollable_vertical = true;
scroll_state.max_scroll_offset_affected_by_page_scale = true;
auto scroll_element_id = CompositorElementIdFromUniqueObjectId(
NewUniqueObjectId(), CompositorElementIdNamespace::kScroll);
scroll_state.compositor_element_id = scroll_element_id;
auto scroll = ScrollPaintPropertyNode::Create(ScrollPaintPropertyNode::Root(),
std::move(scroll_state));
auto scroll_translation =
CreateScrollTranslation(*scale_transform_node, 0, 0, *scroll);
TestPaintArtifact artifact;
artifact.Chunk(*scroll_translation, c0(), e0())
.RectDrawing(gfx::Rect(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_mutable();
cc::ScrollNode* cc_scroll_node =
scroll_tree.FindNodeFromElementId(scroll_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::PointF(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(), MakeRotationMatrix(90), gfx::Point3F(),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
gfx::Rect 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, LayerCount());
int effect_ids[6];
for (size_t i = 0; i < LayerCount(); i++)
effect_ids[i] = LayerAt(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,
CompositingReason::kActiveFilterAnimation);
auto filter2 = CreateFilterEffect(*opacity, filter,
CompositingReason::kActiveFilterAnimation);
gfx::Rect 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, LayerCount());
auto filter_id1 = LayerAt(0)->effect_tree_index();
auto filter_id2 = LayerAt(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(), MakeRotationMatrix(90), gfx::Point3F(),
CompositingReason::k3DTransform);
TestPaintArtifact artifact;
gfx::Rect 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, LayerCount());
int effect_ids[6];
for (size_t i = 0; i < LayerCount(); i++)
effect_ids[i] = LayerAt(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);
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, kHasRenderSurface);
// Effect |c| has one direct and one indirect compositing layers, so has
// render surface.
EXPECT_OPACITY(effect_ids[4], 1.f, kHasRenderSurface);
EXPECT_OPACITY(effect_tree.Node(effect_ids[4])->parent_id, 1.f,
kHasRenderSurface);
}
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);
TestPaintArtifact artifact;
gfx::Rect 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, LayerCount());
EXPECT_OPACITY(LayerAt(0)->effect_tree_index(), 0.5, kHasRenderSurface);
EXPECT_OPACITY(LayerAt(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 = CreateAnimatingTransform(t0());
auto e1 = CreateOpacityEffect(e0(), *t1, nullptr, 1.f);
auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(50, 50, 50, 50));
TestPaintArtifact artifact;
gfx::Rect 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, LayerCount());
const auto* effect =
GetPropertyTrees().effect_tree().Node(LayerAt(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 = CreateAnimatingTransform(t0());
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;
gfx::Rect 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, LayerCount());
const auto* effect =
GetPropertyTrees().effect_tree().Node(LayerAt(1)->effect_tree_index());
EXPECT_TRUE(effect->HasRenderSurface());
}
TEST_P(PaintArtifactCompositorTest, OpacityIndirectlyAffectingTwoLayers) {
auto opacity = CreateOpacityEffect(e0(), 0.5f);
auto child_composited_transform = CreateTransform(
t0(), gfx::Transform(), gfx::Point3F(), CompositingReason::k3DTransform);
auto grandchild_composited_transform =
CreateTransform(*child_composited_transform, gfx::Transform(),
gfx::Point3F(), CompositingReason::k3DTransform);
auto child_effect = CreateOpacityEffect(*opacity, 1.f);
auto grandchild_effect = CreateOpacityEffect(*child_effect, 1.f);
TestPaintArtifact artifact;
artifact.Chunk(*child_composited_transform, c0(), *child_effect)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
artifact.Chunk(*grandchild_composited_transform, c0(), *grandchild_effect)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
const auto& effect_tree = GetPropertyTrees().effect_tree();
int layer0_effect_id = LayerAt(0)->effect_tree_index();
EXPECT_OPACITY(layer0_effect_id, 1.f, kNoRenderSurface);
int layer1_effect_id = LayerAt(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, WillChangeOpacityRenderSurfaceWithLayer) {
auto opacity =
CreateOpacityEffect(e0(), 1.f, CompositingReason::kWillChangeOpacity);
auto child_composited_transform = CreateTransform(
t0(), gfx::Transform(), gfx::Point3F(), CompositingReason::k3DTransform);
auto child_effect = CreateOpacityEffect(*opacity, 1.f);
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *opacity)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*child_composited_transform, c0(), *child_effect)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
int layer0_effect_id = LayerAt(0)->effect_tree_index();
EXPECT_OPACITY(layer0_effect_id, 1.f, kNoRenderSurface);
int layer1_effect_id = LayerAt(1)->effect_tree_index();
// TODO(crbug.com/1285498): Optimize for will-change: opacity.
// EXPECT_OPACITY(layer1_effect_id, 1.f, kHasRenderSurface);
EXPECT_OPACITY(layer1_effect_id, 1.f, kNoRenderSurface);
}
TEST_P(PaintArtifactCompositorTest,
WillChangeOpacityRenderSurfaceWithoutLayer) {
auto opacity =
CreateOpacityEffect(e0(), 1.f, CompositingReason::kWillChangeOpacity);
auto child_composited_transform = CreateTransform(
t0(), gfx::Transform(), gfx::Point3F(), CompositingReason::k3DTransform);
auto grandchild_composited_transform =
CreateTransform(*child_composited_transform, gfx::Transform(),
gfx::Point3F(), CompositingReason::k3DTransform);
auto child_effect = CreateOpacityEffect(*opacity, 1.f);
auto grandchild_effect = CreateOpacityEffect(*child_effect, 1.f);
TestPaintArtifact artifact;
artifact.Chunk(*child_composited_transform, c0(), *child_effect)
.RectDrawing(gfx::Rect(0, 0, 100, 100), Color::kBlack);
artifact.Chunk(*grandchild_composited_transform, c0(), *grandchild_effect)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
const auto& effect_tree = GetPropertyTrees().effect_tree();
int layer0_effect_id = LayerAt(0)->effect_tree_index();
EXPECT_OPACITY(layer0_effect_id, 1.f, kNoRenderSurface);
int layer1_effect_id = LayerAt(1)->effect_tree_index();
EXPECT_OPACITY(layer1_effect_id, 1.f, kNoRenderSurface);
int opacity_id = effect_tree.Node(layer0_effect_id)->parent_id;
// TODO(crbug.com/1285498): Optimize for will-change: opacity.
// |opacity| affects both layer0 and layer1 which don't have render surfaces,
// so it should have a render surface if we have the optimization for
// will-change:opacity.
// EXPECT_OPACITY(opacity_id, 1.f, kHasRenderSurface);
EXPECT_OPACITY(opacity_id, 1.f, kNoRenderSurface);
}
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(gfx::Rect(150, 150, 100, 100), Color::kWhite);
artifact.Chunk(t0(), c0(), *grandchild_composited_effect)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kGray);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
const auto& effect_tree = GetPropertyTrees().effect_tree();
// layer0's opacity animation needs a render surface because it affects
// both layer0 and layer1.
int layer0_effect_id = LayerAt(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 = LayerAt(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, FilterCreatesRenderSurface) {
CompositorFilterOperations filter;
filter.AppendBlurFilter(5);
auto e1 = CreateFilterEffect(e0(), filter,
CompositingReason::kActiveFilterAnimation);
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_OPACITY(LayerAt(0)->effect_tree_index(), 1.f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, WillChangeFilterCreatesRenderSurface) {
auto e1 = CreateFilterEffect(e0(), CompositorFilterOperations(),
CompositingReason::kWillChangeFilter);
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_OPACITY(LayerAt(0)->effect_tree_index(), 1.f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, FilterAnimationCreatesRenderSurface) {
auto e1 = CreateAnimatingFilterEffect(e0());
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_OPACITY(LayerAt(0)->effect_tree_index(), 1.f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, BackdropFilterCreatesRenderSurface) {
CompositorFilterOperations filter;
filter.AppendBlurFilter(5);
auto e1 = CreateBackdropFilterEffect(e0(), filter);
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_OPACITY(LayerAt(0)->effect_tree_index(), 1.f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest,
WillChangeBackdropFilterCreatesRenderSurface) {
auto e1 =
CreateBackdropFilterEffect(e0(), CompositorFilterOperations(),
CompositingReason::kWillChangeBackdropFilter);
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_OPACITY(LayerAt(0)->effect_tree_index(), 1.f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest,
BackdropFilterAnimationCreatesRenderSurface) {
auto e1 = CreateAnimatingBackdropFilterEffect(e0());
Update(TestPaintArtifact()
.Chunk(t0(), c0(), *e1)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
EXPECT_OPACITY(LayerAt(0)->effect_tree_index(), 1.f, kHasRenderSurface);
}
TEST_P(PaintArtifactCompositorTest, Non2dAxisAlignedClip) {
auto rotate = CreateTransform(t0(), MakeRotationMatrix(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(gfx::Rect(50, 50, 50, 50), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(1u, LayerCount());
// We should create a synthetic effect node for the non-2d-axis-aligned clip.
int clip_id = LayerAt(0)->clip_tree_index();
const auto* cc_clip = GetPropertyTrees().clip_tree().Node(clip_id);
int effect_id = LayerAt(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(), MakeRotationMatrix(45));
FloatRoundedRect rounded_clip(gfx::RectF(50, 50, 50, 50), 5);
auto clip = CreateClip(c0(), *rotate, rounded_clip);
auto opacity = CreateOpacityEffect(
e0(), 0.5f, CompositingReason::kActiveOpacityAnimation);
TestPaintArtifact artifact;
artifact.Chunk(t0(), *clip, *opacity)
.RectDrawing(gfx::Rect(50, 50, 50, 50), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(2u, LayerCount());
// We should create a synthetic effect node for the non-2d-axis-aligned clip.
auto* masked_layer = LayerAt(0);
EXPECT_TRUE(masked_layer->draws_content());
int clip_id = masked_layer->clip_tree_index();
const auto* cc_clip = GetPropertyTrees().clip_tree().Node(clip_id);
int effect_id = masked_layer->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);
// cc_clip should be applied in the clip mask layer.
EXPECT_EQ(cc_effect->clip_id, cc_clip->parent_id);
// The mask layer is needed because the masked layer draws content.
auto* mask_layer = LayerAt(1);
const auto* cc_mask =
GetPropertyTrees().effect_tree().Node(mask_layer->effect_tree_index());
EXPECT_EQ(SkBlendMode::kDstIn, cc_mask->blend_mode);
}
TEST_P(PaintArtifactCompositorTest,
Non2dAxisAlignedClipUnderLaterRenderSurface) {
auto rotate1 = CreateTransform(t0(), MakeRotationMatrix(45), gfx::Point3F(),
CompositingReason::k3DTransform);
auto rotate2 =
CreateTransform(*rotate1, MakeRotationMatrix(-45), gfx::Point3F(),
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)
.Preserves2dAxisAlignment());
TestPaintArtifact artifact;
artifact.Chunk(t0(), c0(), *opacity)
.RectDrawing(gfx::Rect(50, 50, 50, 50), Color::kWhite);
artifact.Chunk(*rotate1, c0(), *opacity)
.RectDrawing(gfx::Rect(50, 50, 50, 50), Color::kWhite);
artifact.Chunk(*rotate2, *clip, *opacity)
.RectDrawing(gfx::Rect(50, 50, 50, 50), Color::kWhite);
Update(artifact.Build());
ASSERT_EQ(3u, LayerCount());
// 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 = LayerAt(2)->clip_tree_index();
const auto* cc_clip = GetPropertyTrees().clip_tree().Node(clip_id);
int effect_id = LayerAt(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);
}
static TransformPaintPropertyNode::State Transform3dState(
const gfx::Transform& transform) {
TransformPaintPropertyNode::State state{{transform}};
state.direct_compositing_reasons = CompositingReason::k3DTransform;
return state;
}
TEST_P(PaintArtifactCompositorTest, TransformChange) {
auto t1 = Create2DTranslation(t0(), 10, 20);
auto t2 = TransformPaintPropertyNode::Create(
*t1, Transform3dState(MakeRotationMatrix(45)));
FakeDisplayItemClient& client =
*MakeGarbageCollected<FakeDisplayItemClient>();
client.Validate();
Update(TestPaintArtifact()
.Chunk(1)
.Properties(*t2, c0(), e0())
.RectDrawing(client, gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
auto* layer = static_cast<cc::PictureLayer*>(LayerAt(0));
auto display_item_list = layer->client()->PaintContentsToDisplayList();
// Change t1 but not t2.
layer->ClearSubtreePropertyChangedForTesting();
ClearPropertyTreeChangedState();
t1->Update(
t0(), TransformPaintPropertyNode::State{{MakeTranslationMatrix(20, 30)}});
EXPECT_EQ(PaintPropertyChangeType::kChangedOnlySimpleValues,
t1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, t2->NodeChanged());
Update(TestPaintArtifact()
.Chunk(1)
.Properties(*t2, c0(), e0())
.RectDrawing(client, gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(0));
EXPECT_EQ(display_item_list.get(),
layer->client()->PaintContentsToDisplayList().get());
EXPECT_TRUE(layer->subtree_property_changed());
// This is set by cc when propagating ancestor change flag to descendants.
EXPECT_TRUE(GetTransformNode(layer).transform_changed);
// This is set by PropertyTreeManager.
EXPECT_TRUE(GetPropertyTrees()
.transform_tree()
.Node(GetTransformNode(layer).parent_id)
->transform_changed);
// Change t2 but not t1.
layer->ClearSubtreePropertyChangedForTesting();
ClearPropertyTreeChangedState();
t2->Update(*t1, Transform3dState(MakeRotationMatrix(135)));
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, t1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kChangedOnlySimpleValues,
t2->NodeChanged());
Update(TestPaintArtifact()
.Chunk(1)
.Properties(*t2, c0(), e0())
.RectDrawing(client, gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(0));
EXPECT_EQ(display_item_list.get(),
layer->client()->PaintContentsToDisplayList().get());
EXPECT_TRUE(layer->subtree_property_changed());
EXPECT_TRUE(GetTransformNode(layer).transform_changed);
EXPECT_FALSE(GetPropertyTrees()
.transform_tree()
.Node(GetTransformNode(layer).parent_id)
->transform_changed);
// Change t2 to be 2d translation which will be decomposited.
layer->ClearSubtreePropertyChangedForTesting();
ClearPropertyTreeChangedState();
t2->Update(*t1, Transform3dState(MakeTranslationMatrix(20, 30)));
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, t1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kChangedOnlyValues, t2->NodeChanged());
Update(TestPaintArtifact()
.Chunk(1)
.Properties(*t2, c0(), e0())
.RectDrawing(client, gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(0));
EXPECT_EQ(display_item_list.get(),
layer->client()->PaintContentsToDisplayList().get());
// The new transform is decomposited, so there is no transform_changed, but
// we set subtree_property_changed because offset_from_transform_parent
// (calculated from the decomposited transforms) changed.
EXPECT_TRUE(layer->subtree_property_changed());
EXPECT_FALSE(GetTransformNode(layer).transform_changed);
// Change no transform nodes, but invalidate client.
layer->ClearSubtreePropertyChangedForTesting();
ClearPropertyTreeChangedState();
client.Invalidate(PaintInvalidationReason::kBackground);
Update(TestPaintArtifact()
.Chunk(1)
.Properties(*t2, c0(), e0())
.RectDrawing(client, gfx::Rect(100, 100, 200, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(0));
EXPECT_NE(display_item_list.get(),
layer->client()->PaintContentsToDisplayList().get());
}
TEST_P(PaintArtifactCompositorTest, EffectChange) {
auto e1 = CreateOpacityEffect(e0(), t0(), nullptr, 0.5f);
auto e2 = CreateOpacityEffect(*e1, t0(), nullptr, 0.6f,
CompositingReason::kWillChangeOpacity);
Update(TestPaintArtifact()
.Chunk(1)
.Properties(t0(), c0(), *e2)
.RectDrawing(gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
cc::Layer* layer = LayerAt(0);
// Change e1 but not e2.
layer->ClearSubtreePropertyChangedForTesting();
ClearPropertyTreeChangedState();
EffectPaintPropertyNode::State e1_state{&t0()};
e1_state.opacity = 0.8f;
e1_state.compositor_element_id = e1->GetCompositorElementId();
e1->Update(e0(), std::move(e1_state));
EXPECT_EQ(PaintPropertyChangeType::kChangedOnlySimpleValues,
e1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, e2->NodeChanged());
Update(TestPaintArtifact()
.Chunk(1)
.Properties(t0(), c0(), *e2)
.RectDrawing(gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(0));
// TODO(wangxianzhu): Probably avoid setting this flag on Effect change.
EXPECT_TRUE(layer->subtree_property_changed());
// This is set by cc when propagating ancestor change flag to descendants.
EXPECT_TRUE(GetEffectNode(layer).effect_changed);
// This is set by PropertyTreeManager.
EXPECT_TRUE(GetPropertyTrees()
.effect_tree()
.Node(GetEffectNode(layer).parent_id)
->effect_changed);
// Change e2 but not e1.
layer->ClearSubtreePropertyChangedForTesting();
ClearPropertyTreeChangedState();
EffectPaintPropertyNode::State e2_state{&t0()};
e2_state.opacity = 0.9f;
e2_state.direct_compositing_reasons = CompositingReason::kWillChangeOpacity;
e2_state.compositor_element_id = e2->GetCompositorElementId();
e2->Update(*e1, std::move(e2_state));
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, e1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kChangedOnlySimpleValues,
e2->NodeChanged());
Update(TestPaintArtifact()
.Chunk(1)
.Properties(t0(), c0(), *e2)
.RectDrawing(gfx::Rect(100, 100, 200, 100), Color::kBlack)
.Build());
ASSERT_EQ(1u, LayerCount());
ASSERT_EQ(layer, LayerAt(0));
// TODO(wangxianzhu): Probably avoid setting this flag on Effect change.
EXPECT_TRUE(layer->subtree_property_changed());
EXPECT_TRUE(GetEffectNode(layer).effect_changed);
EXPECT_FALSE(GetPropertyTrees()
.effect_tree()
.Node(GetEffectNode(layer).parent_id)
->effect_changed);
}
TEST_P(PaintArtifactCompositorTest, DirectlySetScrollOffset) {
auto scroll_state = ScrollState1();
auto& scroll = *scroll_state.Transform().ScrollNode();
auto scroll_element_id = scroll.GetCompositorElementId();
TestPaintArtifact artifact;
CreateScrollableChunk(artifact, scroll_state);
Update(artifact.Build());
const auto& scroll_tree = GetPropertyTrees().scroll_tree();
const auto* scroll_layer = ScrollableLayerAt(0);
const auto* scroll_node =
scroll_tree.FindNodeFromElementId(scroll_element_id);
const auto& transform_tree = GetPropertyTrees().transform_tree();
const auto* transform_node = transform_tree.Node(scroll_node->transform_id);
EXPECT_EQ(scroll_element_id, scroll_node->element_id);
EXPECT_EQ(scroll_element_id, scroll_layer->element_id());
EXPECT_EQ(scroll_node->id, scroll_layer->scroll_tree_index());
EXPECT_EQ(gfx::PointF(-7, -9),
scroll_tree.current_scroll_offset(scroll_element_id));
EXPECT_EQ(gfx::PointF(-7, -9), transform_node->scroll_offset);
auto& host = GetLayerTreeHost();
host.CompositeForTest(base::TimeTicks::Now(), true, base::OnceClosure());
ASSERT_FALSE(const_cast<const cc::LayerTreeHost&>(host)
.pending_commit_state()
->layers_that_should_push_properties.contains(scroll_layer));
ASSERT_FALSE(host.proxy()->CommitRequested());
ASSERT_FALSE(transform_tree.needs_update());
ASSERT_TRUE(GetPaintArtifactCompositor().DirectlySetScrollOffset(
scroll_element_id, gfx::PointF(-10, -20)));
EXPECT_TRUE(const_cast<const cc::LayerTreeHost&>(host)
.pending_commit_state()
->layers_that_should_push_properties.contains(scroll_layer));
EXPECT_TRUE(host.proxy()->CommitRequested());
EXPECT_EQ(gfx::PointF(-10, -20),
scroll_tree.current_scroll_offset(scroll_element_id));
// DirectlySetScrollOffset doesn't update transform node.
EXPECT_EQ(gfx::PointF(-7, -9), transform_node->scroll_offset);
EXPECT_FALSE(transform_tree.needs_update());
}
TEST_P(PaintArtifactCompositorTest, AddNonCompositedScrollNodes) {
// This test requires scroll unification.
if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
return;
uint32_t main_thread_scrolling_reason =
cc::MainThreadScrollingReason::kNotScrollingOnMain;
if (!RuntimeEnabledFeatures::CompositeScrollAfterPaintEnabled()) {
main_thread_scrolling_reason =
cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText;
ASSERT_TRUE(cc::MainThreadScrollingReason::HasNonCompositedScrollReasons(
main_thread_scrolling_reason));
}
auto scroll_state =
ScrollState1(PropertyTreeState::Root(), CompositingReason::kNone,
main_thread_scrolling_reason);
WTF::Vector<const TransformPaintPropertyNode*> scroll_translation_nodes;
scroll_translation_nodes.push_back(&scroll_state.Transform());
Update(
TestPaintArtifact()
.Chunk(1)
.ScrollHitTest(scroll_state.Transform().ScrollNode()->ContainerRect(),
&scroll_state.Transform())
// In CompositeScrollAfterPaint, this chunk being non-opaque makes
// the scroll not composited.
.Chunk(2)
.Properties(scroll_state.Transform(), c0(), e0())
.Build(),
ViewportProperties(), scroll_translation_nodes);
const auto& scroll_tree = GetPropertyTrees().scroll_tree();
auto* scroll_node = scroll_tree.FindNodeFromElementId(
scroll_state.Transform().ScrollNode()->GetCompositorElementId());
EXPECT_TRUE(scroll_node);
EXPECT_FALSE(scroll_node->is_composited);
}
TEST_P(PaintArtifactCompositorTest, AddUnpaintedNonCompositedScrollNodes) {
// This test requires scroll unification.
if (!base::FeatureList::IsEnabled(::features::kScrollUnification)) {
return;
}
const uint32_t main_thread_scrolling_reason =
cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText;
ASSERT_TRUE(cc::MainThreadScrollingReason::HasNonCompositedScrollReasons(
main_thread_scrolling_reason));
auto scroll_state =
ScrollState1(PropertyTreeState::Root(), CompositingReason::kNone,
main_thread_scrolling_reason);
WTF::Vector<const TransformPaintPropertyNode*> scroll_translation_nodes;
scroll_translation_nodes.push_back(&scroll_state.Transform());
TestPaintArtifact artifact;
Update(artifact.Build(), ViewportProperties(), scroll_translation_nodes);
const auto& scroll_tree = GetPropertyTrees().scroll_tree();
auto* scroll_node = scroll_tree.FindNodeFromElementId(
scroll_state.Transform().ScrollNode()->GetCompositorElementId());
EXPECT_TRUE(scroll_node);
EXPECT_FALSE(scroll_node->is_composited);
EXPECT_EQ(scroll_node->transform_id, cc::kInvalidPropertyNodeId);
EXPECT_EQ(gfx::PointF(-7, -9),
scroll_tree.current_scroll_offset(scroll_node->element_id));
}
TEST_P(PaintArtifactCompositorTest, RepaintIndirectScrollHitTest) {
auto scroll_state = ScrollState1();
TestPaintArtifact test_artifact;
CreateScrollableChunk(test_artifact, scroll_state);
auto artifact = test_artifact.Build();
Update(artifact);
ClearPropertyTreeChangedState();
GetPaintArtifactCompositor().UpdateRepaintedLayers(artifact);
// This test passes if no CHECK occurs.
}
TEST_P(PaintArtifactCompositorTest, ClearChangedStateWithIndirectTransform) {
// t1 and t2 are siblings.
auto t1 = Create2DTranslation(t0(), 1, 1);
auto t2 = Create2DTranslation(t0(), 2, 2);
// c1 and c2 are parent and child, referencing t1 and t2, respectively.
auto c1 = CreateClip(c0(), *t1, FloatRoundedRect(1, 1, 1, 1));
auto c2 = CreateClip(*c1, *t2, FloatRoundedRect(2, 2, 2, 2));
EXPECT_EQ(PaintPropertyChangeType::kNodeAddedOrRemoved, t1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kNodeAddedOrRemoved, t2->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kNodeAddedOrRemoved, c1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kNodeAddedOrRemoved, c2->NodeChanged());
auto artifact = TestPaintArtifact()
.Chunk(1)
.Properties(*t2, *c2, e0())
.RectDrawing(gfx::Rect(2, 2, 2, 2), Color::kBlack)
.Build();
Update(artifact);
ClearPropertyTreeChangedState();
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, t1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, t2->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, c1->NodeChanged());
EXPECT_EQ(PaintPropertyChangeType::kUnchanged, c2->NodeChanged());
GetPaintArtifactCompositor().UpdateRepaintedLayers(artifact);
// This test passes if no DCHECK occurs.
}
TEST_P(PaintArtifactCompositorTest,
CompositedPixelMovingFilterWithClipExpander) {
CompositorFilterOperations filter_op;
filter_op.AppendBlurFilter(5);
auto filter =
CreateFilterEffect(e0(), filter_op, CompositingReason::kWillChangeFilter);
auto clip_expander = CreatePixelMovingFilterClipExpander(c0(), *filter);
Update(TestPaintArtifact()
.Chunk(t0(), *clip_expander, *filter)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Build());
ASSERT_EQ(1u, LayerCount());
const auto* cc_clip_expander =
GetPropertyTrees().clip_tree().Node(LayerAt(0)->clip_tree_index());
EXPECT_FALSE(cc_clip_expander->AppliesLocalClip());
EXPECT_EQ(cc_clip_expander->pixel_moving_filter_id,
LayerAt(0)->effect_tree_index());
}
TEST_P(PaintArtifactCompositorTest,
NonCompositedPixelMovingFilterWithCompositedClipExpander) {
CompositorFilterOperations filter_op;
filter_op.AppendBlurFilter(5);
auto filter = CreateFilterEffect(e0(), filter_op);
auto clip_expander = CreatePixelMovingFilterClipExpander(c0(), *filter);
EffectPaintPropertyNode::State mask_state;
mask_state.local_transform_space = &t0();
mask_state.output_clip = clip_expander.get();
mask_state.blend_mode = SkBlendMode::kDstIn;
mask_state.direct_compositing_reasons =
CompositingReason::kBackdropFilterMask;
auto mask = EffectPaintPropertyNode::Create(e0(), std::move(mask_state));
Update(TestPaintArtifact()
.Chunk(t0(), *clip_expander, *filter)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kWhite)
.Chunk(t0(), *clip_expander, *mask)
.RectDrawing(gfx::Rect(150, 150, 100, 100), Color::kBlack)
.Build());
ASSERT_EQ(2u, LayerCount());
const auto* cc_clip_expander =
GetPropertyTrees().clip_tree().Node(LayerAt(0)->clip_tree_index());
EXPECT_TRUE(cc_clip_expander->AppliesLocalClip());
}
} // namespace blink