blob: e81c28080ea5397b728c8363c5e1a47553a58664 [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/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(), SkMatrix());
EXPECT_EQ(layer->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(), SkMatrix());
EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds());
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, ChildrenLargerThanPath) {
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);
SkRect child_paint_bounds;
layer->Preroll(preroll_context(), SkMatrix());
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_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(), SkMatrix());
// The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
// their shadows , so we do not do any painting there.
EXPECT_EQ(layer->paint_bounds(),
PhysicalShapeLayer::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(), SkMatrix());
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.
EXPECT_EQ(layers[i]->paint_bounds(),
(PhysicalShapeLayer::ComputeShadowBounds(
layer_path, initial_elevations[i], 1.0f /* pixel_ratio */,
SkMatrix())));
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 = PhysicalShapeLayer::ComputeShadowBounds(
path, elevation, 1.0f, SkMatrix());
for (SkScalar scale : scales) {
for (SkScalar translateX : translates) {
for (SkScalar translateY : translates) {
SkMatrix ctm;
ctm.setScaleTranslate(scale, scale, translateX, translateY);
SkRect bounds = PhysicalShapeLayer::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();
PhysicalShapeLayer::DrawShadow(canvas, path, SK_ColorBLACK,
1.0f, false, 1.0f);
},
[=](SkCanvas* canvas) {
SkPath path;
path.addRect(test_case[1]).close();
PhysicalShapeLayer::DrawShadow(canvas, path, SK_ColorBLACK,
1.0f, false, 1.0f);
},
SkSize::Make(100, 100)),
0);
}
}
static bool ReadbackResult(PrerollContext* context,
Clip clip_behavior,
std::shared_ptr<Layer> child,
bool before) {
const SkMatrix initial_matrix = SkMatrix();
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, initial_matrix);
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, false, 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));
}
} // namespace testing
} // namespace flutter