diff --git a/BUILD.gn b/BUILD.gn index 5fd4f5482f601..240b440aec2e7 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -94,7 +94,10 @@ group("flutter") { # Fuchsia currently only supports a subset of our unit tests if (is_fuchsia) { - public_deps += [ "$flutter_root/fml:fml_tests" ] + public_deps += [ + "$flutter_root/flow:flow_tests", + "$flutter_root/fml:fml_tests", + ] } } diff --git a/flow/BUILD.gn b/flow/BUILD.gn index c2f0c98415a3e..19d7ad6747639 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -4,8 +4,8 @@ if (is_fuchsia) { import("//build/fuchsia/sdk.gni") + import("$flutter_root/tools/fuchsia/fuchsia_archive.gni") } - import("$flutter_root/testing/testing.gni") source_set("flow") { @@ -102,6 +102,22 @@ test_fixtures("flow_fixtures") { fixtures = [] } +source_set("flow_testing") { + testonly = true + + sources = [ + "testing/layer_test.h", + "testing/mock_layer.cc", + "testing/mock_layer.h", + ] + + public_deps = [ + ":flow", + "$flutter_root/testing:skia", + "//third_party/googletest:gtest", + ] +} + executable("flow_unittests") { testonly = true @@ -109,8 +125,21 @@ executable("flow_unittests") { "flow_run_all_unittests.cc", "flow_test_utils.cc", "flow_test_utils.h", + "layers/backdrop_filter_layer_unittests.cc", + "layers/clip_path_layer_unittests.cc", + "layers/clip_rect_layer_unittests.cc", + "layers/clip_rrect_layer_unittests.cc", + "layers/color_filter_layer_unittests.cc", + "layers/container_layer_unittests.cc", + "layers/layer_tree_unittests.cc", + "layers/opacity_layer_unittests.cc", "layers/performance_overlay_layer_unittests.cc", "layers/physical_shape_layer_unittests.cc", + "layers/picture_layer_unittests.cc", + "layers/platform_view_layer_unittests.cc", + "layers/shader_mask_layer_unittests.cc", + "layers/texture_layer_unittests.cc", + "layers/transform_layer_unittests.cc", "matrix_decomposition_unittests.cc", "mutators_stack_unittests.cc", "raster_cache_unittests.cc", @@ -121,10 +150,22 @@ executable("flow_unittests") { deps = [ ":flow", ":flow_fixtures", + ":flow_testing", "$flutter_root/fml", + "$flutter_root/testing:skia", "$flutter_root/testing:testing_lib", "//third_party/dart/runtime:libdart_jit", # for tracing "//third_party/googletest:gtest", "//third_party/skia", ] } + +if (is_fuchsia) { + fuchsia_test_archive("flow_tests") { + deps = [ + ":flow_unittests", + ] + + binary = "flow_unittests" + } +} diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 13c3feefd1e89..f2f18c6474c21 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -142,6 +142,7 @@ class MutatorsStack { // Returns an iterator pointing to the bottom of the stack. const std::vector>::const_reverse_iterator Bottom() const; + bool empty() const { return vector_.empty(); } bool operator==(const MutatorsStack& other) const { if (vector_.size() != other.vector_.size()) { diff --git a/flow/layers/backdrop_filter_layer_unittests.cc b/flow/layers/backdrop_filter_layer_unittests.cc new file mode 100644 index 0000000000000..641db8f4c6bea --- /dev/null +++ b/flow/layers/backdrop_filter_layer_unittests.cc @@ -0,0 +1,188 @@ +// Copyright 2019 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/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/effects/SkImageFilters.h" + +namespace flutter { +namespace testing { + +static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); + +using BackdropFilterLayerDeathTest = LayerTest; +using BackdropFilterLayerTest = LayerTest; + +TEST_F(BackdropFilterLayerDeathTest, EmptyLayer) { + auto layer = std::make_shared(sk_sp()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(BackdropFilterLayerDeathTest, PaintBeforePreroll) { + 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(child_path); + auto layer = std::make_shared(sk_sp()); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(BackdropFilterLayerTest, EmptyFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix(initial_transform); + mock_layer->ExpectMutators({}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), nullptr, 1}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(BackdropFilterLayerTest, SimpleFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix(initial_transform); + mock_layer->ExpectMutators({}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, + MockCanvas::SaveLayerData{child_bounds, SkPaint(), layer_filter, 1}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(BackdropFilterLayerTest, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + 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_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectMutators({}); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectMutators({}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, SkPaint(), + layer_filter, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(BackdropFilterLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto layer_filter2 = SkImageFilters::Paint(SkPaint(SkColors::kDkGray)); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1); + auto layer2 = std::make_shared(layer_filter2); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer1->Preroll(preroll_context(), initial_transform); + 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(), mock_layer2->paint_bounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectMutators({}); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectMutators({}); + + layer1->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, SkPaint(), + layer_filter1, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_path2.getBounds(), SkPaint(), + layer_filter2, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/clip_path_layer_unittests.cc b/flow/layers/clip_path_layer_unittests.cc new file mode 100644 index 0000000000000..22bd2c21f7bf2 --- /dev/null +++ b/flow/layers/clip_path_layer_unittests.cc @@ -0,0 +1,206 @@ +// Copyright 2019 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/clip_path_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 { + +static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); + +using ClipPathLayerDeathTest = LayerTest; +using ClipPathLayerTest = LayerTest; + +TEST_F(ClipPathLayerDeathTest, ClipNone) { + EXPECT_DEATH_IF_SUPPORTED( + std::make_shared(SkPath(), Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipPathLayerDeathTest, EmptyLayer) { + auto layer = std::make_shared(SkPath(), Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipPathLayerDeathTest, PaintBeforePreroll) { + 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(layer_path, Clip::hardEdge); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipPathLayerDeathTest, CulledLayer) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + preroll_context()->cull_rect = kEmptyRect; // Cull everything + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kEmptyRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(kEmptyRect); + mock_layer->ExpectParentMatrix(SkMatrix()); + mock_layer->ExpectMutators({}); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipPathLayerTest, ChildOutsideBounds) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(intersect_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_path)}); + + // TODO(dworsham): This seems like a bug. This should be a death test. + // Even though `context->cull_rect` and `child_bounds` are disjoint, `layer` + // and `mock_layer` still get drawn. + // + // Should we do + // `context->cull_rect.intersect(child_paint_bounds); + // set_paint_bounds(context->cull_rect);` + // inside of |ClipPathLayer| instead? + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ClipPathLayerTest, FullyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(layer_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_path)}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ClipPathLayerTest, PartiallyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + // TODO(dworsham): This seems like a bug. It doesn't take + // `context->cull_rect` into account at all. + // + // Should we do + // `context->cull_rect.intersect(child_paint_bounds); + // set_paint_bounds(context->cull_rect);` + // inside of |ClipPathLayer| instead? + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(intersect_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_path)}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/clip_rect_layer_unittests.cc b/flow/layers/clip_rect_layer_unittests.cc new file mode 100644 index 0000000000000..bde03bdba9170 --- /dev/null +++ b/flow/layers/clip_rect_layer_unittests.cc @@ -0,0 +1,201 @@ +// Copyright 2019 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/clip_rect_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 { + +static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); + +using ClipRectLayerDeathTest = LayerTest; +using ClipRectLayerTest = LayerTest; + +TEST_F(ClipRectLayerDeathTest, ClipNone) { + EXPECT_DEATH_IF_SUPPORTED( + std::make_shared(kEmptyRect, Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipRectLayerDeathTest, EmptyLayer) { + auto layer = std::make_shared(kEmptyRect, Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRectLayerDeathTest, PaintBeforePreroll) { + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRectLayerDeathTest, CulledLayer) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + preroll_context()->cull_rect = kEmptyRect; // Cull everything + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kEmptyRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(kEmptyRect); + mock_layer->ExpectParentMatrix(SkMatrix()); + mock_layer->ExpectMutators({}); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRectLayerTest, ChildOutsideBounds) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(intersect_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_bounds)}); + + // TODO(dworsham): This seems like a bug. This should be a death test. + // Even though `context->cull_rect` and `child_bounds` are disjoint, `layer` + // and `mock_layer` still get drawn. + // + // Should we do + // `context->cull_rect.intersect(child_paint_bounds); + // set_paint_bounds(context->cull_rect);` + // inside of |ClipRectLayer| instead? + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ClipRectLayerTest, FullyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(layer_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_bounds)}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ClipRectLayerTest, PartiallyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + // TODO(dworsham): This seems like a bug. It doesn't take + // `context->cull_rect` into account at all. + // + // Should we do + // `context->cull_rect.intersect(child_paint_bounds); + // set_paint_bounds(context->cull_rect);` + // inside of |ClipRectLayer| instead? + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(intersect_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_bounds)}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/clip_rrect_layer_unittests.cc b/flow/layers/clip_rrect_layer_unittests.cc new file mode 100644 index 0000000000000..3568597f3022b --- /dev/null +++ b/flow/layers/clip_rrect_layer_unittests.cc @@ -0,0 +1,209 @@ +// Copyright 2019 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/clip_rrect_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 { + +static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); + +using ClipRRectLayerDeathTest = LayerTest; +using ClipRRectLayerTest = LayerTest; + +TEST_F(ClipRRectLayerDeathTest, ClipNone) { + const SkRRect layer_rrect = SkRRect::MakeEmpty(); + EXPECT_DEATH_IF_SUPPORTED( + std::make_shared(layer_rrect, Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipRRectLayerDeathTest, EmptyLayer) { + const SkRRect layer_rrect = SkRRect::MakeEmpty(); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRRectLayerDeathTest, PaintBeforePreroll) { + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRRectLayerDeathTest, CulledLayer) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + preroll_context()->cull_rect = kEmptyRect; // Cull everything + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kEmptyRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(kEmptyRect); + mock_layer->ExpectParentMatrix(SkMatrix()); + mock_layer->ExpectMutators({}); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRRectLayerTest, ChildOutsideBounds) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(intersect_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_rrect)}); + + // TODO(dworsham): This seems like a bug. This should be a death test. + // Even though `context->cull_rect` and `child_bounds` are disjoint, `layer` + // and `mock_layer` still get drawn. + // + // Should we do + // `context->cull_rect.intersect(child_paint_bounds); + // set_paint_bounds(context->cull_rect);` + // inside of |ClipRRectLayer| instead? + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ClipRRectLayerTest, FullyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(layer_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_rrect)}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ClipRRectLayerTest, PartiallyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Leaves untouched + EXPECT_TRUE(preroll_context()->mutators_stack.empty()); // Leaves untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + // TODO(dworsham): This seems like a bug. It doesn't take + // `context->cull_rect` into account at all. + // + // Should we do + // `context->cull_rect.intersect(child_paint_bounds); + // set_paint_bounds(context->cull_rect);` + // inside of |ClipRRectLayer| instead? + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentCullRect(intersect_bounds); + mock_layer->ExpectParentMatrix(initial_matrix); + mock_layer->ExpectMutators({Mutator(layer_rrect)}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc new file mode 100644 index 0000000000000..8b33512c8c2f6 --- /dev/null +++ b/flow/layers/color_filter_layer_unittests.cc @@ -0,0 +1,202 @@ +// Copyright 2019 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/color_filter_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" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/effects/SkColorMatrixFilter.h" + +namespace flutter { +namespace testing { + +static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); + +using ColorFilterLayerDeathTest = LayerTest; +using ColorFilterLayerTest = LayerTest; + +TEST_F(ColorFilterLayerDeathTest, EmptyLayer) { + auto layer = std::make_shared(sk_sp()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ColorFilterLayerDeathTest, PaintBeforePreroll) { + 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(child_path); + auto layer = std::make_shared(sk_sp()); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ColorFilterLayerTest, EmptyFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix(initial_transform); + mock_layer->ExpectMutators({}); + + SkPaint filter_paint; + filter_paint.setColorFilter(nullptr); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, + MockCanvas::SaveLayerData{child_bounds, filter_paint, nullptr, 1}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ColorFilterLayerTest, SimpleFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix(initial_transform); + mock_layer->ExpectMutators({}); + + SkPaint filter_paint; + filter_paint.setColorFilter(layer_filter); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, + MockCanvas::SaveLayerData{child_bounds, filter_paint, nullptr, 1}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ColorFilterLayerTest, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + 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_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectMutators({}); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectMutators({}); + + SkPaint filter_paint; + filter_paint.setColorFilter(layer_filter); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, filter_paint, nullptr, + 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ColorFilterLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(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 SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto layer_filter2 = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorMAGENTA, SK_ColorBLUE); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1); + auto layer2 = std::make_shared(layer_filter2); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer1->Preroll(preroll_context(), initial_transform); + 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(), mock_layer2->paint_bounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectMutators({}); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectMutators({}); + + SkPaint filter_paint1, filter_paint2; + filter_paint1.setColorFilter(layer_filter1); + filter_paint2.setColorFilter(layer_filter2); + layer1->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, filter_paint1, nullptr, + 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_path2.getBounds(), filter_paint2, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index d5c6a2a03a34a..0121758fb5fa6 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -23,6 +23,12 @@ void ContainerLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(child_paint_bounds); } +void ContainerLayer::Paint(PaintContext& context) const { + FML_DCHECK(needs_painting()); + + PaintChildren(context); +} + void ContainerLayer::PrerollChildren(PrerollContext* context, const SkMatrix& child_matrix, SkRect* child_paint_bounds) { diff --git a/flow/layers/container_layer.h b/flow/layers/container_layer.h index ef1c03328d1df..687d750aa4e9d 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -18,7 +18,7 @@ class ContainerLayer : public Layer { void Add(std::shared_ptr layer); void Preroll(PrerollContext* context, const SkMatrix& matrix) override; - + void Paint(PaintContext& context) const override; #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/container_layer_unittests.cc b/flow/layers/container_layer_unittests.cc new file mode 100644 index 0000000000000..88d11a82f108c --- /dev/null +++ b/flow/layers/container_layer_unittests.cc @@ -0,0 +1,196 @@ +// Copyright 2019 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/container_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 ContainerLayerDeathTest = LayerTest; +using ContainerLayerTest = LayerTest; + +TEST_F(ContainerLayerDeathTest, LayerWithParentHasPlatformView) { + auto layer = std::make_shared(); + + preroll_context()->has_platform_view = true; + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), + "!context->has_platform_view"); +} + +TEST_F(ContainerLayerDeathTest, EmptyLayer) { + auto layer = std::make_shared(); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ContainerLayerDeathTest, PaintBeforePreroll) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ContainerLayerTest, Simple) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPaint child_paint(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), child_path.getBounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + mock_layer->ExpectParentMatrix(initial_transform); + mock_layer->ExpectParentHasPlatformView(false); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path, child_paint}}}); +} + +TEST_F(ContainerLayerTest, Multiple) { + SkPath child_path1; + child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child_path2; + child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f); + SkPaint child_paint1(SkColors::kGray); + SkPaint child_paint2(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer1 = std::make_shared( + child_path1, child_paint1, true /* fake_has_platform_view */); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect expected_total_bounds = child_path1.getBounds(); + expected_total_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_TRUE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_total_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectParentHasPlatformView(false); + mock_layer2->ExpectParentHasPlatformView(false); // Siblings are independent + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path2, child_paint2}}}); +} + +TEST_F(ContainerLayerTest, MultipleWithEmpty) { + SkPath child_path1; + child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPaint child_paint1(SkColors::kGray); + SkPaint child_paint2(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(SkPath(), child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), SkPath().getBounds()); + EXPECT_EQ(layer->paint_bounds(), child_path1.getBounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_FALSE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectParentHasPlatformView(false); + mock_layer2->ExpectParentHasPlatformView(false); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}}); +} + +TEST_F(ContainerLayerTest, NeedsSystemComposite) { + SkPath child_path1; + child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child_path2; + child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f); + SkPaint child_paint1(SkColors::kGray); + SkPaint child_paint2(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer1 = std::make_shared( + child_path1, child_paint1, false /* fake_has_platform_view */, + true /* fake_needs_system_composite */); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect expected_total_bounds = child_path1.getBounds(); + expected_total_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_total_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_TRUE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_TRUE(layer->needs_system_composite()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectParentHasPlatformView(false); + mock_layer2->ExpectParentHasPlatformView(false); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path2, child_paint2}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc new file mode 100644 index 0000000000000..57b77e6047df7 --- /dev/null +++ b/flow/layers/opacity_layer_unittests.cc @@ -0,0 +1,103 @@ +// Copyright 2019 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/opacity_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 OpacityLayerDeathTest = LayerTest; +using OpacityLayerTest = LayerTest; + +TEST_F(OpacityLayerDeathTest, LeafLayer) { + auto layer = + std::make_shared(SK_AlphaOPAQUE, SkPoint::Make(0.0f, 0.0f)); + + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), + "layers\\(\\)\\.size\\(\\)\\ > 0"); +} + +TEST_F(OpacityLayerDeathTest, EmptyLayer) { + auto mock_layer = std::make_shared(SkPath()); + auto layer = + std::make_shared(SK_AlphaOPAQUE, SkPoint::Make(0.0f, 0.0f)); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(mock_layer->paint_bounds(), SkPath().getBounds()); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(OpacityLayerDeathTest, PaintBeforePreroll) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path); + auto layer = + std::make_shared(SK_AlphaOPAQUE, SkPoint::Make(0.0f, 0.0f)); + layer->Add(mock_layer); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(OpacityLayerTest, FullyOpaque) { + const SkPoint child_offset = SkPoint::Make(0.5f, 1.5f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkMatrix child_transform = + SkMatrix::MakeTrans(child_offset.fX, child_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_child_transform = + RasterCache::GetIntegralTransCTM(child_transform); +#endif + const SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + const SkPaint child_paint = SkPaint(SkColors::kGreen); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(SK_AlphaOPAQUE, child_offset); + layer->Add(mock_layer); + + const SkRect layer_bounds = child_transform.mapRect(child_path.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), layer_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix( + SkMatrix::Concat(initial_transform, child_transform)); + mock_layer->ExpectMutators( + {Mutator(SK_AlphaOPAQUE), Mutator(child_transform)}); + + const SkPaint opacity_paint = SkPaint(SkColors::kBlack); // A = 1.0f + SkRect opacity_bounds; + layer_bounds.makeOffset(-child_offset.fX, -child_offset.fY) + .roundOut(&opacity_bounds); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{child_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_child_transform}}, +#endif + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{opacity_bounds, opacity_paint, nullptr, + 2}}, + MockCanvas::DrawCall{2, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index 605717c870ee3..0e87aaf4bb7fc 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -5,7 +5,11 @@ #include "flutter/flow/flow_test_utils.h" #include "flutter/flow/layers/performance_overlay_layer.h" #include "flutter/flow/raster_cache.h" +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/build_config.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/utils/SkBase64.h" @@ -14,6 +18,11 @@ #include +namespace flutter { +namespace testing { + +using PerformanceOverlayLayerTest = LayerTest; + // To get the size of kMockedTimes in compile time. template constexpr int size(const T (&array)[N]) noexcept { @@ -114,3 +123,6 @@ TEST(PerformanceOverlayLayer90fps, Gold) { TEST(PerformanceOverlayLayer120fps, Gold) { TestPerformanceOverlayLayerGold(120); } + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index 21c9265ce00dd..35041fb3c48d9 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -22,7 +22,9 @@ PhysicalShapeLayer::PhysicalShapeLayer(SkColor color, : color_(color), shadow_color_(shadow_color), device_pixel_ratio_(device_pixel_ratio), +#if defined(OS_FUCHSIA) viewport_depth_(viewport_depth), +#endif elevation_(elevation), path_(path), isRect_(false), @@ -65,48 +67,11 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context, // Let the system compositor draw all shadows for us. set_needs_system_composite(true); #else - // Add some margin to the paint bounds to leave space for the shadow. - // We fill this whole region and clip children to it so we don't need to - // join the child paint bounds. - // The offset is calculated as follows: - - // .--- (kLightRadius) - // -------/ (light) - // | / - // | / - // |/ - // |O - // /| (kLightHeight) - // / | - // / | - // / | - // / | - // ------------- (layer) - // /| | - // / | | (elevation) - // A / | |B - // ------------------------------------------------ (canvas) - // --- (extent of shadow) - // - // E = lt } t = (r + w/2)/h - // } => - // r + w/2 = ht } E = (l/h)(r + w/2) - // - // Where: E = extent of shadow - // l = elevation of layer - // r = radius of the light source - // w = width of the layer - // h = light height - // t = tangent of AOB, i.e., multiplier for elevation to extent - SkRect bounds(path_.getBounds()); - // tangent for x - double tx = (kLightRadius * device_pixel_ratio_ + bounds.width() * 0.5) / - kLightHeight; - // tangent for y - double ty = (kLightRadius * device_pixel_ratio_ + bounds.height() * 0.5) / - kLightHeight; - bounds.outset(elevation_ * tx, elevation_ * ty); - set_paint_bounds(bounds); + // We will draw the shadow in Paint(), so add some margin to the paint + // bounds to leave space for the shadow. We fill this whole region and clip + // children to it so we don't need to join the child paint bounds. + set_paint_bounds(ComputeShadowBounds(path_.getBounds(), elevation_, + device_pixel_ratio_)); #endif // defined(OS_FUCHSIA) } } @@ -188,6 +153,50 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { context.internal_nodes_canvas->restoreToCount(saveCount); } +SkRect PhysicalShapeLayer::ComputeShadowBounds(const SkRect& bounds, + float elevation, + float pixel_ratio) { + // The shadow offset is calculated as follows: + // .--- (kLightRadius) + // -------/ (light) + // | / + // | / + // |/ + // |O + // /| (kLightHeight) + // / | + // / | + // / | + // / | + // ------------- (layer) + // /| | + // / | | (elevation) + // A / | |B + // ------------------------------------------------ (canvas) + // --- (extent of shadow) + // + // E = lt } t = (r + w/2)/h + // } => + // r + w/2 = ht } E = (l/h)(r + w/2) + // + // Where: E = extent of shadow + // l = elevation of layer + // r = radius of the light source + // w = width of the layer + // h = light height + // t = tangent of AOB, i.e., multiplier for elevation to extent + // tangent for x + double tx = + (kLightRadius * pixel_ratio + bounds.width() * 0.5) / kLightHeight; + // tangent for y + double ty = + (kLightRadius * pixel_ratio + bounds.height() * 0.5) / kLightHeight; + SkRect shadow_bounds(bounds); + shadow_bounds.outset(elevation * tx, elevation * ty); + + return shadow_bounds; +} + void PhysicalShapeLayer::DrawShadow(SkCanvas* canvas, const SkPath& path, SkColor color, diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index f884fe02fc5bd..232494f9e0b1a 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -20,6 +20,9 @@ class PhysicalShapeLayer : public ContainerLayer { Clip clip_behavior); ~PhysicalShapeLayer() override; + static SkRect ComputeShadowBounds(const SkRect& bounds, + float elevation, + float pixel_ratio); static void DrawShadow(SkCanvas* canvas, const SkPath& path, SkColor color, @@ -35,19 +38,21 @@ class PhysicalShapeLayer : public ContainerLayer { void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) + float total_elevation() const { return total_elevation_; } + private: SkColor color_; SkColor shadow_color_; SkScalar device_pixel_ratio_; +#if defined(OS_FUCHSIA) float viewport_depth_; +#endif float elevation_ = 0.0f; float total_elevation_ = 0.0f; SkPath path_; bool isRect_; SkRRect frameRRect_; Clip clip_behavior_; - - friend class PhysicalShapeLayer_TotalElevation_Test; }; } // namespace flutter diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc index 972424a2fec6d..2d21a9c9e1e6f 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/flow/layers/physical_shape_layer_unittests.cc @@ -4,65 +4,212 @@ #include "flutter/flow/layers/physical_shape_layer.h" -#include "gtest/gtest.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 { -TEST(PhysicalShapeLayer, TotalElevation) { - std::shared_ptr layers[4]; +using PhysicalShapeLayerDeathTest = LayerTest; +using PhysicalShapeLayerTest = LayerTest; - SkColor dummy_color = 0; - SkPath dummy_path; - for (int i = 0; i < 4; i += 1) { - layers[i] = - std::make_shared(dummy_color, dummy_color, - 1.0f, // pixel ratio, - 1.0f, // depth - (float)(i + 1), // elevation - dummy_path, Clip::none); - } +TEST_F(PhysicalShapeLayerDeathTest, EmptyLayer) { + auto layer = + std::make_shared(SK_ColorBLACK, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + 0.0f, // elevation + SkPath(), Clip::none); - layers[0]->Add(layers[1]); - layers[0]->Add(layers[2]); - layers[2]->Add(layers[3]); + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(PhysicalShapeLayerDeathTest, PaintBeforePreroll) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = + std::make_shared(SK_ColorBLACK, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + 0.0f, // elevation + SkPath(), Clip::none); + layer->Add(mock_layer); - const Stopwatch unused_stopwatch; - TextureRegistry unused_texture_registry; - MutatorsStack unused_stack; - PrerollContext preroll_context{ - nullptr, // raster_cache (don't consult the cache) - nullptr, // gr_context (used for the raster cache) - nullptr, // external view embedder - unused_stack, // mutator stack - nullptr, // SkColorSpace* dst_color_space - kGiantRect, // SkRect cull_rect - unused_stopwatch, // frame time (dont care) - unused_stopwatch, // engine time (dont care) - unused_texture_registry, // texture registry (not supported) - false, // checkerboard_offscreen_layers - 0.0f, // total elevation - }; - - SkMatrix identity; - identity.setIdentity(); - - layers[0]->Preroll(&preroll_context, identity); - - // It should look like this: - // layers[0] +1.0f + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(PhysicalShapeLayerTest, NonEmptyLayer) { + SkPath layer_path; + layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto layer = + std::make_shared(SK_ColorGREEN, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + 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()); + EXPECT_FALSE(layer->needs_system_composite()); + + SkPaint layer_paint; + layer_paint.setColor(SK_ColorGREEN); + layer_paint.setAntiAlias(true); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls({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(SK_ColorRED, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + 0.0f, // elevation + child1_path, Clip::none); + auto child2 = + std::make_shared(SK_ColorBLUE, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + 0.0f, // elevation + child2_path, Clip::none); + auto layer = + std::make_shared(SK_ColorGREEN, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + 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()); + EXPECT_FALSE(layer->needs_system_composite()); + + 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()); + mock_canvas().ExpectDrawCalls( + {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( + SK_ColorGREEN, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + initial_elevation, layer_path, Clip::none); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), + PhysicalShapeLayer::ComputeShadowBounds(layer_path.getBounds(), + initial_elevation, 1.0f)); + EXPECT_EQ(layer->total_elevation(), initial_elevation); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + SkPaint layer_paint; + layer_paint.setColor(SK_ColorGREEN); + layer_paint.setAntiAlias(true); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {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 + // | layers[2] +3.0f = 4.0f // | | - // | layers[3] +4.0f + // | layers[3] +4.0f = 8.0f // | // | - // layers[1] + 2.0f - EXPECT_EQ(layers[0]->total_elevation_, 1.0f); - EXPECT_EQ(layers[1]->total_elevation_, 3.0f); - EXPECT_EQ(layers[2]->total_elevation_, 4.0f); - EXPECT_EQ(layers[3]->total_elevation_, 8.0f); + // layers[1] + 2.0f = 3.0f + constexpr float initial_elevations[4] = {1.0f, 2.0f, 3.0f, 4.0f}; + constexpr float total_elevations[4] = {1.0f, 3.0f, 4.0f, 8.0f}; + SkPath layer_path; + layer_path.addRect(0, 0, 80, 80).close(); + + std::shared_ptr layers[4]; + for (int i = 0; i < 4; i += 1) { + layers[i] = std::make_shared( + SK_ColorBLACK, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + 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) { + EXPECT_EQ(layers[i]->total_elevation(), total_elevations[i]); + EXPECT_EQ(layers[i]->paint_bounds(), + PhysicalShapeLayer::ComputeShadowBounds(layer_path.getBounds(), + initial_elevations[i], + 1.0f /* pixel_raio */)); + EXPECT_TRUE(layers[i]->needs_painting()); + } + + SkPaint layer_paint; + layer_paint.setColor(SK_ColorBLACK); + layer_paint.setAntiAlias(true); + layers[0]->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {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}}}); } +} // namespace testing } // namespace flutter diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc new file mode 100644 index 0000000000000..0121e91838e20 --- /dev/null +++ b/flow/layers/picture_layer_unittests.cc @@ -0,0 +1,102 @@ +// Copyright 2019 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. + +#define FML_USED_ON_EMBEDDER + +#include "flutter/flow/layers/picture_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkPicture.h" + +#ifndef SUPPORT_FRACTIONAL_TRANSLATION +#include "flutter/flow/raster_cache.h" +#endif + +namespace flutter { +namespace testing { + +using PictureLayerDeathTest = SkiaGPUObjectLayerTest; +using PictureLayerTest = SkiaGPUObjectLayerTest; + +TEST_F(PictureLayerDeathTest, InvalidPicture) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(), false, false); + + // TODO(dworsham): This crashes on a nullptr access; should be a DCHECK. + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), ""); +} + +TEST_F(PictureLayerDeathTest, PaintBeforePrerollInvalidPicture) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(), false, false); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "picture_\\.get\\(\\)"); +} + +TEST_F(PictureLayerDeathTest, PaintBeforePreroll) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(PictureLayerDeathTest, EmptyLayer) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkRect picture_bounds = SkRect::MakeEmpty(); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(PictureLayerTest, SimplePicture) { + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkMatrix layer_offset_matrix = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), + picture_bounds.makeOffset(layer_offset.fX, layer_offset.fY)); + EXPECT_EQ(layer->picture(), mock_picture.get()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, + MockCanvas::ConcatMatrixData{layer_offset_matrix}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{RasterCache::GetIntegralTransCTM( + layer_offset_matrix)}}, +#endif + MockCanvas::DrawCall{1, MockCanvas::DrawPictureData{nullptr, nullptr, + mock_picture.get()}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc new file mode 100644 index 0000000000000..63d9e23e48bd9 --- /dev/null +++ b/flow/layers/platform_view_layer_unittests.cc @@ -0,0 +1,38 @@ +// Copyright 2019 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/platform_view_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 PlatformViewLayerTest = LayerTest; + +TEST_F(PlatformViewLayerTest, NullViewEmbedderDoesntPrerollCompositeOrPaint) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(8.0f, 8.0f); + const int64_t view_id = 0; + auto layer = + std::make_shared(layer_offset, layer_size, view_id); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(layer->paint_bounds(), + SkRect::MakeSize(layer_size) + .makeOffset(layer_offset.fX, layer_offset.fY)); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + layer->Paint(paint_context()); + EXPECT_EQ(paint_context().leaf_nodes_canvas, &mock_canvas()); + mock_canvas().ExpectDrawCalls({}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc new file mode 100644 index 0000000000000..33358314fddb5 --- /dev/null +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -0,0 +1,247 @@ +// Copyright 2019 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/shader_mask_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" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/include/effects/SkPerlinNoiseShader.h" + +namespace flutter { +namespace testing { + +static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); + +using ShaderMaskLayerDeathTest = LayerTest; +using ShaderMaskLayerTest = LayerTest; + +TEST_F(ShaderMaskLayerDeathTest, EmptyLayer) { + auto layer = + std::make_shared(nullptr, kEmptyRect, SkBlendMode::kSrc); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ShaderMaskLayerDeathTest, PaintBeforePreroll) { + 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(child_path); + auto layer = + std::make_shared(nullptr, kEmptyRect, SkBlendMode::kSrc); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ShaderMaskLayerTest, EmptyFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr, layer_bounds, + SkBlendMode::kSrc); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix(initial_transform); + mock_layer->ExpectMutators({}); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(nullptr); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), nullptr, 1}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{SkRect::MakeWH(layer_bounds.width(), + layer_bounds.height()), + filter_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ShaderMaskLayerTest, SimpleFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter, layer_bounds, + SkBlendMode::kSrc); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix(initial_transform); + mock_layer->ExpectMutators({}); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(layer_filter); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), nullptr, 1}}, + MockCanvas::DrawCall{1, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{SkRect::MakeWH(layer_bounds.width(), + layer_bounds.height()), + filter_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ShaderMaskLayerTest, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter, layer_bounds, + SkBlendMode::kSrc); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + 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_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectMutators({}); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectMutators({}); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(layer_filter); + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, + MockCanvas::SaveLayerData{children_bounds, SkPaint(), nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{SkRect::MakeWH(layer_bounds.width(), + layer_bounds.height()), + filter_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(ShaderMaskLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 7.5f, 8.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto layer_filter2 = + SkPerlinNoiseShader::MakeImprovedNoise(2.0f, 2.0f, 2, 2.0f); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1, layer_bounds, + SkBlendMode::kSrc); + auto layer2 = std::make_shared(layer_filter2, layer_bounds, + SkBlendMode::kSrc); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer1->Preroll(preroll_context(), initial_transform); + 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(), mock_layer2->paint_bounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + mock_layer1->ExpectParentMatrix(initial_transform); + mock_layer1->ExpectMutators({}); + mock_layer2->ExpectParentMatrix(initial_transform); + mock_layer2->ExpectMutators({}); + + SkPaint filter_paint1, filter_paint2; + filter_paint1.setBlendMode(SkBlendMode::kSrc); + filter_paint2.setBlendMode(SkBlendMode::kSrc); + filter_paint1.setShader(layer_filter1); + filter_paint2.setShader(layer_filter2); + layer1->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{ + 0, + MockCanvas::SaveLayerData{children_bounds, SkPaint(), nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_path2.getBounds(), SkPaint(), + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawRectData{SkRect::MakeWH(layer_bounds.width(), + layer_bounds.height()), + filter_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{SkRect::MakeWH(layer_bounds.width(), + layer_bounds.height()), + filter_paint1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/texture_layer_unittests.cc b/flow/layers/texture_layer_unittests.cc new file mode 100644 index 0000000000000..e41b5b72621c5 --- /dev/null +++ b/flow/layers/texture_layer_unittests.cc @@ -0,0 +1,34 @@ +// Copyright 2019 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/texture_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 TextureLayerTest = LayerTest; + +TEST_F(TextureLayerTest, InvalidTexture) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(8.0f, 8.0f); + auto layer = + std::make_shared(layer_offset, layer_size, 0, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), + SkRect::MakeSize(layer_size) + .makeOffset(layer_offset.fX, layer_offset.fY)); + EXPECT_TRUE(layer->needs_painting()); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls({}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/transform_layer_unittests.cc b/flow/layers/transform_layer_unittests.cc new file mode 100644 index 0000000000000..b466f55f9560a --- /dev/null +++ b/flow/layers/transform_layer_unittests.cc @@ -0,0 +1,204 @@ +// Copyright 2019 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/transform_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 TransformLayerDeathTest = LayerTest; +using TransformLayerTest = LayerTest; + +TEST_F(TransformLayerDeathTest, EmptyLayer) { + auto layer = std::make_shared(SkMatrix()); // identity + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(TransformLayerDeathTest, PaintBeforePreroll) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = std::make_shared(SkMatrix()); // identity + layer->Add(mock_layer); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(TransformLayerTest, Identity) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = std::make_shared(SkMatrix()); // identity + layer->Add(mock_layer); + + preroll_context()->cull_rect = cull_rect; + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix(SkMatrix()); // identity + mock_layer->ExpectParentCullRect(cull_rect); + mock_layer->ExpectMutators({Mutator(SkMatrix())}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path, SkPaint()}}}); +} + +TEST_F(TransformLayerTest, Simple) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + SkMatrix layer_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix inverse_layer_transform; + EXPECT_TRUE(layer_transform.invert(&inverse_layer_transform)); + + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = std::make_shared(layer_transform); + layer->Add(mock_layer); + + preroll_context()->cull_rect = cull_rect; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), + layer_transform.mapRect(mock_layer->paint_bounds())); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + mock_layer->ExpectParentMatrix( + SkMatrix::Concat(initial_transform, layer_transform)); + mock_layer->ExpectParentCullRect(inverse_layer_transform.mapRect(cull_rect)); + mock_layer->ExpectMutators({Mutator(layer_transform)}); + + layer->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer_transform}}, + MockCanvas::DrawCall{1, MockCanvas::DrawPathData{child_path, SkPaint()}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(TransformLayerTest, Nested) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + SkMatrix layer1_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix layer2_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix inverse_layer1_transform, inverse_layer2_transform; + EXPECT_TRUE(layer1_transform.invert(&inverse_layer1_transform)); + EXPECT_TRUE(layer2_transform.invert(&inverse_layer2_transform)); + + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer1 = std::make_shared(layer1_transform); + auto layer2 = std::make_shared(layer2_transform); + layer1->Add(layer2); + layer2->Add(mock_layer); + + preroll_context()->cull_rect = cull_rect; + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer2->paint_bounds(), + layer2_transform.mapRect(mock_layer->paint_bounds())); + EXPECT_EQ(layer1->paint_bounds(), + layer1_transform.mapRect(layer2->paint_bounds())); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + mock_layer->ExpectParentMatrix(SkMatrix::Concat( + SkMatrix::Concat(initial_transform, layer1_transform), layer2_transform)); + mock_layer->ExpectParentCullRect(inverse_layer2_transform.mapRect( + inverse_layer1_transform.mapRect(cull_rect))); + mock_layer->ExpectMutators( + {Mutator(layer2_transform), Mutator(layer1_transform)}); + + layer1->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer1_transform}}, + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{2, MockCanvas::ConcatMatrixData{layer2_transform}}, + MockCanvas::DrawCall{2, MockCanvas::DrawPathData{child_path, SkPaint()}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +TEST_F(TransformLayerTest, NestedSeparated) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + SkMatrix layer1_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix layer2_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix inverse_layer1_transform, inverse_layer2_transform; + EXPECT_TRUE(layer1_transform.invert(&inverse_layer1_transform)); + EXPECT_TRUE(layer2_transform.invert(&inverse_layer2_transform)); + + auto mock_layer1 = + std::make_shared(child_path, SkPaint(SkColors::kBlue)); + auto mock_layer2 = + std::make_shared(child_path, SkPaint(SkColors::kGreen)); + auto layer1 = std::make_shared(layer1_transform); + auto layer2 = std::make_shared(layer2_transform); + layer1->Add(mock_layer1); + layer2->Add(mock_layer2); + mock_layer1->SetChild(layer2); + + SkRect expected_mock1_bounds = child_path.getBounds(); + preroll_context()->cull_rect = cull_rect; + layer1->Preroll(preroll_context(), initial_transform); + expected_mock1_bounds.join(layer2->paint_bounds()); + + EXPECT_EQ(mock_layer2->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer2->paint_bounds(), + layer2_transform.mapRect(mock_layer2->paint_bounds())); + EXPECT_EQ(mock_layer1->paint_bounds(), expected_mock1_bounds); + EXPECT_EQ(layer1->paint_bounds(), + layer1_transform.mapRect(mock_layer1->paint_bounds())); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + mock_layer1->ExpectParentMatrix( + SkMatrix::Concat(initial_transform, layer1_transform)); + mock_layer2->ExpectParentMatrix(SkMatrix::Concat( + SkMatrix::Concat(initial_transform, layer1_transform), layer2_transform)); + mock_layer1->ExpectParentCullRect( + inverse_layer1_transform.mapRect(cull_rect)); + mock_layer2->ExpectParentCullRect(inverse_layer2_transform.mapRect( + inverse_layer1_transform.mapRect(cull_rect))); + mock_layer1->ExpectMutators({Mutator(layer1_transform)}); + mock_layer2->ExpectMutators( + {Mutator(layer2_transform), Mutator(layer1_transform)}); + + layer1->Paint(paint_context()); + mock_canvas().ExpectDrawCalls( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer1_transform}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, SkPaint(SkColors::kBlue)}}, + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{2, MockCanvas::ConcatMatrixData{layer2_transform}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path, SkPaint(SkColors::kGreen)}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/matrix_decomposition_unittests.cc b/flow/matrix_decomposition_unittests.cc index cf96025276737..4cfcf854d9b5f 100644 --- a/flow/matrix_decomposition_unittests.cc +++ b/flow/matrix_decomposition_unittests.cc @@ -2,16 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/fml/build_config.h" +#include "flutter/flow/matrix_decomposition.h" +#include "flutter/fml/build_config.h" #if defined(OS_WIN) #define _USE_MATH_DEFINES #endif #include -#include "flutter/flow/matrix_decomposition.h" #include "gtest/gtest.h" +namespace flutter { +namespace testing { + TEST(MatrixDecomposition, Rotation) { SkMatrix44 matrix = SkMatrix44::I(); @@ -93,7 +96,8 @@ TEST(MatrixDecomposition, Combination) { } TEST(MatrixDecomposition, ScaleFloatError) { - for (float scale = 0.0001f; scale < 2.0f; scale += 0.000001f) { + constexpr float scale_increment = 0.00001f; + for (float scale = 0.0001f; scale < 2.0f; scale += scale_increment) { SkMatrix44 matrix = SkMatrix44::I(); matrix.setScale(scale, scale, 1.0f); @@ -152,3 +156,6 @@ TEST(MatrixDecomposition, ScaleFloatError) { ASSERT_FLOAT_EQ(0, decomposition3.rotation().fData[1]); ASSERT_FLOAT_EQ(0, decomposition3.rotation().fData[2]); } + +} // namespace testing +} // namespace flutter diff --git a/flow/mutators_stack_unittests.cc b/flow/mutators_stack_unittests.cc index 97cfe9a54a7c7..1d31a81623307 100644 --- a/flow/mutators_stack_unittests.cc +++ b/flow/mutators_stack_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/flow/embedded_views.h" + #include "gtest/gtest.h" namespace flutter { diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index 64f4405ebe5a0..bd83d807875f2 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -3,10 +3,15 @@ // found in the LICENSE file. #include "flutter/flow/raster_cache.h" + #include "gtest/gtest.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkPictureRecorder.h" +namespace flutter { +namespace testing { +namespace { + sk_sp GetSamplePicture() { SkPictureRecorder recorder; recorder.beginRecording(SkRect::MakeWH(150, 100)); @@ -17,6 +22,8 @@ sk_sp GetSamplePicture() { return recorder.finishRecordingAsPicture(); } +} // namespace + TEST(RasterCache, SimpleInitialization) { flutter::RasterCache cache; ASSERT_TRUE(true); @@ -93,3 +100,6 @@ TEST(RasterCache, SweepsRemoveUnusedFrames) { ASSERT_FALSE(cache.Prepare(NULL, picture.get(), matrix, srgb.get(), true, false)); // 5 } + +} // namespace testing +} // namespace flutter diff --git a/flow/skia_gpu_object_unittests.cc b/flow/skia_gpu_object_unittests.cc index aa259a6909eec..a5efc38389067 100644 --- a/flow/skia_gpu_object_unittests.cc +++ b/flow/skia_gpu_object_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/flow/skia_gpu_object.h" + #include "flutter/fml/message_loop.h" #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/fml/task_runner.h" @@ -13,8 +14,6 @@ namespace flutter { namespace testing { -using SkiaGpuObjectTest = flutter::testing::ThreadTest; - class TestSkObject : public SkRefCnt { public: TestSkObject(std::shared_ptr latch, @@ -22,7 +21,9 @@ class TestSkObject : public SkRefCnt { : latch_(latch), dtor_task_queue_id_(dtor_task_queue_id) {} ~TestSkObject() { - *dtor_task_queue_id_ = fml::MessageLoop::GetCurrentTaskQueueId(); + if (dtor_task_queue_id_) { + *dtor_task_queue_id_ = fml::MessageLoop::GetCurrentTaskQueueId(); + } latch_->Signal(); } @@ -31,19 +32,133 @@ class TestSkObject : public SkRefCnt { fml::TaskQueueId* dtor_task_queue_id_; }; -TEST_F(SkiaGpuObjectTest, UnrefQueue) { - fml::RefPtr task_runner = CreateNewThread(); - fml::RefPtr queue = fml::MakeRefCounted( - task_runner, fml::TimeDelta::FromSeconds(0)); +// Death tests and threads do not get along. This fixture gives access to the +// current thread context but doesn't start any other threads. +using SkiaGpuObjectDeathTest = ThreadTest; + +// This fixture starts a 2nd thread by default. The |SkiaUnrefQueue| uses the +// 2nd thread to perform unref() operations on its data items. +class SkiaGpuObjectTest : public SkiaGpuObjectDeathTest { + public: + SkiaGpuObjectTest() + : unref_task_runner_(CreateNewThread()), + unref_queue_(fml::MakeRefCounted( + unref_task_runner(), + fml::TimeDelta::FromSeconds(0))), + delayed_unref_queue_(fml::MakeRefCounted( + unref_task_runner(), + fml::TimeDelta::FromSeconds(3))) {} + ~SkiaGpuObjectTest() override = default; + + fml::RefPtr unref_task_runner() { + return unref_task_runner_; + } + fml::RefPtr unref_queue() { return unref_queue_; } + fml::RefPtr delayed_unref_queue() { + return delayed_unref_queue_; + } + + private: + fml::RefPtr unref_task_runner_; + fml::RefPtr unref_queue_; + fml::RefPtr delayed_unref_queue_; +}; + +TEST_F(SkiaGpuObjectDeathTest, CreateObjectWithNoQueueDies) { + SkiaGPUObject object; + + EXPECT_DEATH_IF_SUPPORTED( + object = SkiaGPUObject( + sk_make_sp( + std::make_shared(), nullptr), + nullptr), + "queue_ && object_"); +} + +TEST_F(SkiaGpuObjectDeathTest, CreateObjectWithNoSkSpDies) { + SkiaGPUObject object; + auto unref_queue = fml::MakeRefCounted( + GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); + + EXPECT_DEATH_IF_SUPPORTED( + object = SkiaGPUObject(nullptr, unref_queue), + "queue_ && object_"); +} +TEST_F(SkiaGpuObjectTest, QueueSimple) { std::shared_ptr latch = std::make_shared(); fml::TaskQueueId dtor_task_queue_id(0); SkRefCnt* ref_object = new TestSkObject(latch, &dtor_task_queue_id); - queue->Unref(ref_object); + unref_queue()->Unref(ref_object); + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectDestructor) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + + { + auto object = sk_make_sp(latch, &dtor_task_queue_id); + SkiaGPUObject sk_object(object, unref_queue()); + ASSERT_EQ(sk_object.get(), object); + ASSERT_EQ(dtor_task_queue_id, 0); + } + + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectReset) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + SkiaGPUObject sk_object( + sk_make_sp(latch, &dtor_task_queue_id), unref_queue()); + + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectResetBeforeDestructor) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + + { + auto object = sk_make_sp(latch, &dtor_task_queue_id); + SkiaGPUObject sk_object(object, unref_queue()); + ASSERT_EQ(sk_object.get(), object); + ASSERT_EQ(dtor_task_queue_id, 0); + + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + } + + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectResetTwice) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + SkiaGPUObject sk_object( + sk_make_sp(latch, &dtor_task_queue_id), unref_queue()); + + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + latch->Wait(); - ASSERT_EQ(dtor_task_queue_id, task_runner->GetTaskQueueId()); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); } } // namespace testing diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h new file mode 100644 index 0000000000000..dd4f59abe7200 --- /dev/null +++ b/flow/testing/layer_test.h @@ -0,0 +1,88 @@ +// Copyright 2019 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. + +#ifndef FLOW_TESTING_LAYER_TEST_H_ +#define FLOW_TESTING_LAYER_TEST_H_ + +#include +#include + +#include "flutter/flow/layers/layer.h" +#include "flutter/flow/skia_gpu_object.h" +#include "flutter/testing/mock_canvas.h" +#include "flutter/testing/thread_test.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/utils/SkNWayCanvas.h" + +namespace flutter { +namespace testing { + +template +class LayerTestBase : public BaseT { + public: + LayerTestBase(); + virtual ~LayerTestBase() = default; + + TextureRegistry& texture_regitry() { return texture_registry_; } + MockCanvas& mock_canvas() { return canvas_; } + PrerollContext* preroll_context() { return &preroll_context_; } + Layer::PaintContext& paint_context() { return paint_context_; } + + private: + Stopwatch stopwatch_; + MutatorsStack mutators_stack_; + TextureRegistry texture_registry_; + MockCanvas canvas_; + + PrerollContext preroll_context_; + Layer::PaintContext paint_context_; +}; + +template +LayerTestBase::LayerTestBase() + : preroll_context_({ + nullptr, // raster_cache (don't care) + nullptr, // gr_context (don't care) + nullptr, // external view embedder + mutators_stack_, // mutator stack + canvas_.imageInfo().colorSpace(), // dst_color_space + kGiantRect, // SkRect cull_rect + stopwatch_, // frame time (dont care) + stopwatch_, // engine time (dont care) + texture_registry_, // texture registry (dont care) + false, // checkerboard_offscreen_layers + 0.0f, // total elevation + }), + paint_context_({ + canvas_.internal_canvas(), // internal_nodes_canvas + &canvas_, // leaf_nodes_canvas + nullptr, // gr_context (don't care) + nullptr, // view_embedder (don't care) + stopwatch_, // raster_time (don't care) + stopwatch_, // ui_time (don't care) + texture_registry_, // texture_registry (don't care) + nullptr, // raster_cache (don't care) + false, // checkerboard_offscreen_layers + }) {} + +using LayerTest = LayerTestBase<::testing::Test>; +class SkiaGPUObjectLayerTest : public LayerTestBase { + public: + SkiaGPUObjectLayerTest() + : unref_queue_(fml::MakeRefCounted( + GetCurrentTaskRunner(), + fml::TimeDelta::FromSeconds(0))) {} + ~SkiaGPUObjectLayerTest() override = default; + + fml::RefPtr unref_queue() { return unref_queue_; } + + private: + fml::RefPtr unref_queue_; +}; + +} // namespace testing +} // namespace flutter + +#endif // FLOW_TESTING_LAYER_TEST_H_ diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc new file mode 100644 index 0000000000000..c526aab62e7d6 --- /dev/null +++ b/flow/testing/mock_layer.cc @@ -0,0 +1,81 @@ +// Copyright 2019 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/testing/mock_layer.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +MockLayer::MockLayer(SkPath path, + SkPaint paint, + bool fake_has_platform_view, + bool fake_needs_system_composite) + : fake_paint_path_(path), + fake_paint_(paint), + fake_has_platform_view_(fake_has_platform_view), + fake_needs_system_composite_(fake_needs_system_composite) {} + +void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { + parent_mutators_ = context->mutators_stack; + parent_matrix_ = matrix; + parent_cull_rect_ = context->cull_rect; + parent_elevation_ = context->total_elevation; + parent_has_platform_view_ = context->has_platform_view; + + SkRect total_paint_bounds = fake_paint_path_.getBounds(); + bool child_has_platform_view = fake_has_platform_view_; + bool child_needs_system_composite = fake_needs_system_composite_; + if (child_layer_) { + child_layer_->Preroll(context, matrix); + child_has_platform_view = + child_has_platform_view || context->has_platform_view; + child_needs_system_composite = + child_needs_system_composite || child_layer_->needs_system_composite(); + total_paint_bounds.join(child_layer_->paint_bounds()); + } + context->has_platform_view = child_has_platform_view; + set_paint_bounds(total_paint_bounds); + set_needs_system_composite(child_needs_system_composite); +} + +void MockLayer::Paint(PaintContext& context) const { + FML_DCHECK(needs_painting()); + + context.leaf_nodes_canvas->drawPath(fake_paint_path_, fake_paint_); + if (child_layer_) { + child_layer_->Paint(context); + } +} + +void MockLayer::ExpectMutators(const std::vector& stack) { + uint elements_count = 0; + for (auto mutator_iter = parent_mutators_.Bottom(); + mutator_iter != parent_mutators_.Top(); mutator_iter++) { + EXPECT_LT(elements_count, stack.size()); + EXPECT_NE(*mutator_iter, nullptr); + EXPECT_EQ(**mutator_iter, stack[elements_count]); + elements_count++; + } + EXPECT_EQ(elements_count, stack.size()); +} + +void MockLayer::ExpectParentMatrix(const SkMatrix& matrix) { + EXPECT_EQ(matrix, parent_matrix_); +} + +void MockLayer::ExpectParentCullRect(const SkRect& rect) { + EXPECT_EQ(rect, parent_cull_rect_); +} + +void MockLayer::ExpectParentElevation(float elevation) { + EXPECT_EQ(elevation, parent_elevation_); +} + +void MockLayer::ExpectParentHasPlatformView(bool has_platform_view) { + EXPECT_EQ(has_platform_view, parent_has_platform_view_); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h new file mode 100644 index 0000000000000..ffb23330d77d0 --- /dev/null +++ b/flow/testing/mock_layer.h @@ -0,0 +1,52 @@ +// Copyright 2019 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. + +#ifndef FLOW_TESTING_MOCK_LAYER_H_ +#define FLOW_TESTING_MOCK_LAYER_H_ + +#include "flutter/flow/layers/layer.h" + +namespace flutter { +namespace testing { + +class MockLayer : public Layer { + public: + MockLayer(SkPath path, + SkPaint paint = SkPaint(), + bool fake_has_platform_view = false, + bool fake_needs_system_composite = false); + ~MockLayer() override = default; + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Paint(PaintContext& context) const override; + + void SetChild(std::shared_ptr child) { child_layer_ = child; } + + void ExpectMutators(const std::vector& stack); + void ExpectParentMatrix(const SkMatrix& matrix); + void ExpectParentCullRect(const SkRect& rect); + void ExpectParentElevation(float elevation); + void ExpectParentHasPlatformView(bool has_platform_view); + + private: + std::shared_ptr child_layer_; + + MutatorsStack parent_mutators_; + SkMatrix parent_matrix_; + SkRect parent_cull_rect_; + float parent_elevation_ = 0; + bool parent_has_platform_view_ = false; + + SkPath fake_paint_path_; + SkPaint fake_paint_; + bool fake_has_platform_view_; + bool fake_needs_system_composite_; + + FML_DISALLOW_COPY_AND_ASSIGN(MockLayer); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLOW_TESTING_MOCK_LAYER_H_ diff --git a/flow/texture_unittests.cc b/flow/texture_unittests.cc index d292e3965af87..d9f676446c1f9 100644 --- a/flow/texture_unittests.cc +++ b/flow/texture_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/flow/texture.h" + #include "gtest/gtest.h" namespace flutter { @@ -11,7 +12,6 @@ namespace testing { class MockTexture : public Texture { public: MockTexture(int64_t textureId) : Texture(textureId) {} - ~MockTexture() override = default; // Called from GPU thread. @@ -20,26 +20,111 @@ class MockTexture : public Texture { bool freeze, GrContext* context) override {} - void OnGrContextCreated() override {} - - void OnGrContextDestroyed() override {} - + void OnGrContextCreated() override { gr_context_created_ = true; } + void OnGrContextDestroyed() override { gr_context_destroyed_ = true; } void MarkNewFrameAvailable() override {} - void OnTextureUnregistered() override { unregistered_ = true; } + bool gr_context_created() { return gr_context_created_; } + bool gr_context_destroyed() { return gr_context_destroyed_; } bool unregistered() { return unregistered_; } private: + bool gr_context_created_ = false; + bool gr_context_destroyed_ = false; bool unregistered_ = false; }; -TEST(TextureRegistry, UnregisterTextureCallbackTriggered) { - TextureRegistry textureRegistry; - std::shared_ptr mockTexture = std::make_shared(0); - textureRegistry.RegisterTexture(mockTexture); - textureRegistry.UnregisterTexture(0); - ASSERT_TRUE(mockTexture->unregistered()); +class TextureRegistryTest : public ::testing::Test { + public: + TextureRegistryTest() = default; + ~TextureRegistryTest() override = default; + + TextureRegistry& registry() { return registry_; } + + private: + TextureRegistry registry_; +}; + +TEST_F(TextureRegistryTest, UnregisterTextureCallbackTriggered) { + std::shared_ptr mock_texture1 = std::make_shared(0); + std::shared_ptr mock_texture2 = std::make_shared(1); + + registry().RegisterTexture(mock_texture1); + registry().RegisterTexture(mock_texture2); + ASSERT_EQ(registry().GetTexture(0), mock_texture1); + ASSERT_EQ(registry().GetTexture(1), mock_texture2); + ASSERT_FALSE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); + + registry().UnregisterTexture(0); + ASSERT_EQ(registry().GetTexture(0), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); + + registry().UnregisterTexture(1); + ASSERT_EQ(registry().GetTexture(1), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_TRUE(mock_texture2->unregistered()); +} + +TEST_F(TextureRegistryTest, GrContextCallbackTriggered) { + std::shared_ptr mock_texture1 = std::make_shared(0); + std::shared_ptr mock_texture2 = std::make_shared(1); + + registry().RegisterTexture(mock_texture1); + registry().RegisterTexture(mock_texture2); + ASSERT_FALSE(mock_texture1->gr_context_created()); + ASSERT_FALSE(mock_texture2->gr_context_created()); + ASSERT_FALSE(mock_texture1->gr_context_destroyed()); + ASSERT_FALSE(mock_texture2->gr_context_destroyed()); + + registry().OnGrContextCreated(); + ASSERT_TRUE(mock_texture1->gr_context_created()); + ASSERT_TRUE(mock_texture2->gr_context_created()); + + registry().UnregisterTexture(0); + registry().OnGrContextDestroyed(); + ASSERT_FALSE(mock_texture1->gr_context_destroyed()); + ASSERT_TRUE(mock_texture2->gr_context_created()); +} + +TEST_F(TextureRegistryTest, RegisterTextureTwice) { + std::shared_ptr mock_texture1 = std::make_shared(0); + std::shared_ptr mock_texture2 = std::make_shared(0); + + registry().RegisterTexture(mock_texture1); + ASSERT_EQ(registry().GetTexture(0), mock_texture1); + registry().RegisterTexture(mock_texture2); + ASSERT_EQ(registry().GetTexture(0), mock_texture2); + ASSERT_FALSE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); + + registry().UnregisterTexture(0); + ASSERT_EQ(registry().GetTexture(0), nullptr); + ASSERT_FALSE(mock_texture1->unregistered()); + ASSERT_TRUE(mock_texture2->unregistered()); +} + +TEST_F(TextureRegistryTest, ReuseSameTextureSlot) { + std::shared_ptr mock_texture1 = std::make_shared(0); + std::shared_ptr mock_texture2 = std::make_shared(0); + + registry().RegisterTexture(mock_texture1); + ASSERT_EQ(registry().GetTexture(0), mock_texture1); + + registry().UnregisterTexture(0); + ASSERT_EQ(registry().GetTexture(0), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); + + registry().RegisterTexture(mock_texture2); + ASSERT_EQ(registry().GetTexture(0), mock_texture2); + + registry().UnregisterTexture(0); + ASSERT_EQ(registry().GetTexture(0), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_TRUE(mock_texture2->unregistered()); } } // namespace testing diff --git a/fml/BUILD.gn b/fml/BUILD.gn index 75ff7aeae8c08..ee920d62337ad 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -2,12 +2,11 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/fuchsia/sdk.gni") -import("$flutter_root/testing/testing.gni") - if (is_fuchsia) { + import("//build/fuchsia/sdk.gni") import("$flutter_root/tools/fuchsia/fuchsia_archive.gni") } +import("$flutter_root/testing/testing.gni") source_set("fml") { sources = [ diff --git a/testing/BUILD.gn b/testing/BUILD.gn index a8db2229e29f5..9b2b43965aee4 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -52,6 +52,8 @@ source_set("skia") { sources = [ "$flutter_root/testing/assertions_skia.h", + "$flutter_root/testing/mock_canvas.cc", + "$flutter_root/testing/mock_canvas.h", ] public_deps = [ diff --git a/testing/assertions_skia.h b/testing/assertions_skia.h index 2b501189a23ae..4bb7c92d76b2c 100644 --- a/testing/assertions_skia.h +++ b/testing/assertions_skia.h @@ -7,8 +7,11 @@ #include +#include "third_party/skia/include/core/SkClipOp.h" #include "third_party/skia/include/core/SkMatrix.h" #include "third_party/skia/include/core/SkMatrix44.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPath.h" #include "third_party/skia/include/core/SkPoint3.h" #include "third_party/skia/include/core/SkRRect.h" @@ -16,6 +19,45 @@ // Printing //------------------------------------------------------------------------------ +inline std::ostream& operator<<(std::ostream& os, const SkClipOp& o) { + switch (o) { + case SkClipOp::kDifference: + os << "ClipOpDifference"; + break; + case SkClipOp::kIntersect: + os << "ClipOpIntersect"; + break; +#ifdef SK_SUPPORT_DEPRECATED_CLIPOPS + case SkClipOp::kUnion_deprecated: + os << "ClipOpUnion_deprecated"; + break; + case SkClipOp::kXOR_deprecated: + os << "ClipOpXOR_deprecated"; + break; + case SkClipOp::kReverseDifference_deprecated: + os << "ClipOpReverseDifference_deprecated"; + break; + case SkClipOp::kReplace_deprecated: + os << "ClipOpReplace_deprectaed"; + break; +#else + case SkClipOp::kExtraEnumNeedInternallyPleaseIgnoreWillGoAway2: + os << "ClipOpReserved2"; + break; + case SkClipOp::kExtraEnumNeedInternallyPleaseIgnoreWillGoAway3: + os << "ClipOpReserved3"; + break; + case SkClipOp::kExtraEnumNeedInternallyPleaseIgnoreWillGoAway4: + os << "ClipOpReserved4"; + break; + case SkClipOp::kExtraEnumNeedInternallyPleaseIgnoreWillGoAway5: + os << "ClipOpReserved5"; + break; +#endif + } + return os; +} + inline std::ostream& operator<<(std::ostream& os, const SkMatrix& m) { os << std::endl; os << "Scale X: " << m[SkMatrix::kMScaleX] << ", "; @@ -44,36 +86,44 @@ inline std::ostream& operator<<(std::ostream& os, const SkMatrix44& m) { } inline std::ostream& operator<<(std::ostream& os, const SkVector3& v) { - os << v.x() << ", " << v.y() << ", " << v.z(); - return os; + return os << v.x() << ", " << v.y() << ", " << v.z(); } inline std::ostream& operator<<(std::ostream& os, const SkVector4& v) { - os << v.fData[0] << ", " << v.fData[1] << ", " << v.fData[2] << ", " - << v.fData[3]; - return os; + return os << v.fData[0] << ", " << v.fData[1] << ", " << v.fData[2] << ", " + << v.fData[3]; } inline std::ostream& operator<<(std::ostream& os, const SkRect& r) { - os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", " - << r.fBottom; - return os; + return os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", " + << r.fBottom; } inline std::ostream& operator<<(std::ostream& os, const SkRRect& r) { - os << "LTRB: " << r.rect().fLeft << ", " << r.rect().fTop << ", " - << r.rect().fRight << ", " << r.rect().fBottom; - return os; + return os << "LTRB: " << r.rect().fLeft << ", " << r.rect().fTop << ", " + << r.rect().fRight << ", " << r.rect().fBottom; +} + +inline std::ostream& operator<<(std::ostream& os, const SkPath& r) { + return os << "Valid: " << r.isValid() << ", FillType: " << r.getFillType() + << ", Bounds: " << r.getBounds(); } inline std::ostream& operator<<(std::ostream& os, const SkPoint& r) { - os << "XY: " << r.fX << ", " << r.fY; - return os; + return os << "XY: " << r.fX << ", " << r.fY; } inline std::ostream& operator<<(std::ostream& os, const SkISize& size) { - os << size.width() << ", " << size.height(); - return os; + return os << size.width() << ", " << size.height(); +} + +inline std::ostream& operator<<(std::ostream& os, const SkColor4f& r) { + return os << r.fR << ", " << r.fG << ", " << r.fB << ", " << r.fA; +} + +inline std::ostream& operator<<(std::ostream& os, const SkPaint& r) { + return os << "Color: " << r.getColor4f() << ", Style: " << r.getStyle() + << ", AA: " << r.isAntiAlias() << ", Shader: " << r.getShader(); } #endif // FLUTTER_TESTING_ASSERTIONS_SKIA_H_ diff --git a/testing/mock_canvas.cc b/testing/mock_canvas.cc new file mode 100644 index 0000000000000..c810ba3bb36c3 --- /dev/null +++ b/testing/mock_canvas.cc @@ -0,0 +1,283 @@ +// Copyright 2019 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/testing/mock_canvas.h" + +#include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkImageInfo.h" + +namespace flutter { +namespace testing { + +MockCanvas::MockCanvas() + : SkCanvasVirtualEnforcer(64, 64), + internal_canvas_(imageInfo().width(), imageInfo().height()), + current_layer_(0) { + internal_canvas_.addCanvas(this); +} + +void MockCanvas::ExpectDrawCalls(std::vector expected_calls) { + EXPECT_EQ(expected_calls, draw_calls_); + FML_DCHECK(current_layer_ == 0); +} + +void MockCanvas::willSave() { + draw_calls_.emplace_back( + DrawCall{current_layer_, SaveData{current_layer_ + 1}}); + current_layer_++; // Must go here; func params order of eval is undefined +} + +SkCanvas::SaveLayerStrategy MockCanvas::getSaveLayerStrategy( + const SaveLayerRec& rec) { + // saveLayer calls this prior to running, so we use it to track saveLayer + // calls + draw_calls_.emplace_back(DrawCall{ + current_layer_, + SaveLayerData{rec.fBounds ? *rec.fBounds : SkRect(), + rec.fPaint ? *rec.fPaint : SkPaint(), + rec.fBackdrop ? sk_ref_sp(rec.fBackdrop) + : sk_sp(), + current_layer_ + 1}}); + current_layer_++; // Must go here; func params order of eval is undefined + return kNoLayer_SaveLayerStrategy; +} + +void MockCanvas::willRestore() { + FML_DCHECK(current_layer_ > 0); + + draw_calls_.emplace_back( + DrawCall{current_layer_, RestoreData{current_layer_ - 1}}); + current_layer_--; // Must go here; func params order of eval is undefined +} + +void MockCanvas::didConcat(const SkMatrix& matrix) { + draw_calls_.emplace_back(DrawCall{current_layer_, ConcatMatrixData{matrix}}); +} + +void MockCanvas::didSetMatrix(const SkMatrix& matrix) { + draw_calls_.emplace_back(DrawCall{current_layer_, SetMatrixData{matrix}}); +} + +void MockCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) { + draw_calls_.emplace_back(DrawCall{current_layer_, DrawRectData{rect, paint}}); +} + +void MockCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { + draw_calls_.emplace_back(DrawCall{current_layer_, DrawPathData{path, paint}}); +} + +void MockCanvas::onDrawShadowRec(const SkPath& path, + const SkDrawShadowRec& rec) { + (void)rec; // Can't use b/c Skia keeps this type anonymous. + draw_calls_.emplace_back(DrawCall{current_layer_, DrawShadowData{path}}); +} + +void MockCanvas::onDrawPicture(const SkPicture* picture, + const SkMatrix* matrix, + const SkPaint* paint) { + if (!paint || paint->canComputeFastBounds()) { + SkRect bounds = picture->cullRect(); + if (paint) { + paint->computeFastBounds(bounds, &bounds); + } + if (matrix) { + matrix->mapRect(&bounds); + } + if (this->quickReject(bounds)) { + return; + } + } + + draw_calls_.emplace_back( + DrawCall{current_layer_, DrawPictureData{matrix, paint, picture}}); +} + +void MockCanvas::onClipRect(const SkRect& rect, + SkClipOp op, + ClipEdgeStyle style) { + draw_calls_.emplace_back( + DrawCall{current_layer_, ClipRectData{rect, op, style}}); +} + +void MockCanvas::onClipRRect(const SkRRect& rrect, + SkClipOp op, + ClipEdgeStyle style) { + draw_calls_.emplace_back( + DrawCall{current_layer_, ClipRRectData{rrect, op, style}}); +} + +void MockCanvas::onClipPath(const SkPath& path, + SkClipOp op, + ClipEdgeStyle style) { + draw_calls_.emplace_back( + DrawCall{current_layer_, ClipPathData{path, op, style}}); +} + +bool MockCanvas::onDoSaveBehind(const SkRect*) { + FML_DCHECK(false); + return false; +} + +void MockCanvas::onDrawAnnotation(const SkRect&, const char[], SkData*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawDrawable(SkDrawable*, const SkMatrix*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawTextBlob(const SkTextBlob*, + SkScalar, + SkScalar, + const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawPatch(const SkPoint[12], + const SkColor[4], + const SkPoint[4], + SkBlendMode, + const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawPaint(const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawBehind(const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawPoints(PointMode, + size_t, + const SkPoint[], + const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawRegion(const SkRegion&, const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawOval(const SkRect&, const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawArc(const SkRect&, + SkScalar, + SkScalar, + bool, + const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawRRect(const SkRRect&, const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawBitmap(const SkBitmap&, + SkScalar, + SkScalar, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawBitmapRect(const SkBitmap&, + const SkRect*, + const SkRect&, + const SkPaint*, + SrcRectConstraint) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawImage(const SkImage*, + SkScalar, + SkScalar, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawImageRect(const SkImage*, + const SkRect*, + const SkRect&, + const SkPaint*, + SrcRectConstraint) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawImageNine(const SkImage*, + const SkIRect&, + const SkRect&, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawBitmapNine(const SkBitmap&, + const SkIRect&, + const SkRect&, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawImageLattice(const SkImage*, + const Lattice&, + const SkRect&, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawBitmapLattice(const SkBitmap&, + const Lattice&, + const SkRect&, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawVerticesObject(const SkVertices*, + const SkVertices::Bone[], + int, + SkBlendMode, + const SkPaint&) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawAtlas(const SkImage*, + const SkRSXform[], + const SkRect[], + const SkColor[], + int, + SkBlendMode, + const SkRect*, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawEdgeAAQuad(const SkRect&, + const SkPoint[4], + QuadAAFlags, + const SkColor4f&, + SkBlendMode) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawEdgeAAImageSet(const ImageSetEntry[], + int, + const SkPoint[], + const SkMatrix[], + const SkPaint*, + SrcRectConstraint) { + FML_DCHECK(false); +} + +void MockCanvas::onClipRegion(const SkRegion&, SkClipOp) { + FML_DCHECK(false); +} + +} // namespace testing +} // namespace flutter diff --git a/testing/mock_canvas.h b/testing/mock_canvas.h new file mode 100644 index 0000000000000..ce3becb9aad24 --- /dev/null +++ b/testing/mock_canvas.h @@ -0,0 +1,392 @@ +// Copyright 2019 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. + +#ifndef TESTING_MOCK_CANVAS_H_ +#define TESTING_MOCK_CANVAS_H_ + +#include +#include + +#include "flutter/testing/assertions_skia.h" +#include "gtest/gtest.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkCanvasVirtualEnforcer.h" +#include "third_party/skia/include/core/SkClipOp.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/utils/SkNWayCanvas.h" + +namespace flutter { +namespace testing { + +class MockCanvas : public SkCanvasVirtualEnforcer { + public: + using SkCanvas::kHard_ClipEdgeStyle; + using SkCanvas::kSoft_ClipEdgeStyle; + + struct SaveData { + int save_to_layer; + }; + + struct SaveLayerData { + SkRect save_bounds; + SkPaint restore_paint; + sk_sp backdrop_filter; + int save_to_layer; + }; + + struct RestoreData { + int restore_to_layer; + }; + + struct ConcatMatrixData { + SkMatrix matrix; + }; + + struct SetMatrixData { + SkMatrix matrix; + }; + + struct DrawRectData { + SkRect rect; + SkPaint paint; + }; + + struct DrawPathData { + SkPath path; + SkPaint paint; + }; + + struct DrawPictureData { + const SkMatrix* matrix; + const SkPaint* paint; + const SkPicture* picture; + }; + + struct DrawShadowData { + SkPath path; + }; + + struct ClipRectData { + SkRect rect; + SkClipOp clip_op; + ClipEdgeStyle style; + }; + + struct ClipRRectData { + SkRRect rrect; + SkClipOp clip_op; + ClipEdgeStyle style; + }; + + struct ClipPathData { + SkPath path; + SkClipOp clip_op; + ClipEdgeStyle style; + }; + + using DrawCallData = std::variant; + + // A single call made against this canvas. + struct DrawCall { + int layer; + DrawCallData data; + }; + + MockCanvas(); + virtual ~MockCanvas() = default; + + SkNWayCanvas* internal_canvas() { return &internal_canvas_; } + + void ExpectDrawCalls(std::vector expected_calls); + const std::vector& draw_calls() const { return draw_calls_; } + + protected: + // Save/restore/set operations that we track. + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override; + void willRestore() override; + void didRestore() override {} + void didConcat(const SkMatrix& matrix) override; + void didSetMatrix(const SkMatrix& matrix) override; + + // Draw and clip operations that we track. + void onDrawRect(const SkRect& rect, const SkPaint& paint) override; + void onDrawPath(const SkPath& path, const SkPaint& paint) override; + void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override; + void onDrawPicture(const SkPicture* picture, + const SkMatrix* matrix, + const SkPaint* paint) override; + void onClipRect(const SkRect& rect, + SkClipOp op, + ClipEdgeStyle style) override; + void onClipRRect(const SkRRect& rrect, + SkClipOp op, + ClipEdgeStyle style) override; + void onClipPath(const SkPath& path, + SkClipOp op, + ClipEdgeStyle style) override; + + // Operations that we don't track. + bool onDoSaveBehind(const SkRect*) override; + void onDrawAnnotation(const SkRect&, const char[], SkData*) override; + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + void onDrawTextBlob(const SkTextBlob*, + SkScalar, + SkScalar, + const SkPaint&) override; + void onDrawPatch(const SkPoint[12], + const SkColor[4], + const SkPoint[4], + SkBlendMode, + const SkPaint&) override; + void onDrawPaint(const SkPaint&) override; + void onDrawBehind(const SkPaint&) override; + void onDrawPoints(PointMode, + size_t, + const SkPoint[], + const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, + SkScalar, + SkScalar, + bool, + const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawBitmap(const SkBitmap&, + SkScalar, + SkScalar, + const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, + const SkRect*, + const SkRect&, + const SkPaint*, + SrcRectConstraint) override; + void onDrawImage(const SkImage*, SkScalar, SkScalar, const SkPaint*) override; + void onDrawImageRect(const SkImage*, + const SkRect*, + const SkRect&, + const SkPaint*, + SrcRectConstraint) override; + void onDrawImageNine(const SkImage*, + const SkIRect&, + const SkRect&, + const SkPaint*) override; + void onDrawBitmapNine(const SkBitmap&, + const SkIRect&, + const SkRect&, + const SkPaint*) override; + void onDrawImageLattice(const SkImage*, + const Lattice&, + const SkRect&, + const SkPaint*) override; + void onDrawBitmapLattice(const SkBitmap&, + const Lattice&, + const SkRect&, + const SkPaint*) override; + void onDrawVerticesObject(const SkVertices*, + const SkVertices::Bone[], + int, + SkBlendMode, + const SkPaint&) override; + void onDrawAtlas(const SkImage*, + const SkRSXform[], + const SkRect[], + const SkColor[], + int, + SkBlendMode, + const SkRect*, + const SkPaint*) override; + void onDrawEdgeAAQuad(const SkRect&, + const SkPoint[4], + QuadAAFlags, + const SkColor4f&, + SkBlendMode) override; + void onDrawEdgeAAImageSet(const ImageSetEntry[], + int, + const SkPoint[], + const SkMatrix[], + const SkPaint*, + SrcRectConstraint) override; + void onClipRegion(const SkRegion&, SkClipOp) override; + + private: + SkNWayCanvas internal_canvas_; + + std::vector draw_calls_; + int current_layer_; +}; + +inline bool operator==(const MockCanvas::SaveData& a, + const MockCanvas::SaveData& b) { + return a.save_to_layer == b.save_to_layer; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::SaveData& data) { + return os << data.save_to_layer; +} + +inline bool operator==(const MockCanvas::SaveLayerData& a, + const MockCanvas::SaveLayerData& b) { + return a.save_bounds == b.save_bounds && a.restore_paint == b.restore_paint && + a.backdrop_filter == b.backdrop_filter && + a.save_to_layer == b.save_to_layer; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::SaveLayerData& data) { + return os << data.save_bounds << " " << data.restore_paint << " " + << data.backdrop_filter << " " << data.save_to_layer; +} + +inline bool operator==(const MockCanvas::RestoreData& a, + const MockCanvas::RestoreData& b) { + return a.restore_to_layer == b.restore_to_layer; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::RestoreData& data) { + return os << data.restore_to_layer; +} + +inline bool operator==(const MockCanvas::ConcatMatrixData& a, + const MockCanvas::ConcatMatrixData& b) { + return a.matrix == b.matrix; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::ConcatMatrixData& data) { + return os << data.matrix; +} + +inline bool operator==(const MockCanvas::SetMatrixData& a, + const MockCanvas::SetMatrixData& b) { + return a.matrix == b.matrix; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::SetMatrixData& data) { + return os << data.matrix; +} + +inline bool operator==(const MockCanvas::DrawRectData& a, + const MockCanvas::DrawRectData& b) { + return a.rect == b.rect && a.paint == b.paint; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawRectData& data) { + return os << data.rect << " " << data.paint; +} + +inline bool operator==(const MockCanvas::DrawPathData& a, + const MockCanvas::DrawPathData& b) { + return a.path == b.path && a.paint == b.paint; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawPathData& data) { + return os << data.path << " " << data.paint; +} + +inline bool operator==(const MockCanvas::DrawPictureData& a, + const MockCanvas::DrawPictureData& b) { + return a.picture == b.picture && a.matrix == b.matrix && a.paint == b.paint; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawPictureData& data) { + os << data.picture << " "; + if (data.matrix) { + os << *data.matrix << " "; + } else { + os << "(null)" + << " "; + } + if (data.paint) { + os << *data.paint << " "; + } else { + os << "(null)" + << " "; + } + return os; +} + +inline bool operator==(const MockCanvas::DrawShadowData& a, + const MockCanvas::DrawShadowData& b) { + return a.path == b.path; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawShadowData& data) { + return os << data.path; +} + +inline bool operator==(const MockCanvas::ClipRectData& a, + const MockCanvas::ClipRectData& b) { + return a.rect == b.rect && a.clip_op == b.clip_op && a.style == b.style; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipRectData& data) { + return os << data.rect << " " << data.clip_op << " " << data.style; +} + +inline bool operator==(const MockCanvas::ClipRRectData& a, + const MockCanvas::ClipRRectData& b) { + return a.rrect == b.rrect && a.clip_op == b.clip_op && a.style == b.style; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipRRectData& data) { + return os << data.rrect << " " << data.clip_op << " " << data.style; +} + +inline bool operator==(const MockCanvas::ClipPathData& a, + const MockCanvas::ClipPathData& b) { + return a.path == b.path && a.clip_op == b.clip_op && a.style == b.style; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipPathData& data) { + return os << data.path << " " << data.clip_op << " " << data.style; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawCallData& data) { + std::visit([&os](auto& d) { os << d; }, data); + return os; +} + +inline bool operator==(const MockCanvas::DrawCall& a, + const MockCanvas::DrawCall& b) { + return a.layer == b.layer && a.data == b.data; +} + +inline std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawCall& draw) { + return os << "[Layer: " << draw.layer << ", Data: " << draw.data << "]"; +} + +} // namespace testing +} // namespace flutter + +#endif // TESTING_MOCK_CANVAS_H_ diff --git a/testing/thread_test.cc b/testing/thread_test.cc index 88415169a3c70..0f503769360a2 100644 --- a/testing/thread_test.cc +++ b/testing/thread_test.cc @@ -10,17 +10,11 @@ namespace flutter { namespace testing { // |testing::Test| -void ThreadTest::SetUp() { +ThreadTest::ThreadTest() { fml::MessageLoop::EnsureInitializedForCurrentThread(); current_task_runner_ = fml::MessageLoop::GetCurrent().GetTaskRunner(); } -// |testing::Test| -void ThreadTest::TearDown() { - current_task_runner_ = nullptr; - extra_threads_.clear(); -} - fml::RefPtr ThreadTest::GetCurrentTaskRunner() { return current_task_runner_; } diff --git a/testing/thread_test.h b/testing/thread_test.h index 8c55dbf80ce68..652fb4f480f77 100644 --- a/testing/thread_test.h +++ b/testing/thread_test.h @@ -26,9 +26,12 @@ namespace testing { /// class ThreadTest : public ::testing::Test { public: + ThreadTest(); + ~ThreadTest() override = default; + //---------------------------------------------------------------------------- /// @brief Get the task runner for the thread that the current unit-test - /// is running on. The creates a message loop is necessary. + /// is running on. This creates a message loop as necessary. /// /// @attention Unlike all other threads and task runners, this task runner is /// shared by all tests running in the process. Tests must ensure @@ -56,13 +59,6 @@ class ThreadTest : public ::testing::Test { /// fml::RefPtr CreateNewThread(std::string name = ""); - protected: - // |testing::Test| - void SetUp() override; - - // |testing::Test| - void TearDown() override; - private: fml::RefPtr current_task_runner_; std::vector> extra_threads_;