blob: e73433889ff5d2b6eab57b2e0a3d0b4ca05cfd0f [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/flow/layers/backdrop_filter_layer.h"
#include "flutter/flow/layers/clip_rect_layer.h"
#include "flutter/flow/layers/clip_rect_layer.h"
#include "flutter/flow/layers/transform_layer.h"
#include "flutter/flow/testing/diff_context_test.h"
#include "flutter/flow/testing/layer_test.h"
#include "flutter/flow/testing/mock_layer.h"
#include "flutter/fml/macros.h"
namespace flutter {
namespace testing {
using BackdropFilterLayerTest = LayerTest;
#ifndef NDEBUG
TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies) {
auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);
auto layer = std::make_shared<BackdropFilterLayer>(filter.shared(),
DlBlendMode::kSrcOver);
auto parent = std::make_shared<ClipRectLayer>(kEmptyRect, Clip::hardEdge);
parent->Add(layer);
parent->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), kEmptyRect);
EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);
EXPECT_FALSE(layer->needs_painting(paint_context()));
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(context\\)");
}
TEST_F(BackdropFilterLayerTest, PaintBeforePrerollDies) {
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
const SkPath child_path = SkPath().addRect(child_bounds);
auto mock_layer = std::make_shared<MockLayer>(child_path);
auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);
auto layer = std::make_shared<BackdropFilterLayer>(filter.shared(),
DlBlendMode::kSrcOver);
layer->Add(mock_layer);
EXPECT_EQ(layer->paint_bounds(), kEmptyRect);
EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(context\\)");
}
#endif
TEST_F(BackdropFilterLayerTest, EmptyFilter) {
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
const SkPath child_path = SkPath().addRect(child_bounds);
const DlPaint child_paint = DlPaint(DlColor::kYellow());
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer =
std::make_shared<BackdropFilterLayer>(nullptr, DlBlendMode::kSrcOver);
layer->Add(mock_layer);
auto parent = std::make_shared<ClipRectLayer>(child_bounds, Clip::hardEdge);
parent->Add(layer);
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
parent->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);
parent->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
/* (ClipRect)parent::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRect(child_bounds, DlCanvas::ClipOp::kIntersect,
false);
/* (BackdropFilter)layer::Paint */ {
expected_builder.Save();
{
expected_builder.SaveLayer(&child_bounds, nullptr, nullptr);
{
/* mock_layer::Paint */ {
expected_builder.DrawPath(child_path, child_paint);
}
}
expected_builder.Restore();
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
TEST_F(BackdropFilterLayerTest, SimpleFilter) {
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
const SkPath child_path = SkPath().addRect(child_bounds);
const DlPaint child_paint = DlPaint(DlColor::kYellow());
auto layer_filter =
std::make_shared<DlBlurImageFilter>(2.5, 3.2, DlTileMode::kClamp);
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer = std::make_shared<BackdropFilterLayer>(layer_filter,
DlBlendMode::kSrcOver);
layer->Add(mock_layer);
auto parent = std::make_shared<ClipRectLayer>(child_bounds, Clip::hardEdge);
parent->Add(layer);
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
parent->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);
parent->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
/* (ClipRect)parent::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRect(child_bounds, DlCanvas::ClipOp::kIntersect,
false);
/* (BackdropFilter)layer::Paint */ {
expected_builder.Save();
{
expected_builder.SaveLayer(&child_bounds, nullptr,
layer_filter.get());
{
/* mock_layer::Paint */ {
expected_builder.DrawPath(child_path, child_paint);
}
}
expected_builder.Restore();
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
TEST_F(BackdropFilterLayerTest, NonSrcOverBlend) {
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
const SkPath child_path = SkPath().addRect(child_bounds);
const DlPaint child_paint = DlPaint(DlColor::kYellow());
auto layer_filter =
std::make_shared<DlBlurImageFilter>(2.5, 3.2, DlTileMode::kClamp);
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer =
std::make_shared<BackdropFilterLayer>(layer_filter, DlBlendMode::kSrc);
layer->Add(mock_layer);
auto parent = std::make_shared<ClipRectLayer>(child_bounds, Clip::hardEdge);
parent->Add(layer);
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
parent->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);
DlPaint filter_paint = DlPaint();
filter_paint.setBlendMode(DlBlendMode::kSrc);
parent->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
/* (ClipRect)parent::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRect(child_bounds, DlCanvas::ClipOp::kIntersect,
false);
/* (BackdropFilter)layer::Paint */ {
expected_builder.Save();
{
DlPaint save_paint = DlPaint().setBlendMode(DlBlendMode::kSrc);
expected_builder.SaveLayer(&child_bounds, &save_paint,
layer_filter.get());
{
/* mock_layer::Paint */ {
expected_builder.DrawPath(child_path, child_paint);
}
}
expected_builder.Restore();
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
TEST_F(BackdropFilterLayerTest, MultipleChildren) {
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);
const SkPath child_path1 = SkPath().addRect(child_bounds);
const SkPath child_path2 =
SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f));
const DlPaint child_paint1 = DlPaint(DlColor::kYellow());
const DlPaint child_paint2 = DlPaint(DlColor::kCyan());
SkRect children_bounds = child_path1.getBounds();
children_bounds.join(child_path2.getBounds());
auto layer_filter =
std::make_shared<DlBlurImageFilter>(2.5, 3.2, DlTileMode::kClamp);
auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);
auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);
auto layer = std::make_shared<BackdropFilterLayer>(layer_filter,
DlBlendMode::kSrcOver);
layer->Add(mock_layer1);
layer->Add(mock_layer2);
auto parent =
std::make_shared<ClipRectLayer>(children_bounds, Clip::hardEdge);
parent->Add(layer);
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
parent->Preroll(preroll_context());
EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds());
EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds());
EXPECT_EQ(layer->paint_bounds(), children_bounds);
EXPECT_EQ(layer->child_paint_bounds(), children_bounds);
EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));
EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);
EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);
parent->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
/* (ClipRect)parent::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRect(children_bounds, DlCanvas::ClipOp::kIntersect,
false);
/* (BackdropFilter)layer::Paint */ {
expected_builder.Save();
{
expected_builder.SaveLayer(&children_bounds, nullptr,
layer_filter.get());
{
/* mock_layer1::Paint */ {
expected_builder.DrawPath(child_path1, child_paint1);
}
/* mock_layer2::Paint */ {
expected_builder.DrawPath(child_path2, child_paint2);
}
}
expected_builder.Restore();
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
TEST_F(BackdropFilterLayerTest, Nested) {
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);
const SkPath child_path1 = SkPath().addRect(child_bounds);
const SkPath child_path2 =
SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f));
const DlPaint child_paint1 = DlPaint(DlColor::kYellow());
const DlPaint child_paint2 = DlPaint(DlColor::kCyan());
SkRect children_bounds = child_path1.getBounds();
children_bounds.join(child_path2.getBounds());
auto layer_filter1 =
std::make_shared<DlBlurImageFilter>(2.5, 3.2, DlTileMode::kClamp);
auto layer_filter2 =
std::make_shared<DlBlurImageFilter>(2.7, 3.1, DlTileMode::kDecal);
auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);
auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);
auto layer1 = std::make_shared<BackdropFilterLayer>(layer_filter1,
DlBlendMode::kSrcOver);
auto layer2 = std::make_shared<BackdropFilterLayer>(layer_filter2,
DlBlendMode::kSrcOver);
layer2->Add(mock_layer2);
layer1->Add(mock_layer1);
layer1->Add(layer2);
auto parent =
std::make_shared<ClipRectLayer>(children_bounds, Clip::hardEdge);
parent->Add(layer1);
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
parent->Preroll(preroll_context());
EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds());
EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds());
EXPECT_EQ(layer1->paint_bounds(), children_bounds);
EXPECT_EQ(layer2->paint_bounds(), children_bounds);
EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));
EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));
EXPECT_TRUE(layer1->needs_painting(paint_context()));
EXPECT_TRUE(layer2->needs_painting(paint_context()));
EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);
EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);
parent->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
/* (ClipRect)parent::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRect(children_bounds, DlCanvas::ClipOp::kIntersect,
false);
/* (BackdropFilter)layer1::Paint */ {
expected_builder.Save();
{
expected_builder.SaveLayer(&children_bounds, nullptr,
layer_filter1.get());
{
/* mock_layer1::Paint */ {
expected_builder.DrawPath(child_path1, child_paint1);
}
/* (BackdropFilter)layer2::Paint */ {
expected_builder.Save();
{
expected_builder.SaveLayer(&children_bounds, nullptr,
layer_filter2.get());
{
/* mock_layer2::Paint */ {
expected_builder.DrawPath(child_path2, child_paint2);
}
}
expected_builder.Restore();
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
TEST_F(BackdropFilterLayerTest, Readback) {
std::shared_ptr<DlImageFilter> no_filter;
auto layer_filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);
auto initial_transform = SkMatrix();
// BDF with filter always reads from surface
auto layer1 = std::make_shared<BackdropFilterLayer>(layer_filter.shared(),
DlBlendMode::kSrcOver);
preroll_context()->surface_needs_readback = false;
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
layer1->Preroll(preroll_context());
EXPECT_TRUE(preroll_context()->surface_needs_readback);
// BDF with no filter does not read from surface itself
auto layer2 =
std::make_shared<BackdropFilterLayer>(no_filter, DlBlendMode::kSrcOver);
preroll_context()->surface_needs_readback = false;
layer2->Preroll(preroll_context());
EXPECT_FALSE(preroll_context()->surface_needs_readback);
// BDF with no filter does not block prior readback value
preroll_context()->surface_needs_readback = true;
layer2->Preroll(preroll_context());
EXPECT_TRUE(preroll_context()->surface_needs_readback);
// BDF with no filter blocks child with readback
auto mock_layer = std::make_shared<MockLayer>(SkPath(), DlPaint());
mock_layer->set_fake_reads_surface(true);
layer2->Add(mock_layer);
preroll_context()->surface_needs_readback = false;
layer2->Preroll(preroll_context());
EXPECT_FALSE(preroll_context()->surface_needs_readback);
}
TEST_F(BackdropFilterLayerTest, OpacityInheritance) {
auto backdrop_filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);
const SkPath mock_path = SkPath().addRect(SkRect::MakeLTRB(0, 0, 10, 10));
const DlPaint mock_paint = DlPaint(DlColor::kRed());
const SkRect clip_rect = SkRect::MakeLTRB(0, 0, 100, 100);
auto clip = std::make_shared<ClipRectLayer>(clip_rect, Clip::hardEdge);
auto parent = std::make_shared<OpacityLayer>(128, SkPoint::Make(0, 0));
auto layer = std::make_shared<BackdropFilterLayer>(backdrop_filter.shared(),
DlBlendMode::kSrcOver);
auto child = std::make_shared<MockLayer>(mock_path, mock_paint);
layer->Add(child);
parent->Add(layer);
clip->Add(parent);
clip->Preroll(preroll_context());
clip->Paint(display_list_paint_context());
DisplayListBuilder expected_builder(clip_rect);
/* ClipRectLayer::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRect(clip_rect, DlCanvas::ClipOp::kIntersect, false);
/* OpacityLayer::Paint */ {
// NOP - it hands opacity down to BackdropFilterLayer
/* BackdropFilterLayer::Paint */ {
DlPaint save_paint;
save_paint.setAlpha(128);
expected_builder.SaveLayer(&clip_rect, &save_paint, &backdrop_filter);
{
/* MockLayer::Paint */ {
DlPaint child_paint;
child_paint.setColor(DlColor::kRed());
expected_builder.DrawPath(mock_path, child_paint);
}
}
expected_builder.Restore();
}
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
using BackdropLayerDiffTest = DiffContextTest;
TEST_F(BackdropLayerDiffTest, BackdropLayer) {
auto filter = DlBlurImageFilter(10, 10, DlTileMode::kClamp);
{
// tests later assume 30px readback area, fail early if that's not the case
SkIRect readback;
EXPECT_EQ(filter.get_input_device_bounds(SkIRect::MakeWH(10, 10),
SkMatrix::I(), readback),
&readback);
EXPECT_EQ(readback, SkIRect::MakeLTRB(-30, -30, 40, 40));
}
MockLayerTree l1(SkISize::Make(100, 100));
l1.root()->Add(std::make_shared<BackdropFilterLayer>(filter.shared(),
DlBlendMode::kSrcOver));
// no clip, effect over entire surface
auto damage = DiffLayerTree(l1, MockLayerTree(SkISize::Make(100, 100)));
EXPECT_EQ(damage.frame_damage, SkIRect::MakeWH(100, 100));
MockLayerTree l2(SkISize::Make(100, 100));
auto clip = std::make_shared<ClipRectLayer>(SkRect::MakeLTRB(20, 20, 60, 60),
Clip::hardEdge);
clip->Add(std::make_shared<BackdropFilterLayer>(filter.shared(),
DlBlendMode::kSrcOver));
l2.root()->Add(clip);
damage = DiffLayerTree(l2, MockLayerTree(SkISize::Make(100, 100)));
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 90, 90));
MockLayerTree l3;
auto scale = std::make_shared<TransformLayer>(SkMatrix::Scale(2.0, 2.0));
scale->Add(clip);
l3.root()->Add(scale);
damage = DiffLayerTree(l3, MockLayerTree());
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 180, 180));
MockLayerTree l4;
l4.root()->Add(scale);
// path just outside of readback region, doesn't affect blur
auto path1 = SkPath().addRect(SkRect::MakeLTRB(180, 180, 190, 190));
l4.root()->Add(std::make_shared<MockLayer>(path1));
damage = DiffLayerTree(l4, l3);
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(180, 180, 190, 190));
MockLayerTree l5;
l5.root()->Add(scale);
// path just inside of readback region, must trigger backdrop repaint
auto path2 = SkPath().addRect(SkRect::MakeLTRB(179, 179, 189, 189));
l5.root()->Add(std::make_shared<MockLayer>(path2));
damage = DiffLayerTree(l5, l4);
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 190, 190));
}
TEST_F(BackdropLayerDiffTest, BackdropLayerInvalidTransform) {
auto filter = DlBlurImageFilter(10, 10, DlTileMode::kClamp);
{
// tests later assume 30px readback area, fail early if that's not the case
SkIRect readback;
EXPECT_EQ(filter.get_input_device_bounds(SkIRect::MakeWH(10, 10),
SkMatrix::I(), readback),
&readback);
EXPECT_EQ(readback, SkIRect::MakeLTRB(-30, -30, 40, 40));
}
MockLayerTree l1(SkISize::Make(100, 100));
SkMatrix transform;
transform.setPerspX(0.1);
transform.setPerspY(0.1);
auto transform_layer = std::make_shared<TransformLayer>(transform);
l1.root()->Add(transform_layer);
transform_layer->Add(std::make_shared<BackdropFilterLayer>(
filter.shared(), DlBlendMode::kSrcOver));
auto damage = DiffLayerTree(l1, MockLayerTree(SkISize::Make(100, 100)));
EXPECT_EQ(damage.frame_damage, SkIRect::MakeWH(100, 100));
}
} // namespace testing
} // namespace flutter