| // 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 |