blob: f51d8d5ca2fae936a5d9e0e27c4e9421658531d1 [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/physical_shape_layer.h"
#include "flutter/display_list/display_list_canvas_dispatcher.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"
#include "flutter/testing/mock_canvas.h"
namespace flutter {
namespace testing {
using PhysicalShapeLayerTest = LayerTest;
#ifndef NDEBUG
TEST_F(PhysicalShapeLayerTest, PaintingEmptyLayerDies) {
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorBLACK, SK_ColorBLACK,
0.0f, // elevation
SkPath(), Clip::none);
layer->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty());
EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty());
EXPECT_FALSE(layer->needs_painting(paint_context()));
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(context\\)");
}
TEST_F(PhysicalShapeLayerTest, PaintBeforePrerollDies) {
SkPath child_path;
child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
auto mock_layer = std::make_shared<MockLayer>(child_path, SkPaint());
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorBLACK, SK_ColorBLACK,
0.0f, // elevation
SkPath(), Clip::none);
layer->Add(mock_layer);
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(context\\)");
}
#endif
TEST_F(PhysicalShapeLayerTest, NonEmptyLayer) {
SkPath layer_path;
layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, Clip::none);
layer->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds());
EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty());
EXPECT_TRUE(layer->needs_painting(paint_context()));
SkPaint layer_paint;
layer_paint.setColor(SK_ColorGREEN);
layer_paint.setAntiAlias(true);
layer->Paint(paint_context());
EXPECT_EQ(mock_canvas().draw_calls(),
std::vector({MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));
}
TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathClip) {
SkPath layer_path;
layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
SkPath child1_path;
child1_path.addRect(4, 0, 12, 12).close();
SkPath child2_path;
child2_path.addRect(3, 2, 5, 15).close();
auto child1 = std::make_shared<PhysicalShapeLayer>(SK_ColorRED, SK_ColorBLACK,
0.0f, // elevation
child1_path, Clip::none);
auto child2 =
std::make_shared<PhysicalShapeLayer>(SK_ColorBLUE, SK_ColorBLACK,
0.0f, // elevation
child2_path, Clip::none);
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, Clip::hardEdge);
layer->Add(child1);
layer->Add(child2);
SkRect child_paint_bounds = SkRect::MakeEmpty();
layer->Preroll(preroll_context());
child_paint_bounds.join(child1->paint_bounds());
child_paint_bounds.join(child2->paint_bounds());
EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds());
EXPECT_NE(layer->paint_bounds(), child_paint_bounds);
EXPECT_EQ(layer->child_paint_bounds(), child_paint_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
SkPaint layer_paint;
layer_paint.setColor(SK_ColorGREEN);
layer_paint.setAntiAlias(true);
SkPaint child1_paint;
child1_paint.setColor(SK_ColorRED);
child1_paint.setAntiAlias(true);
SkPaint child2_paint;
child2_paint.setColor(SK_ColorBLUE);
child2_paint.setAntiAlias(true);
layer->Paint(paint_context());
EXPECT_EQ(mock_canvas().draw_calls(),
std::vector({
MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
MockCanvas::DrawCall{
1, MockCanvas::ClipRectData{layer_path.getBounds(),
SkClipOp::kIntersect}},
MockCanvas::DrawCall{
1, MockCanvas::DrawPathData{child1_path, child1_paint}},
// Child 2 is rendered when using Skia as a state delegate
// because the quickReject tests are conservative.
MockCanvas::DrawCall{
1, MockCanvas::DrawPathData{child2_path, child2_paint}},
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}},
}));
DisplayListBuilder expected_builder;
{ // layer::Paint()
expected_builder.drawPath(
layer_path, DlPaint().setColor(DlColor::kGreen()).setAntiAlias(true));
expected_builder.save();
{
expected_builder.clipPath(layer_path, SkClipOp::kIntersect, false);
{ // child1::Paint()
expected_builder.drawPath(
child1_path,
DlPaint().setColor(DlColor::kRed()).setAntiAlias(true));
}
// child2::Paint() is not called due to layer cullling
// This is the expected and intended behavior.
}
expected_builder.restore();
}
layer->Paint(display_list_paint_context());
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathNoClip) {
SkPath layer_path;
layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
SkPath child1_path;
child1_path.addRect(4, 0, 12, 12).close();
SkPath child2_path;
child2_path.addRect(3, 2, 5, 15).close();
auto child1 = std::make_shared<PhysicalShapeLayer>(SK_ColorRED, SK_ColorBLACK,
0.0f, // elevation
child1_path, Clip::none);
auto child2 =
std::make_shared<PhysicalShapeLayer>(SK_ColorBLUE, SK_ColorBLACK,
0.0f, // elevation
child2_path, Clip::none);
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, Clip::none);
layer->Add(child1);
layer->Add(child2);
layer->Preroll(preroll_context());
SkRect child_bounds = child1->paint_bounds();
child_bounds.join(child2->paint_bounds());
SkRect total_bounds = child_bounds;
total_bounds.join(layer_path.getBounds());
EXPECT_NE(layer->paint_bounds(), layer_path.getBounds());
EXPECT_EQ(layer->paint_bounds(), total_bounds);
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
SkPaint layer_paint;
layer_paint.setColor(SK_ColorGREEN);
layer_paint.setAntiAlias(true);
SkPaint child1_paint;
child1_paint.setColor(SK_ColorRED);
child1_paint.setAntiAlias(true);
SkPaint child2_paint;
child2_paint.setColor(SK_ColorBLUE);
child2_paint.setAntiAlias(true);
layer->Paint(paint_context());
EXPECT_EQ(
mock_canvas().draw_calls(),
std::vector({MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{child1_path, child1_paint}},
MockCanvas::DrawCall{0, MockCanvas::DrawPathData{
child2_path, child2_paint}}}));
}
TEST_F(PhysicalShapeLayerTest, ElevationSimple) {
constexpr float initial_elevation = 20.0f;
SkPath layer_path;
layer_path.addRect(0, 0, 8, 8).close();
auto layer = std::make_shared<PhysicalShapeLayer>(
SK_ColorGREEN, SK_ColorBLACK, initial_elevation, layer_path, Clip::none);
layer->Preroll(preroll_context());
// The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
// their shadows , so we do not do any painting there.
EXPECT_EQ(layer->paint_bounds(),
DisplayListCanvasDispatcher::ComputeShadowBounds(
layer_path, initial_elevation, 1.0f, SkMatrix()));
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(layer->elevation(), initial_elevation);
SkPaint layer_paint;
layer_paint.setColor(SK_ColorGREEN);
layer_paint.setAntiAlias(true);
layer->Paint(paint_context());
EXPECT_EQ(
mock_canvas().draw_calls(),
std::vector(
{MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},
MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));
}
TEST_F(PhysicalShapeLayerTest, ElevationComplex) {
// The layer tree should look like this:
// layers[0] +1.0f = 1.0f
// | \
// | \
// | \
// | layers[2] +3.0f = 4.0f
// | |
// | layers[3] +4.0f = 8.0f
// |
// |
// layers[1] + 2.0f = 3.0f
constexpr float initial_elevations[4] = {1.0f, 2.0f, 3.0f, 4.0f};
SkPath layer_path;
layer_path.addRect(0, 0, 80, 80).close();
std::shared_ptr<PhysicalShapeLayer> layers[4];
for (int i = 0; i < 4; i += 1) {
layers[i] = std::make_shared<PhysicalShapeLayer>(
SK_ColorBLACK, SK_ColorBLACK, initial_elevations[i], layer_path,
Clip::none);
}
layers[0]->Add(layers[1]);
layers[0]->Add(layers[2]);
layers[2]->Add(layers[3]);
layers[0]->Preroll(preroll_context());
for (int i = 0; i < 4; i += 1) {
// On Fuchsia, the system compositor handles all elevated
// PhysicalShapeLayers and their shadows , so we do not do any painting
// there.
SkRect paint_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
layer_path, initial_elevations[i], 1.0f /* pixel_ratio */, SkMatrix());
// Without clipping the children will be painted as well
for (auto layer : layers[i]->layers()) {
paint_bounds.join(layer->paint_bounds());
}
EXPECT_EQ(layers[i]->paint_bounds(), paint_bounds);
EXPECT_TRUE(layers[i]->needs_painting(paint_context()));
}
SkPaint layer_paint;
layer_paint.setColor(SK_ColorBLACK);
layer_paint.setAntiAlias(true);
layers[0]->Paint(paint_context());
EXPECT_EQ(
mock_canvas().draw_calls(),
std::vector(
{MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},
MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},
MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},
MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},
MockCanvas::DrawCall{
0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));
}
TEST_F(PhysicalShapeLayerTest, ShadowNotDependsCtm) {
constexpr SkScalar elevations[] = {1, 2, 3, 4, 5, 10};
constexpr SkScalar scales[] = {0.5, 1, 1.5, 2, 3, 5};
constexpr SkScalar translates[] = {0, 1, -1, 0.5, 2, 10};
SkPath path;
path.addRect(0, 0, 8, 8).close();
for (SkScalar elevation : elevations) {
SkRect baseline_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
path, elevation, 1.0f, SkMatrix());
for (SkScalar scale : scales) {
for (SkScalar translate_x : translates) {
for (SkScalar translate_y : translates) {
SkMatrix ctm;
ctm.setScaleTranslate(scale, scale, translate_x, translate_y);
SkRect bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
path, elevation, scale, ctm);
EXPECT_FLOAT_EQ(bounds.fLeft, baseline_bounds.fLeft);
EXPECT_FLOAT_EQ(bounds.fTop, baseline_bounds.fTop);
EXPECT_FLOAT_EQ(bounds.fRight, baseline_bounds.fRight);
EXPECT_FLOAT_EQ(bounds.fBottom, baseline_bounds.fBottom);
}
}
}
}
}
static int RasterizedDifferenceInPixels(
const std::function<void(SkCanvas*)>& actual_draw_function,
const std::function<void(SkCanvas*)>& expected_draw_function,
const SkSize& canvas_size) {
sk_sp<SkSurface> actual_surface =
SkSurface::MakeRasterN32Premul(canvas_size.width(), canvas_size.height());
sk_sp<SkSurface> expected_surface =
SkSurface::MakeRasterN32Premul(canvas_size.width(), canvas_size.height());
actual_surface->getCanvas()->drawColor(SK_ColorWHITE);
expected_surface->getCanvas()->drawColor(SK_ColorWHITE);
actual_draw_function(actual_surface->getCanvas());
expected_draw_function(expected_surface->getCanvas());
SkPixmap actual_pixels;
EXPECT_TRUE(actual_surface->peekPixels(&actual_pixels));
SkPixmap expected_pixels;
EXPECT_TRUE(expected_surface->peekPixels(&expected_pixels));
int different_pixels = 0;
for (int y = 0; y < canvas_size.height(); y++) {
const uint32_t* actual_row = actual_pixels.addr32(0, y);
const uint32_t* expected_row = expected_pixels.addr32(0, y);
for (int x = 0; x < canvas_size.width(); x++) {
if (actual_row[x] != expected_row[x]) {
different_pixels++;
}
}
}
return different_pixels;
}
TEST_F(PhysicalShapeLayerTest, ShadowNotDependsPathSize) {
constexpr SkRect test_cases[][2] = {
{{20, -100, 80, 80}, {20, -1000, 80, 80}},
{{20, 20, 80, 200}, {20, 20, 80, 2000}},
};
for (const SkRect* test_case : test_cases) {
EXPECT_EQ(RasterizedDifferenceInPixels(
[=](SkCanvas* canvas) {
SkPath path;
path.addRect(test_case[0]).close();
DisplayListCanvasDispatcher::DrawShadow(
canvas, path, SK_ColorBLACK, 1.0f, false, 1.0f);
},
[=](SkCanvas* canvas) {
SkPath path;
path.addRect(test_case[1]).close();
DisplayListCanvasDispatcher::DrawShadow(
canvas, path, SK_ColorBLACK, 1.0f, false, 1.0f);
},
SkSize::Make(100, 100)),
0);
}
}
static bool ReadbackResult(PrerollContext* context,
Clip clip_behavior,
const std::shared_ptr<Layer>& child,
bool before) {
const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const SkPath layer_path = SkPath().addRect(layer_bounds);
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, clip_behavior);
if (child != nullptr) {
layer->Add(child);
}
context->surface_needs_readback = before;
layer->Preroll(context);
return context->surface_needs_readback;
}
TEST_F(PhysicalShapeLayerTest, Readback) {
PrerollContext* context = preroll_context();
SkPath path;
SkPaint paint;
const Clip hard = Clip::hardEdge;
const Clip soft = Clip::antiAlias;
const Clip save_layer = Clip::antiAliasWithSaveLayer;
std::shared_ptr<MockLayer> nochild;
auto reader = std::make_shared<MockLayer>(path, paint);
reader->set_fake_reads_surface(true);
auto nonreader = std::make_shared<MockLayer>(path, paint);
// No children, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
// No children, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
// Non readback child, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
// Non readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
// Readback child, no prior readback -> readback after unless SaveLayer
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
// Readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
}
TEST_F(PhysicalShapeLayerTest, OpacityInheritance) {
SkPath layer_path;
layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, Clip::none);
PrerollContext* context = preroll_context();
context->renderable_state_flags = 0;
layer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, 0);
}
using PhysicalShapeLayerDiffTest = DiffContextTest;
TEST_F(PhysicalShapeLayerDiffTest, NoClipPaintRegion) {
MockLayerTree tree1;
const SkPath layer_path = SkPath().addRect(SkRect::MakeXYWH(0, 0, 100, 100));
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, Clip::none);
const SkPath layer_path2 =
SkPath().addRect(SkRect::MakeXYWH(200, 200, 200, 200));
auto layer2 = std::make_shared<MockLayer>(layer_path2);
layer->Add(layer2);
tree1.root()->Add(layer);
auto damage = DiffLayerTree(tree1, MockLayerTree());
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 400, 400));
}
TEST_F(PhysicalShapeLayerDiffTest, ClipPaintRegion) {
MockLayerTree tree1;
const SkPath layer_path = SkPath().addRect(SkRect::MakeXYWH(0, 0, 100, 100));
auto layer =
std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,
0.0f, // elevation
layer_path, Clip::hardEdge);
const SkPath layer_path2 =
SkPath().addRect(SkRect::MakeXYWH(200, 200, 200, 200));
auto layer2 = std::make_shared<MockLayer>(layer_path2);
layer->Add(layer2);
tree1.root()->Add(layer);
auto damage = DiffLayerTree(tree1, MockLayerTree());
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 100, 100));
}
} // namespace testing
} // namespace flutter