diff --git a/BUILD.gn b/BUILD.gn index 361a5c6f0e2f6..419ae849544a1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -95,7 +95,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/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c296ebe416fbd..1c549bd6e61c3 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -30,24 +30,32 @@ FILE: ../../../flutter/flow/instrumentation.cc FILE: ../../../flutter/flow/instrumentation.h FILE: ../../../flutter/flow/layers/backdrop_filter_layer.cc FILE: ../../../flutter/flow/layers/backdrop_filter_layer.h +FILE: ../../../flutter/flow/layers/backdrop_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/child_scene_layer.cc FILE: ../../../flutter/flow/layers/child_scene_layer.h FILE: ../../../flutter/flow/layers/clip_path_layer.cc FILE: ../../../flutter/flow/layers/clip_path_layer.h +FILE: ../../../flutter/flow/layers/clip_path_layer_unittests.cc FILE: ../../../flutter/flow/layers/clip_rect_layer.cc FILE: ../../../flutter/flow/layers/clip_rect_layer.h +FILE: ../../../flutter/flow/layers/clip_rect_layer_unittests.cc FILE: ../../../flutter/flow/layers/clip_rrect_layer.cc FILE: ../../../flutter/flow/layers/clip_rrect_layer.h +FILE: ../../../flutter/flow/layers/clip_rrect_layer_unittests.cc FILE: ../../../flutter/flow/layers/color_filter_layer.cc FILE: ../../../flutter/flow/layers/color_filter_layer.h +FILE: ../../../flutter/flow/layers/color_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/container_layer.cc FILE: ../../../flutter/flow/layers/container_layer.h +FILE: ../../../flutter/flow/layers/container_layer_unittests.cc FILE: ../../../flutter/flow/layers/layer.cc FILE: ../../../flutter/flow/layers/layer.h FILE: ../../../flutter/flow/layers/layer_tree.cc FILE: ../../../flutter/flow/layers/layer_tree.h +FILE: ../../../flutter/flow/layers/layer_tree_unittests.cc FILE: ../../../flutter/flow/layers/opacity_layer.cc FILE: ../../../flutter/flow/layers/opacity_layer.h +FILE: ../../../flutter/flow/layers/opacity_layer_unittests.cc FILE: ../../../flutter/flow/layers/performance_overlay_layer.cc FILE: ../../../flutter/flow/layers/performance_overlay_layer.h FILE: ../../../flutter/flow/layers/performance_overlay_layer_unittests.cc @@ -56,14 +64,19 @@ FILE: ../../../flutter/flow/layers/physical_shape_layer.h FILE: ../../../flutter/flow/layers/physical_shape_layer_unittests.cc FILE: ../../../flutter/flow/layers/picture_layer.cc FILE: ../../../flutter/flow/layers/picture_layer.h +FILE: ../../../flutter/flow/layers/picture_layer_unittests.cc FILE: ../../../flutter/flow/layers/platform_view_layer.cc FILE: ../../../flutter/flow/layers/platform_view_layer.h +FILE: ../../../flutter/flow/layers/platform_view_layer_unittests.cc FILE: ../../../flutter/flow/layers/shader_mask_layer.cc FILE: ../../../flutter/flow/layers/shader_mask_layer.h +FILE: ../../../flutter/flow/layers/shader_mask_layer_unittests.cc FILE: ../../../flutter/flow/layers/texture_layer.cc FILE: ../../../flutter/flow/layers/texture_layer.h +FILE: ../../../flutter/flow/layers/texture_layer_unittests.cc FILE: ../../../flutter/flow/layers/transform_layer.cc FILE: ../../../flutter/flow/layers/transform_layer.h +FILE: ../../../flutter/flow/layers/transform_layer_unittests.cc FILE: ../../../flutter/flow/matrix_decomposition.cc FILE: ../../../flutter/flow/matrix_decomposition.h FILE: ../../../flutter/flow/matrix_decomposition_unittests.cc diff --git a/flow/BUILD.gn b/flow/BUILD.gn index c2f0c98415a3e..137aa76d57811 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,26 @@ test_fixtures("flow_fixtures") { fixtures = [] } +source_set("flow_testing") { + testonly = true + + sources = [ + "testing/layer_test.h", + "testing/mock_layer.cc", + "testing/mock_layer.h", + "testing/mock_texture.cc", + "testing/mock_texture.h", + "testing/skia_gpu_object_layer_test.cc", + "testing/skia_gpu_object_layer_test.h", + ] + + public_deps = [ + ":flow", + "$flutter_root/testing:skia", + "//third_party/googletest:gtest", + ] +} + executable("flow_unittests") { testonly = true @@ -109,22 +129,74 @@ 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", "skia_gpu_object_unittests.cc", + "testing/mock_layer_unittests.cc", + "testing/mock_texture_unittests.cc", "texture_unittests.cc", ] 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_archive("flow_tests") { + testonly = true + + deps = [ + ":flow_unittests", + ] + + binary = "flow_unittests" + + libraries = common_libs + + meta_dir = "$flutter_root/testing/fuchsia/meta" + cmx_file = "$meta_dir/fuchsia_test.cmx" + + resources = [ + { + path = rebase_path( + "$flutter_root/testing/resources/performance_overlay_gold_60fps.png") + dest = "flutter/testing/resources/performance_overlay_gold_60fps.png" + }, + { + path = rebase_path( + "$flutter_root/testing/resources/performance_overlay_gold_90fps.png") + dest = "flutter/testing/resources/performance_overlay_gold_90fps.png" + }, + { + path = rebase_path( + "$flutter_root/testing/resources/performance_overlay_gold_120fps.png") + dest = "flutter/testing/resources/performance_overlay_gold_120fps.png" + }, + ] + } +} diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 919ee83a8bb86..030eb88c8a06d 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -143,6 +143,7 @@ class MutatorsStack { // Returns an iterator pointing to the bottom of the stack. const std::vector>::const_reverse_iterator Bottom() const; + bool is_empty() const { return vector_.empty(); } bool operator==(const MutatorsStack& other) const { if (vector_.size() != other.vector_.size()) { @@ -156,10 +157,26 @@ class MutatorsStack { return true; } + bool operator==(const std::vector& other) const { + if (vector_.size() != other.size()) { + return false; + } + for (size_t i = 0; i < vector_.size(); i++) { + if (*vector_[i] != other[i]) { + return false; + } + } + return true; + } + bool operator!=(const MutatorsStack& other) const { return !operator==(other); } + bool operator!=(const std::vector& other) const { + return !operator==(other); + } + private: std::vector> vector_; }; // MutatorsStack diff --git a/flow/flow_run_all_unittests.cc b/flow/flow_run_all_unittests.cc index 4cf0ba3d7fcdf..39963730172ee 100644 --- a/flow/flow_run_all_unittests.cc +++ b/flow/flow_run_all_unittests.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "flutter/fml/build_config.h" #include "flutter/fml/command_line.h" #include "flutter/fml/logging.h" #include "gtest/gtest.h" @@ -23,8 +24,14 @@ int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); fml::CommandLine cmd = fml::CommandLineFromArgcArgv(argc, argv); + +#if defined(OS_FUCHSIA) + flutter::SetGoldenDir(cmd.GetOptionValueWithDefault( + "golden-dir", "/pkg/data/flutter/testing/resources")); +#else flutter::SetGoldenDir( cmd.GetOptionValueWithDefault("golden-dir", "flutter/testing/resources")); +#endif flutter::SetFontFile(cmd.GetOptionValueWithDefault( "font-file", "flutter/third_party/txt/third_party/fonts/Roboto-Regular.ttf")); diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index 573db97f191a2..d5799cc4f95dc 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -9,8 +9,6 @@ namespace flutter { BackdropFilterLayer::BackdropFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} -BackdropFilterLayer::~BackdropFilterLayer() = default; - void BackdropFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "BackdropFilterLayer::Paint"); FML_DCHECK(needs_painting()); diff --git a/flow/layers/backdrop_filter_layer.h b/flow/layers/backdrop_filter_layer.h index ede9ceeef41f8..732a1ac27e89a 100644 --- a/flow/layers/backdrop_filter_layer.h +++ b/flow/layers/backdrop_filter_layer.h @@ -14,7 +14,6 @@ namespace flutter { class BackdropFilterLayer : public ContainerLayer { public: BackdropFilterLayer(sk_sp filter); - ~BackdropFilterLayer() override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/backdrop_filter_layer_unittests.cc b/flow/layers/backdrop_filter_layer_unittests.cc new file mode 100644 index 0000000000000..e02745adcc83a --- /dev/null +++ b/flow/layers/backdrop_filter_layer_unittests.cc @@ -0,0 +1,186 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/backdrop_filter_layer.h" + +#include "flutter/flow/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 { + +using BackdropFilterLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies) { + 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(BackdropFilterLayerTest, PaintBeforePrerollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(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\\(\\)"); +} +#endif + +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()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + layer1->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector( + {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.cc b/flow/layers/clip_path_layer.cc index d08c19b34eeb9..3957837d0d7f5 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -17,8 +17,6 @@ ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } -ClipPathLayer::~ClipPathLayer() = default; - void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect previous_cull_rect = context->cull_rect; SkRect clip_path_bounds = clip_path_.getBounds(); diff --git a/flow/layers/clip_path_layer.h b/flow/layers/clip_path_layer.h index fd4d56f0db7f0..c21e53c34e76e 100644 --- a/flow/layers/clip_path_layer.h +++ b/flow/layers/clip_path_layer.h @@ -12,7 +12,6 @@ namespace flutter { class ClipPathLayer : public ContainerLayer { public: ClipPathLayer(const SkPath& clip_path, Clip clip_behavior = Clip::antiAlias); - ~ClipPathLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; diff --git a/flow/layers/clip_path_layer_unittests.cc b/flow/layers/clip_path_layer_unittests.cc new file mode 100644 index 0000000000000..fb91d81f725d4 --- /dev/null +++ b/flow/layers/clip_path_layer_unittests.cc @@ -0,0 +1,196 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 { + +using ClipPathLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ClipPathLayerTest, ClipNoneBehaviorDies) { + EXPECT_DEATH_IF_SUPPORTED( + auto clip = std::make_shared(SkPath(), Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipPathLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(SkPath(), Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipPathLayerTest, PaintBeforePrerollDies) { + 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(ClipPathLayerTest, PaintingCulledLayerDies) { + 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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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.cc b/flow/layers/clip_rect_layer.cc index de7590624e408..191132a055788 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -11,8 +11,6 @@ ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } -ClipRectLayer::~ClipRectLayer() = default; - void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect previous_cull_rect = context->cull_rect; if (context->cull_rect.intersect(clip_rect_)) { diff --git a/flow/layers/clip_rect_layer.h b/flow/layers/clip_rect_layer.h index 76c5a3f01c873..50eef22a46e2f 100644 --- a/flow/layers/clip_rect_layer.h +++ b/flow/layers/clip_rect_layer.h @@ -12,7 +12,6 @@ namespace flutter { class ClipRectLayer : public ContainerLayer { public: ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior); - ~ClipRectLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/clip_rect_layer_unittests.cc b/flow/layers/clip_rect_layer_unittests.cc new file mode 100644 index 0000000000000..2784f62705b3f --- /dev/null +++ b/flow/layers/clip_rect_layer_unittests.cc @@ -0,0 +1,194 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 { + +using ClipRectLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ClipRectLayerTest, ClipNoneBehaviorDies) { + EXPECT_DEATH_IF_SUPPORTED( + auto clip = std::make_shared(kEmptyRect, Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipRectLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(kEmptyRect, Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRectLayerTest, PaintBeforePrerollDies) { + 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(ClipRectLayerTest, PaintingCulledLayerDies) { + 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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_bounds)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_bounds)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_bounds)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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.cc b/flow/layers/clip_rrect_layer.cc index 9899a1658732d..e02f8d2411a2b 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -11,8 +11,6 @@ ClipRRectLayer::ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } -ClipRRectLayer::~ClipRRectLayer() = default; - void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect previous_cull_rect = context->cull_rect; SkRect clip_rrect_bounds = clip_rrect_.getBounds(); diff --git a/flow/layers/clip_rrect_layer.h b/flow/layers/clip_rrect_layer.h index 53f74f30a0776..ce1cca2b568de 100644 --- a/flow/layers/clip_rrect_layer.h +++ b/flow/layers/clip_rrect_layer.h @@ -12,7 +12,6 @@ namespace flutter { class ClipRRectLayer : public ContainerLayer { public: ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior); - ~ClipRRectLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; diff --git a/flow/layers/clip_rrect_layer_unittests.cc b/flow/layers/clip_rrect_layer_unittests.cc new file mode 100644 index 0000000000000..5c9ed21a3a33f --- /dev/null +++ b/flow/layers/clip_rrect_layer_unittests.cc @@ -0,0 +1,199 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 { + +using ClipRRectLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ClipRRectLayerTest, ClipNoneBehaviorDies) { + const SkRRect layer_rrect = SkRRect::MakeEmpty(); + EXPECT_DEATH_IF_SUPPORTED( + auto clip = std::make_shared(layer_rrect, Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipRRectLayerTest, PaintingEmptyLayerDies) { + 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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRRectLayerTest, PaintBeforePreollDies) { + 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(ClipRRectLayerTest, PaintingCulledLayerDies) { + 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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // 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()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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.cc b/flow/layers/color_filter_layer.cc index f838b0612b2e5..8e5ea19ac8484 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -9,8 +9,6 @@ namespace flutter { ColorFilterLayer::ColorFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} -ColorFilterLayer::~ColorFilterLayer() = default; - void ColorFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ColorFilterLayer::Paint"); FML_DCHECK(needs_painting()); diff --git a/flow/layers/color_filter_layer.h b/flow/layers/color_filter_layer.h index cf1de4cb610fc..628c5b17f6e1c 100644 --- a/flow/layers/color_filter_layer.h +++ b/flow/layers/color_filter_layer.h @@ -14,7 +14,6 @@ namespace flutter { class ColorFilterLayer : public ContainerLayer { public: ColorFilterLayer(sk_sp filter); - ~ColorFilterLayer() override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc new file mode 100644 index 0000000000000..204a02357ad98 --- /dev/null +++ b/flow/layers/color_filter_layer_unittests.cc @@ -0,0 +1,199 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 { + +using ColorFilterLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ColorFilterLayerTest, PaintingEmptyLayerDies) { + 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(ColorFilterLayerTest, PaintBeforePreollDies) { + 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\\(\\)"); +} +#endif + +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()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setColorFilter(nullptr); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setColorFilter(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setColorFilter(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint1, filter_paint2; + filter_paint1.setColorFilter(layer_filter1); + filter_paint2.setColorFilter(layer_filter2); + layer1->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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..fdb13411a9ac8 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -8,8 +8,6 @@ namespace flutter { ContainerLayer::ContainerLayer() {} -ContainerLayer::~ContainerLayer() = default; - void ContainerLayer::Add(std::shared_ptr layer) { layer->set_parent(this); layers_.push_back(std::move(layer)); @@ -23,6 +21,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..a0c054b1ff15c 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -13,12 +13,11 @@ namespace flutter { class ContainerLayer : public Layer { public: ContainerLayer(); - ~ContainerLayer() override; 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..c5230fe7e70f3 --- /dev/null +++ b/flow/layers/container_layer_unittests.cc @@ -0,0 +1,202 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 ContainerLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ContainerLayerTest, 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(ContainerLayerTest, PaintingEmptyLayerDies) { + 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(ContainerLayerTest, PaintBeforePreollDies) { + 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\\(\\)"); +} +#endif + +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()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer->parent_cull_rect(), kGiantRect); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), + kGiantRect); // Siblings are independent + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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.cc b/flow/layers/layer_tree.cc index f0e37c9bed565..caa5531a79c06 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -17,8 +17,6 @@ LayerTree::LayerTree() checkerboard_raster_cache_images_(false), checkerboard_offscreen_layers_(false) {} -LayerTree::~LayerTree() = default; - void LayerTree::RecordBuildTime(fml::TimePoint start) { build_start_ = start; build_finish_ = fml::TimePoint::Now(); diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index 124b8a85dea45..b9edeeceba2ed 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -22,8 +22,6 @@ class LayerTree { public: LayerTree(); - ~LayerTree(); - void Preroll(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache = false); diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc new file mode 100644 index 0000000000000..e0961723e3c6e --- /dev/null +++ b/flow/layers/layer_tree_unittests.cc @@ -0,0 +1,203 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/layer_tree.h" + +#include "flutter/flow/compositor_context.h" +#include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/canvas_test.h" +#include "flutter/testing/mock_canvas.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +class LayerTreeTest : public CanvasTest { + public: + void SetUp() override { + root_transform_ = SkMatrix::MakeTrans(1.0f, 1.0f); + scoped_frame_ = compositor_context_.AcquireFrame( + nullptr, &mock_canvas(), nullptr, root_transform_, false, nullptr); + } + + void TearDown() override { scoped_frame_ = nullptr; } + + LayerTree& layer_tree() { return layer_tree_; } + CompositorContext::ScopedFrame& frame() { return *scoped_frame_.get(); } + const SkMatrix& root_transform() { return root_transform_; } + + private: + LayerTree layer_tree_; + CompositorContext compositor_context_; + SkMatrix root_transform_; + std::unique_ptr scoped_frame_; +}; + +TEST_F(LayerTreeTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(); + + layer_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + + layer_tree().Paint(frame()); +} + +TEST_F(LayerTreeTest, PaintBeforePreollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child_path; + child_path.addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + layer_tree().set_root_layer(layer); + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + + layer_tree().Paint(frame()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +TEST_F(LayerTreeTest, Simple) { + 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::kCyan); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + layer_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + 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()); + EXPECT_EQ(mock_layer->parent_matrix(), root_transform()); + + layer_tree().Paint(frame()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path, child_paint}}})); +} + +TEST_F(LayerTreeTest, Multiple) { + const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path2 = SkPath().addRect(8.0f, 2.0f, 16.5f, 14.5f); + const SkPaint child_paint1(SkColors::kGray); + const SkPaint child_paint2(SkColors::kGreen); + 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_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + 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()); + EXPECT_EQ(mock_layer1->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer2->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), + kGiantRect); // Siblings are independent + + layer_tree().Paint(frame()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{0, MockCanvas::DrawPathData{ + child_path2, child_paint2}}})); +} + +TEST_F(LayerTreeTest, MultipleWithEmpty) { + const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPaint child_paint1(SkColors::kGray); + const SkPaint child_paint2(SkColors::kGreen); + 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_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + 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()); + EXPECT_EQ(mock_layer1->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer2->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer_tree().Paint(frame()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}})); +} + +TEST_F(LayerTreeTest, NeedsSystemComposite) { + const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path2 = SkPath().addRect(8.0f, 2.0f, 16.5f, 14.5f); + const SkPaint child_paint1(SkColors::kGray); + const SkPaint child_paint2(SkColors::kGreen); + 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_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + 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()); + EXPECT_EQ(mock_layer1->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer2->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer_tree().Paint(frame()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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/opacity_layer.cc b/flow/layers/opacity_layer.cc index 6257700ffbddf..1b407307fd068 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -11,8 +11,6 @@ namespace flutter { OpacityLayer::OpacityLayer(int alpha, const SkPoint& offset) : alpha_(alpha), offset_(offset) {} -OpacityLayer::~OpacityLayer() = default; - void OpacityLayer::EnsureSingleChild() { FML_DCHECK(layers().size() > 0); // OpacityLayer should never be a leaf diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index 795d8841ba6ed..f1c18c51918e7 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -26,7 +26,6 @@ class OpacityLayer : public ContainerLayer { // to many leaf layers. Therefore we try to capture that offset here to stop // the propagation as repainting the OpacityLayer is expensive. OpacityLayer(int alpha, const SkPoint& offset); - ~OpacityLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc new file mode 100644 index 0000000000000..6ca78a8538fdf --- /dev/null +++ b/flow/layers/opacity_layer_unittests.cc @@ -0,0 +1,312 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 OpacityLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(OpacityLayerTest, 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(OpacityLayerTest, PaintingEmptyLayerDies) { + 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(OpacityLayerTest, PaintBeforePreollDies) { + 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\\(\\)"); +} +#endif + +TEST_F(OpacityLayerTest, FullyOpaque) { + const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); + const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer_transform = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer_transform)); +#endif + const SkPaint child_paint = SkPaint(SkColors::kGreen); + const SkRect expected_layer_bounds = + layer_transform.mapRect(child_path.getBounds()); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(SK_AlphaOPAQUE, layer_offset); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform), Mutator(SK_AlphaOPAQUE)})); + + const SkPaint opacity_paint = SkPaint(SkColors::kBlack); // A = 1.0f + SkRect opacity_bounds; + expected_layer_bounds.makeOffset(-layer_offset.fX, -layer_offset.fY) + .roundOut(&opacity_bounds); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer_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}}}); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +TEST_F(OpacityLayerTest, FullyTransparent) { + const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); + const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer_transform = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer_transform)); +#endif + const SkPaint child_paint = SkPaint(SkColors::kGreen); + const SkRect expected_layer_bounds = + layer_transform.mapRect(child_path.getBounds()); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = + std::make_shared(SK_AlphaTRANSPARENT, layer_offset); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ( + mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform), Mutator(SK_AlphaTRANSPARENT)})); + + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer_transform}}, +#endif + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::ClipRectData{kEmptyRect, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{2, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +TEST_F(OpacityLayerTest, HalfTransparent) { + const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); + const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer_transform = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer_transform)); +#endif + const SkPaint child_paint = SkPaint(SkColors::kGreen); + const SkRect expected_layer_bounds = + layer_transform.mapRect(child_path.getBounds()); + const SkAlpha alpha_half = 255 / 2; + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(alpha_half, layer_offset); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform), Mutator(alpha_half)})); + + const SkPaint opacity_paint = + SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha_half))); + SkRect opacity_bounds; + expected_layer_bounds.makeOffset(-layer_offset.fX, -layer_offset.fY) + .roundOut(&opacity_bounds); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer_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}}}); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +TEST_F(OpacityLayerTest, Nested) { + const SkPath child1_path = SkPath().addRect(SkRect::MakeWH(5.0f, 6.0f)); + const SkPath child2_path = SkPath().addRect(SkRect::MakeWH(2.0f, 7.0f)); + const SkPath child3_path = SkPath().addRect(SkRect::MakeWH(6.0f, 6.0f)); + const SkPoint layer1_offset = SkPoint::Make(0.5f, 1.5f); + const SkPoint layer2_offset = SkPoint::Make(2.5f, 0.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer1_transform = + SkMatrix::MakeTrans(layer1_offset.fX, layer1_offset.fY); + const SkMatrix layer2_transform = + SkMatrix::MakeTrans(layer2_offset.fX, layer2_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer1_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer1_transform)); + const SkMatrix integral_layer2_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); +#endif + const SkPaint child1_paint = SkPaint(SkColors::kRed); + const SkPaint child2_paint = SkPaint(SkColors::kBlue); + const SkPaint child3_paint = SkPaint(SkColors::kGreen); + const SkAlpha alpha1 = 155; + const SkAlpha alpha2 = 224; + auto mock_layer1 = std::make_shared(child1_path, child1_paint); + auto mock_layer2 = std::make_shared(child2_path, child2_paint); + auto mock_layer3 = std::make_shared(child3_path, child3_paint); + auto layer1 = std::make_shared(alpha1, layer1_offset); + auto layer2 = std::make_shared(alpha2, layer2_offset); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + layer1->Add(mock_layer3); // Ensure something is processed after recursion + + const SkRect expected_layer2_bounds = + layer2_transform.mapRect(child2_path.getBounds()); + SkRect expected_layer1_bounds = expected_layer2_bounds; + expected_layer1_bounds.join(child1_path.getBounds()); + expected_layer1_bounds.join(child3_path.getBounds()); + expected_layer1_bounds = layer1_transform.mapRect(expected_layer1_bounds); + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child1_path.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child2_path.getBounds()); + EXPECT_EQ(mock_layer3->paint_bounds(), child3_path.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), expected_layer1_bounds); + EXPECT_EQ(layer2->paint_bounds(), expected_layer2_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(mock_layer3->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), + SkMatrix::Concat(initial_transform, layer1_transform)); + // EXPECT_EQ(mock_layer1->parent_mutators(), + // std::vector({Mutator(layer1_transform), Mutator(alpha1)})); + EXPECT_EQ( + mock_layer2->parent_matrix(), + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); + // EXPECT_EQ(mock_layer2->parent_mutators(), + // std::vector({Mutator(layer1_transform), Mutator(alpha1), + // Mutator(layer2_transform), Mutator(alpha2)})); + EXPECT_EQ(mock_layer3->parent_matrix(), + SkMatrix::Concat(initial_transform, layer1_transform)); + // EXPECT_EQ(mock_layer3->parent_mutators(), + // std::vector({Mutator(layer1_transform), Mutator(alpha1)})); + + const SkPaint opacity1_paint = + SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha1))); + const SkPaint opacity2_paint = + SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha2))); + SkRect opacity1_bounds, opacity2_bounds; + expected_layer1_bounds.makeOffset(-layer1_offset.fX, -layer1_offset.fY) + .roundOut(&opacity1_bounds); + expected_layer2_bounds.makeOffset(-layer2_offset.fX, -layer2_offset.fY) + .roundOut(&opacity2_bounds); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer1_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer1_transform}}, +#endif + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{opacity1_bounds, opacity1_paint, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child1_path, child1_paint}}, + MockCanvas::DrawCall{2, MockCanvas::SaveData{3}}, + MockCanvas::DrawCall{3, MockCanvas::ConcatMatrixData{layer2_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 3, MockCanvas::SetMatrixData{integral_layer2_transform}}, +#endif + MockCanvas::DrawCall{ + 3, MockCanvas::SaveLayerData{opacity2_bounds, opacity2_paint, + nullptr, 4}}, + MockCanvas::DrawCall{ + 4, MockCanvas::DrawPathData{child2_path, child2_paint}}, + MockCanvas::DrawCall{4, MockCanvas::RestoreData{3}}, + MockCanvas::DrawCall{3, MockCanvas::RestoreData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child3_path, child3_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + layer1->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index ebb279c966866..ef7b6f2c6194c 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -8,26 +8,11 @@ #include "flutter/flow/layers/performance_overlay_layer.h" #include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkTextBlob.h" namespace flutter { namespace { -void DrawStatisticsText(SkCanvas& canvas, - const std::string& string, - int x, - int y, - const std::string& font_path) { - SkFont font; - if (font_path != "") { - font = SkFont(SkTypeface::MakeFromFile(font_path.c_str())); - } - font.setSize(15); - SkPaint paint; - paint.setColor(SK_ColorGRAY); - canvas.drawSimpleText(string.c_str(), string.size(), SkTextEncoding::kUTF8, x, - y, font, paint); -} - void VisualizeStopWatch(SkCanvas& canvas, const Stopwatch& stopwatch, SkScalar x, @@ -47,21 +32,39 @@ void VisualizeStopWatch(SkCanvas& canvas, } if (show_labels) { - double max_ms_per_frame = stopwatch.MaxDelta().ToMillisecondsF(); - double average_ms_per_frame = stopwatch.AverageDelta().ToMillisecondsF(); - std::stringstream stream; - stream.setf(std::ios::fixed | std::ios::showpoint); - stream << std::setprecision(1); - stream << label_prefix << " " - << "max " << max_ms_per_frame << " ms/frame, " - << "avg " << average_ms_per_frame << " ms/frame"; - DrawStatisticsText(canvas, stream.str(), x + label_x, y + height + label_y, - font_path); + auto text = PerformanceOverlayLayer::MakeStatisticsText( + stopwatch, label_prefix, font_path); + SkPaint paint; + paint.setColor(SK_ColorGRAY); + canvas.drawTextBlob(text, x + label_x, y + height + label_y, paint); } } } // namespace +sk_sp PerformanceOverlayLayer::MakeStatisticsText( + const Stopwatch& stopwatch, + const std::string& label_prefix, + const std::string& font_path) { + SkFont font; + if (font_path != "") { + font = SkFont(SkTypeface::MakeFromFile(font_path.c_str())); + } + font.setSize(15); + + double max_ms_per_frame = stopwatch.MaxDelta().ToMillisecondsF(); + double average_ms_per_frame = stopwatch.AverageDelta().ToMillisecondsF(); + std::stringstream stream; + stream.setf(std::ios::fixed | std::ios::showpoint); + stream << std::setprecision(1); + stream << label_prefix << " " + << "max " << max_ms_per_frame << " ms/frame, " + << "avg " << average_ms_per_frame << " ms/frame"; + auto text = stream.str(); + return SkTextBlob::MakeFromText(text.c_str(), text.size(), font, + SkTextEncoding::kUTF8); +} + PerformanceOverlayLayer::PerformanceOverlayLayer(uint64_t options, const char* font_path) : options_(options) { diff --git a/flow/layers/performance_overlay_layer.h b/flow/layers/performance_overlay_layer.h index b5c3370d2055a..b1434a221e688 100644 --- a/flow/layers/performance_overlay_layer.h +++ b/flow/layers/performance_overlay_layer.h @@ -7,9 +7,12 @@ #include +#include "flutter/flow/instrumentation.h" #include "flutter/flow/layers/layer.h" #include "flutter/fml/macros.h" +class SkTextBlob; + namespace flutter { const int kDisplayRasterizerStatistics = 1 << 0; @@ -19,6 +22,10 @@ const int kVisualizeEngineStatistics = 1 << 3; class PerformanceOverlayLayer : public Layer { public: + static sk_sp MakeStatisticsText(const Stopwatch& stopwatch, + const std::string& label_prefix, + const std::string& font_path); + explicit PerformanceOverlayLayer(uint64_t options, const char* font_path = nullptr); diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index 605717c870ee3..769f80803a8fa 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -2,18 +2,29 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/flow/flow_test_utils.h" #include "flutter/flow/layers/performance_overlay_layer.h" + +#include "flutter/flow/flow_test_utils.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 "gtest/gtest.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkSerialProcs.h" #include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkTextBlob.h" #include "third_party/skia/include/utils/SkBase64.h" -#include "gtest/gtest.h" - +#include #include +namespace flutter { +namespace testing { +namespace { + // To get the size of kMockedTimes in compile time. template constexpr int size(const T (&array)[N]) noexcept { @@ -77,7 +88,7 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { << "Please either set --golden-dir, or make sure that the unit test is " << "run from the right directory (e.g., flutter/engine/src)."; -#if !OS_LINUX +#if !defined(OS_LINUX) GTEST_SKIP() << "Skipping golden tests on non-Linux OSes"; #endif // OS_LINUX const bool golden_data_matches = golden_data->equals(snapshot_data.get()); @@ -97,12 +108,72 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { << "Golden file mismatch. Please check " << "the difference between " << golden_file_path << " and " << new_golden_file_path << ", and replace the former " - << "with the latter if the difference looks good.\n\n" + << "with the latter if the difference looks good.\nS\n" << "See also the base64 encoded " << new_golden_file_path << ":\n" << b64_char; } } +} // namespace + +using PerformanceOverlayLayerTest = LayerTest; + +TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) { + const uint64_t overlay_opts = kVisualizeRasterizerStatistics; + auto layer = std::make_shared(overlay_opts); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + + // Crashes reading a nullptr. + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), ""); +} + +TEST_F(PerformanceOverlayLayerTest, InvalidOptions) { + const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f); + const uint64_t overlay_opts = 0; + auto layer = std::make_shared(overlay_opts); + + // TODO(): Note calling code has to call set_paint_bounds right now. Make + // this a constructor parameter and move the set_paint_bounds into Preroll + layer->set_paint_bounds(layer_bounds); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), layer_bounds); + EXPECT_TRUE(layer->needs_painting()); + + // Nothing is drawn if options are invalid (0). + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +TEST_F(PerformanceOverlayLayerTest, SimpleRasterizerStatistics) { + const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f); + const uint64_t overlay_opts = kDisplayRasterizerStatistics; + auto layer = std::make_shared(overlay_opts); + + // TODO(): Note calling code has to call set_paint_bounds right now. Make + // this a constructor parameter and move the set_paint_bounds into Preroll + layer->set_paint_bounds(layer_bounds); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), layer_bounds); + EXPECT_TRUE(layer->needs_painting()); + + layer->Paint(paint_context()); + auto overlay_text = PerformanceOverlayLayer::MakeStatisticsText( + paint_context().raster_time, "GPU", ""); + auto overlay_text_data = overlay_text->serialize(SkSerialProcs{}); + SkPaint text_paint; + text_paint.setColor(SK_ColorGRAY); + SkPoint text_position = SkPoint::Make(16.0f, 22.0f); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawTextData{overlay_text_data, text_paint, + text_position}}})); +} + TEST(PerformanceOverlayLayerDefault, Gold) { TestPerformanceOverlayLayerGold(60); } @@ -114,3 +185,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 0a607a88c23b0..91b6c73071d17 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), @@ -48,8 +50,6 @@ PhysicalShapeLayer::PhysicalShapeLayer(SkColor color, } } -PhysicalShapeLayer::~PhysicalShapeLayer() = default; - void PhysicalShapeLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "PhysicalShapeLayer::Preroll"); @@ -66,48 +66,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) } } @@ -192,6 +155,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..1b5564c3662b3 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -18,8 +18,10 @@ class PhysicalShapeLayer : public ContainerLayer { float elevation, const SkPath& path, 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 +37,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_; - float viewport_depth_; +#if defined(OS_FUCHSIA) + float viewport_depth_ = 0.0f; +#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..b9382318e8378 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/flow/layers/physical_shape_layer_unittests.cc @@ -4,65 +4,245 @@ #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 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); - } +#ifndef NDEBUG +TEST_F(PhysicalShapeLayerTest, PaintingEmptyLayerDies) { + 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(PhysicalShapeLayerTest, PaintBeforePreollDies) { + 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\\(\\)"); +} +#endif + +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()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}})); +} + +TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPath) { + SkPath layer_path; + layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child1_path; + child1_path.addRect(4, 0, 12, 12).close(); + SkPath child2_path; + child2_path.addRect(3, 2, 5, 15).close(); + auto child1 = std::make_shared(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()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child1_path, child1_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawPathData{ + child2_path, child2_paint}}})); +} + +TEST_F(PhysicalShapeLayerTest, ElevationSimple) { + constexpr float initial_elevation = 20.0f; + SkPath layer_path; + layer_path.addRect(0, 0, 8, 8).close(); + auto layer = std::make_shared( + SK_ColorGREEN, SK_ColorBLACK, + 1.0f, // pixel ratio + 1.0f, // depth + initial_elevation, layer_path, Clip::none); + + layer->Preroll(preroll_context(), SkMatrix()); + // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and + // their shadows , so we do not do any painting there. +#if defined(OS_FUCHSIA) + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_TRUE(layer->needs_system_composite()); +#else + EXPECT_EQ(layer->paint_bounds(), + PhysicalShapeLayer::ComputeShadowBounds(layer_path.getBounds(), + initial_elevation, 1.0f)); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); +#endif + EXPECT_EQ(layer->total_elevation(), initial_elevation); + + // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and + // their shadows , so we do not use the direct |Paint()| path there. +#if !defined(OS_FUCHSIA) + SkPaint layer_paint; + layer_paint.setColor(SK_ColorGREEN); + layer_paint.setAntiAlias(true); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}})); +#endif +} + +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) { + // On Fuchsia, the system compositor handles all elevated + // PhysicalShapeLayers and their shadows , so we do not do any painting + // there. +#if defined(OS_FUCHSIA) + EXPECT_EQ(layers[i]->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layers[i]->needs_painting()); + EXPECT_TRUE(layers[i]->needs_system_composite()); +#else + EXPECT_EQ(layers[i]->paint_bounds(), + (PhysicalShapeLayer::ComputeShadowBounds( + layer_path.getBounds(), initial_elevations[i], + 1.0f /* pixel_ratio */))); + EXPECT_TRUE(layers[i]->needs_painting()); + EXPECT_FALSE(layers[i]->needs_system_composite()); +#endif + EXPECT_EQ(layers[i]->total_elevation(), total_elevations[i]); + } + + // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and + // their shadows , so we do not use the direct |Paint()| path there. +#if !defined(OS_FUCHSIA) + SkPaint layer_paint; + layer_paint.setColor(SK_ColorBLACK); + layer_paint.setAntiAlias(true); + layers[0]->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}})); +#endif } +} // namespace testing } // namespace flutter diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index c4275e76c13cf..230b648f50e80 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -17,8 +17,6 @@ PictureLayer::PictureLayer(const SkPoint& offset, is_complex_(is_complex), will_change_(will_change) {} -PictureLayer::~PictureLayer() = default; - void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkPicture* sk_picture = picture(); diff --git a/flow/layers/picture_layer.h b/flow/layers/picture_layer.h index 9c40cbef37cbd..e733e7455ca6c 100644 --- a/flow/layers/picture_layer.h +++ b/flow/layers/picture_layer.h @@ -19,7 +19,6 @@ class PictureLayer : public Layer { SkiaGPUObject picture, bool is_complex, bool will_change); - ~PictureLayer() override; SkPicture* picture() const { return picture_.get().get(); } diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc new file mode 100644 index 0000000000000..687c870eeac66 --- /dev/null +++ b/flow/layers/picture_layer_unittests.cc @@ -0,0 +1,105 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define FML_USED_ON_EMBEDDER + +#include "flutter/flow/layers/picture_layer.h" + +#include "flutter/flow/testing/skia_gpu_object_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 PictureLayerTest = SkiaGPUObjectLayerTest; + +#ifndef NDEBUG +TEST_F(PictureLayerTest, PaintBeforePrerollInvalidPictureDies) { + 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(PictureLayerTest, PaintBeforePreollDies) { + 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(PictureLayerTest, PaintingEmptyLayerDies) { + 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\\(\\)"); +} +#endif + +TEST_F(PictureLayerTest, InvalidPictureDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(), false, false); + + // Crashes reading a nullptr. + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), ""); +} + +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()); + auto expected_draw_calls = std::vector( + {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{mock_picture->serialize(), SkPaint(), + SkMatrix()}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 3f72993f97d66..81541b7a0cde8 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -11,8 +11,6 @@ PlatformViewLayer::PlatformViewLayer(const SkPoint& offset, int64_t view_id) : offset_(offset), size_(size), view_id_(view_id) {} -PlatformViewLayer::~PlatformViewLayer() = default; - void PlatformViewLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index 7ce7ccb58a856..242b3734dd3b1 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -14,7 +14,6 @@ namespace flutter { class PlatformViewLayer : public Layer { public: PlatformViewLayer(const SkPoint& offset, const SkSize& size, int64_t view_id); - ~PlatformViewLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc new file mode 100644 index 0000000000000..123f9ab9925f6 --- /dev/null +++ b/flow/layers/platform_view_layer_unittests.cc @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index 36e7b7332aeae..8e681ec725458 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -11,8 +11,6 @@ ShaderMaskLayer::ShaderMaskLayer(sk_sp shader, SkBlendMode blend_mode) : shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {} -ShaderMaskLayer::~ShaderMaskLayer() = default; - void ShaderMaskLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ShaderMaskLayer::Paint"); FML_DCHECK(needs_painting()); diff --git a/flow/layers/shader_mask_layer.h b/flow/layers/shader_mask_layer.h index 01836f4f2fb54..7f633c0372d45 100644 --- a/flow/layers/shader_mask_layer.h +++ b/flow/layers/shader_mask_layer.h @@ -16,7 +16,6 @@ class ShaderMaskLayer : public ContainerLayer { ShaderMaskLayer(sk_sp shader, const SkRect& mask_rect, SkBlendMode blend_mode); - ~ShaderMaskLayer() override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc new file mode 100644 index 0000000000000..d8997c3e65b2f --- /dev/null +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -0,0 +1,257 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 { + +using ShaderMaskLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ShaderMaskLayerTest, PaintingEmptyLayerDies) { + 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(ShaderMaskLayerTest, PaintBeforePreollDies) { + 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\\(\\)"); +} +#endif + +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()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(nullptr); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + 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()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {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.cc b/flow/layers/texture_layer.cc index c7716dd59bc29..848f69c8a115a 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -14,8 +14,6 @@ TextureLayer::TextureLayer(const SkPoint& offset, bool freeze) : offset_(offset), size_(size), texture_id_(texture_id), freeze_(freeze) {} -TextureLayer::~TextureLayer() = default; - void TextureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), size_.height())); diff --git a/flow/layers/texture_layer.h b/flow/layers/texture_layer.h index 7c04471afa0c1..20f6c709d6107 100644 --- a/flow/layers/texture_layer.h +++ b/flow/layers/texture_layer.h @@ -17,7 +17,6 @@ class TextureLayer : public Layer { const SkSize& size, int64_t texture_id, bool freeze); - ~TextureLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/texture_layer_unittests.cc b/flow/layers/texture_layer_unittests.cc new file mode 100644 index 0000000000000..9adf273e61122 --- /dev/null +++ b/flow/layers/texture_layer_unittests.cc @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/texture_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/flow/testing/mock_texture.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()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +TEST_F(TextureLayerTest, PaintingEmptyLayerDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(0.0f, 0.0f); + const int64_t texture_id = 0; + auto mock_texture = std::make_shared(texture_id); + auto layer = std::make_shared(layer_offset, layer_size, + texture_id, false); + + // Ensure the texture is located by the Layer. + preroll_context()->texture_registry.RegisterTexture(mock_texture); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_texture->paint_calls(), + std::vector({MockTexture::PaintCall{ + mock_canvas(), layer->paint_bounds(), false, nullptr}})); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 5a7af132c68f2..9513e8bc0fec6 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -24,8 +24,6 @@ TransformLayer::TransformLayer(const SkMatrix& transform) } } -TransformLayer::~TransformLayer() = default; - void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkMatrix child_matrix; child_matrix.setConcat(matrix, transform_); diff --git a/flow/layers/transform_layer.h b/flow/layers/transform_layer.h index f19a963ced9fe..a21e7d4f10c5b 100644 --- a/flow/layers/transform_layer.h +++ b/flow/layers/transform_layer.h @@ -14,7 +14,6 @@ namespace flutter { class TransformLayer : public ContainerLayer { public: TransformLayer(const SkMatrix& transform); - ~TransformLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; diff --git a/flow/layers/transform_layer_unittests.cc b/flow/layers/transform_layer_unittests.cc new file mode 100644 index 0000000000000..b99f35b2c7a25 --- /dev/null +++ b/flow/layers/transform_layer_unittests.cc @@ -0,0 +1,228 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/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 TransformLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(TransformLayerTest, PaintingEmptyLayerDies) { + 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(TransformLayerTest, PaintBeforePreollDies) { + 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\\(\\)"); +} +#endif + +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()); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); // identity + EXPECT_EQ(mock_layer->parent_cull_rect(), cull_rect); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(SkMatrix())})); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ(mock_layer->parent_cull_rect(), + inverse_layer_transform.mapRect(cull_rect)); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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()); + EXPECT_EQ( + mock_layer->parent_matrix(), + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); + EXPECT_EQ(mock_layer->parent_cull_rect(), + inverse_layer2_transform.mapRect( + inverse_layer1_transform.mapRect(cull_rect))); + EXPECT_EQ( + mock_layer->parent_mutators(), + std::vector({Mutator(layer2_transform), Mutator(layer1_transform)})); + + layer1->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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); + layer1->Add(layer2); + layer2->Add(mock_layer2); + + preroll_context()->cull_rect = cull_rect; + layer1->Preroll(preroll_context(), initial_transform); + SkRect expected_layer1_bounds = layer2->paint_bounds(); + expected_layer1_bounds.join(mock_layer1->paint_bounds()); + layer1_transform.mapRect(&expected_layer1_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(), child_path.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), expected_layer1_bounds); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), + SkMatrix::Concat(initial_transform, layer1_transform)); + EXPECT_EQ( + mock_layer2->parent_matrix(), + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); + EXPECT_EQ(mock_layer1->parent_cull_rect(), + inverse_layer1_transform.mapRect(cull_rect)); + EXPECT_EQ(mock_layer2->parent_cull_rect(), + inverse_layer2_transform.mapRect( + inverse_layer1_transform.mapRect(cull_rect))); + EXPECT_EQ(mock_layer1->parent_mutators(), + std::vector({Mutator(layer1_transform)})); + EXPECT_EQ( + mock_layer2->parent_mutators(), + std::vector({Mutator(layer2_transform), Mutator(layer1_transform)})); + + layer1->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({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..8aa511e4a0a97 100644 --- a/flow/matrix_decomposition_unittests.cc +++ b/flow/matrix_decomposition_unittests.cc @@ -12,6 +12,9 @@ #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.h b/flow/skia_gpu_object.h index 4823ec14208c5..2a09f982a4386 100644 --- a/flow/skia_gpu_object.h +++ b/flow/skia_gpu_object.h @@ -54,14 +54,11 @@ class SkiaGPUObject { using SkiaObjectType = T; SkiaGPUObject() = default; - SkiaGPUObject(sk_sp object, fml::RefPtr queue) : object_(std::move(object)), queue_(std::move(queue)) { FML_DCHECK(object_); } - SkiaGPUObject(SkiaGPUObject&&) = default; - ~SkiaGPUObject() { reset(); } SkiaGPUObject& operator=(SkiaGPUObject&&) = default; diff --git a/flow/skia_gpu_object_unittests.cc b/flow/skia_gpu_object_unittests.cc index aa259a6909eec..9df82ad11c312 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,107 @@ 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)); +class SkiaGpuObjectTest : public ThreadTest { + 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))) { + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + } + + 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(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..e38d690a2eeb7 --- /dev/null +++ b/flow/testing/layer_test.h @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLOW_TESTING_LAYER_TEST_H_ +#define FLOW_TESTING_LAYER_TEST_H_ + +#include "flutter/flow/layers/layer.h" + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/testing/canvas_test.h" +#include "flutter/testing/mock_canvas.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 { + +// This fixture allows generating tests which can |Paint()| and |Preroll()| +// |Layer|'s. +// |LayerTest| is a default implementation based on |::testing::Test|. +// +// |BaseT| should be the base test type, such as |::testing::Test| below. +template +class LayerTestBase : public CanvasTestBase { + using TestT = CanvasTestBase; + + public: + LayerTestBase() + : preroll_context_({ + nullptr, /* raster_cache */ + nullptr, /* gr_context */ + nullptr, /* external_view_embedder */ + mutators_stack_, TestT::mock_canvas().imageInfo().colorSpace(), + kGiantRect, /* cull_rect */ + raster_time_, ui_time_, texture_registry_, + false, /* checkerboard_offscreen_layers */ + 0.0f /* total_elevation */ + }), + paint_context_({ + TestT::mock_canvas().internal_canvas(), /* internal_nodes_canvas */ + &TestT::mock_canvas(), /* leaf_nodes_canvas */ + nullptr, /* gr_context */ + nullptr, /* external_view_embedder */ + raster_time_, ui_time_, texture_registry_, + nullptr, /* raster_cache */ + false, /* checkerboard_offscreen_layers */ + }) {} + + TextureRegistry& texture_regitry() { return texture_registry_; } + PrerollContext* preroll_context() { return &preroll_context_; } + Layer::PaintContext& paint_context() { return paint_context_; } + + private: + Stopwatch raster_time_; + Stopwatch ui_time_; + MutatorsStack mutators_stack_; + TextureRegistry texture_registry_; + + PrerollContext preroll_context_; + Layer::PaintContext paint_context_; + + FML_DISALLOW_COPY_AND_ASSIGN(LayerTestBase); +}; +using LayerTest = LayerTestBase<::testing::Test>; + +} // 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..1065f43054674 --- /dev/null +++ b/flow/testing/mock_layer.cc @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/testing/mock_layer.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; + + context->has_platform_view = fake_has_platform_view_; + set_paint_bounds(fake_paint_path_.getBounds()); + set_needs_system_composite(fake_needs_system_composite_); +} + +void MockLayer::Paint(PaintContext& context) const { + FML_DCHECK(needs_painting()); + + context.leaf_nodes_canvas->drawPath(fake_paint_path_, fake_paint_); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h new file mode 100644 index 0000000000000..b55452a37812c --- /dev/null +++ b/flow/testing/mock_layer.h @@ -0,0 +1,50 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLOW_TESTING_MOCK_LAYER_H_ +#define FLOW_TESTING_MOCK_LAYER_H_ + +#include "flutter/flow/layers/layer.h" + +namespace flutter { +namespace testing { + +// Mock implementation of the |Layer| interface that does nothing but paint +// the specified |path| into the canvas. It records the |PrerollContext| and +// |PaintContext| data passed in by its parent |Layer|, so the test can later +// verify the data against expected values. +class MockLayer : public Layer { + public: + MockLayer(SkPath path, + SkPaint paint = SkPaint(), + bool fake_has_platform_view = false, + bool fake_needs_system_composite = false); + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Paint(PaintContext& context) const override; + + const MutatorsStack& parent_mutators() { return parent_mutators_; } + const SkMatrix& parent_matrix() { return parent_matrix_; } + const SkRect& parent_cull_rect() { return parent_cull_rect_; } + float parent_elevation() { return parent_elevation_; } + bool parent_has_platform_view() { return parent_has_platform_view_; } + + private: + MutatorsStack parent_mutators_; + SkMatrix parent_matrix_; + SkRect parent_cull_rect_ = SkRect::MakeEmpty(); + SkPath fake_paint_path_; + SkPaint fake_paint_; + float parent_elevation_ = 0; + bool parent_has_platform_view_ = false; + bool fake_has_platform_view_ = false; + bool fake_needs_system_composite_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(MockLayer); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLOW_TESTING_MOCK_LAYER_H_ diff --git a/flow/testing/mock_layer_unittests.cc b/flow/testing/mock_layer_unittests.cc new file mode 100644 index 0000000000000..d5a215fde5c76 --- /dev/null +++ b/flow/testing/mock_layer_unittests.cc @@ -0,0 +1,87 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/testing/mock_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using MockLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(MockLayerTest, PaintBeforePreollDies) { + SkPath path = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto layer = std::make_shared(path, SkPaint()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(MockLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(SkPath(), SkPaint()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkPath().getBounds()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(MockLayerTest, SimpleParams) { + const SkPath path = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPaint paint = SkPaint(SkColors::kBlue); + const SkMatrix start_matrix = SkMatrix::MakeTrans(1.0f, 2.0f); + const SkMatrix scale_matrix = SkMatrix::MakeScale(0.5f, 0.5f); + const SkRect cull_rect = SkRect::MakeWH(5.0f, 5.0f); + const float parent_elevation = 5.0f; + const bool parent_has_platform_view = true; + auto layer = std::make_shared(path, paint); + + preroll_context()->mutators_stack.PushTransform(scale_matrix); + preroll_context()->cull_rect = cull_rect; + preroll_context()->total_elevation = parent_elevation; + preroll_context()->has_platform_view = parent_has_platform_view; + layer->Preroll(preroll_context(), start_matrix); + EXPECT_EQ(preroll_context()->has_platform_view, false); + EXPECT_EQ(layer->paint_bounds(), path.getBounds()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + EXPECT_EQ(layer->parent_mutators(), std::vector{Mutator(scale_matrix)}); + EXPECT_EQ(layer->parent_matrix(), start_matrix); + EXPECT_EQ(layer->parent_cull_rect(), cull_rect); + EXPECT_EQ(layer->parent_elevation(), parent_elevation); + EXPECT_EQ(layer->parent_has_platform_view(), parent_has_platform_view); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{path, paint}}})); +} + +TEST_F(MockLayerTest, FakePlatformView) { + auto layer = std::make_shared(SkPath(), SkPaint(), + true /* fake_has_platform_view */); + EXPECT_EQ(preroll_context()->has_platform_view, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->has_platform_view, true); +} + +TEST_F(MockLayerTest, FakeSystemComposite) { + auto layer = std::make_shared( + SkPath(), SkPaint(), false /* fake_has_platform_view */, + true /* fake_needs_system_composite */); + EXPECT_EQ(layer->needs_system_composite(), false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->needs_system_composite(), true); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_texture.cc b/flow/testing/mock_texture.cc new file mode 100644 index 0000000000000..26e49b764cdaf --- /dev/null +++ b/flow/testing/mock_texture.cc @@ -0,0 +1,31 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/testing/mock_texture.h" + +namespace flutter { +namespace testing { + +MockTexture::MockTexture(int64_t textureId) : Texture(textureId) {} + +void MockTexture::Paint(SkCanvas& canvas, + const SkRect& bounds, + bool freeze, + GrContext* context) { + paint_calls_.emplace_back(PaintCall{canvas, bounds, freeze, context}); +} + +bool operator==(const MockTexture::PaintCall& a, + const MockTexture::PaintCall& b) { + return &a.canvas == &b.canvas && a.bounds == b.bounds && + a.context == b.context && a.freeze == b.freeze; +} + +std::ostream& operator<<(std::ostream& os, const MockTexture::PaintCall& data) { + return os << &data.canvas << " " << data.bounds << " " << data.context << " " + << data.freeze; +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_texture.h b/flow/testing/mock_texture.h new file mode 100644 index 0000000000000..c5339ebb77a66 --- /dev/null +++ b/flow/testing/mock_texture.h @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/texture.h" +#include "flutter/testing/assertions_skia.h" + +#include +#include + +namespace flutter { +namespace testing { + +// Mock implementation of the |Texture| interface that does not interact with +// the GPU. It simply records the list of various calls made so the test can +// later verify them against expected data. +class MockTexture : public Texture { + public: + struct PaintCall { + SkCanvas& canvas; + SkRect bounds; + bool freeze; + GrContext* context; + }; + + explicit MockTexture(int64_t textureId); + + // Called from GPU thread. + void Paint(SkCanvas& canvas, + const SkRect& bounds, + bool freeze, + GrContext* context) override; + + void OnGrContextCreated() override { gr_context_created_ = true; } + void OnGrContextDestroyed() override { gr_context_destroyed_ = true; } + void MarkNewFrameAvailable() override {} + void OnTextureUnregistered() override { unregistered_ = true; } + + const std::vector& paint_calls() { return paint_calls_; } + bool gr_context_created() { return gr_context_created_; } + bool gr_context_destroyed() { return gr_context_destroyed_; } + bool unregistered() { return unregistered_; } + + private: + std::vector paint_calls_; + bool gr_context_created_ = false; + bool gr_context_destroyed_ = false; + bool unregistered_ = false; +}; + +extern bool operator==(const MockTexture::PaintCall& a, + const MockTexture::PaintCall& b); +extern std::ostream& operator<<(std::ostream& os, + const MockTexture::PaintCall& data); + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_texture_unittests.cc b/flow/testing/mock_texture_unittests.cc new file mode 100644 index 0000000000000..107eb76307928 --- /dev/null +++ b/flow/testing/mock_texture_unittests.cc @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/testing/mock_texture.h" + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(MockTextureTest, Callbacks) { + auto texture = std::make_shared(0); + + ASSERT_FALSE(texture->gr_context_created()); + texture->OnGrContextCreated(); + ASSERT_TRUE(texture->gr_context_created()); + + ASSERT_FALSE(texture->gr_context_destroyed()); + texture->OnGrContextDestroyed(); + ASSERT_TRUE(texture->gr_context_destroyed()); + + ASSERT_FALSE(texture->unregistered()); + texture->OnTextureUnregistered(); + ASSERT_TRUE(texture->unregistered()); +} + +TEST(MockTextureTest, PaintCalls) { + SkCanvas canvas; + const SkRect paint_bounds1 = SkRect::MakeWH(1.0f, 1.0f); + const SkRect paint_bounds2 = SkRect::MakeWH(2.0f, 2.0f); + const auto expected_paint_calls = + std::vector{MockTexture::PaintCall{canvas, paint_bounds1, false, nullptr}, + MockTexture::PaintCall{canvas, paint_bounds2, true, nullptr}}; + auto texture = std::make_shared(0); + + texture->Paint(canvas, paint_bounds1, false, nullptr); + texture->Paint(canvas, paint_bounds2, true, nullptr); + EXPECT_EQ(texture->paint_calls(), expected_paint_calls); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/skia_gpu_object_layer_test.cc b/flow/testing/skia_gpu_object_layer_test.cc new file mode 100644 index 0000000000000..1fca8ec8f3b81 --- /dev/null +++ b/flow/testing/skia_gpu_object_layer_test.cc @@ -0,0 +1,18 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/testing/skia_gpu_object_layer_test.h" + +#include "flutter/fml/time/time_delta.h" + +namespace flutter { +namespace testing { + +SkiaGPUObjectLayerTest::SkiaGPUObjectLayerTest() + : unref_queue_(fml::MakeRefCounted( + GetCurrentTaskRunner(), + fml::TimeDelta::FromSeconds(0))) {} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/skia_gpu_object_layer_test.h b/flow/testing/skia_gpu_object_layer_test.h new file mode 100644 index 0000000000000..d573ac0b41007 --- /dev/null +++ b/flow/testing/skia_gpu_object_layer_test.h @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLOW_TESTING_SKIA_GPU_OBJECT_LAYER_TEST_H_ +#define FLOW_TESTING_SKIA_GPU_OBJECT_LAYER_TEST_H_ + +#include "flutter/flow/skia_gpu_object.h" +#include "flutter/flow/testing/layer_test.h" +#include "flutter/testing/thread_test.h" + +namespace flutter { +namespace testing { + +// This fixture allows generating tests that create |SkiaGPUObject|'s which +// are destroyed on a |SkiaUnrefQueue|. +class SkiaGPUObjectLayerTest : public LayerTestBase { + public: + SkiaGPUObjectLayerTest(); + + fml::RefPtr unref_queue() { return unref_queue_; } + + private: + fml::RefPtr unref_queue_; +}; + +} // namespace testing +} // namespace flutter + +#endif // FLOW_TESTING_SKIA_GPU_OBJECT_LAYER_TEST_H_ diff --git a/flow/texture.cc b/flow/texture.cc index 6f25c6df89593..15c93d360366e 100644 --- a/flow/texture.cc +++ b/flow/texture.cc @@ -6,9 +6,11 @@ namespace flutter { -TextureRegistry::TextureRegistry() = default; +Texture::Texture(int64_t id) : id_(id) {} -TextureRegistry::~TextureRegistry() = default; +Texture::~Texture() = default; + +TextureRegistry::TextureRegistry() = default; void TextureRegistry::RegisterTexture(std::shared_ptr texture) { mapping_[texture->Id()] = texture; @@ -36,8 +38,4 @@ std::shared_ptr TextureRegistry::GetTexture(int64_t id) { return it != mapping_.end() ? it->second : nullptr; } -Texture::Texture(int64_t id) : id_(id) {} - -Texture::~Texture() = default; - } // namespace flutter diff --git a/flow/texture.h b/flow/texture.h index 6e06445884b66..812588d382bb1 100644 --- a/flow/texture.h +++ b/flow/texture.h @@ -14,12 +14,9 @@ namespace flutter { class Texture { - protected: - Texture(int64_t id); - public: - // Called from GPU thread. - virtual ~Texture(); + Texture(int64_t id); // Called from UI or GPU thread. + virtual ~Texture(); // Called from GPU thread. // Called from GPU thread. virtual void Paint(SkCanvas& canvas, @@ -50,7 +47,6 @@ class Texture { class TextureRegistry { public: TextureRegistry(); - ~TextureRegistry(); // Called from GPU thread. void RegisterTexture(std::shared_ptr texture); diff --git a/flow/texture_unittests.cc b/flow/texture_unittests.cc index d292e3965af87..f3eb0fc0931ac 100644 --- a/flow/texture_unittests.cc +++ b/flow/texture_unittests.cc @@ -2,44 +2,97 @@ // 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_texture.h" #include "flutter/flow/texture.h" + #include "gtest/gtest.h" namespace flutter { namespace testing { -class MockTexture : public Texture { - public: - MockTexture(int64_t textureId) : Texture(textureId) {} +TEST(TextureRegistryTest, UnregisterTextureCallbackTriggered) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto 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(TextureRegistryTest, GrContextCallbackTriggered) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto 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()); - ~MockTexture() override = default; + registry.OnGrContextCreated(); + ASSERT_TRUE(mock_texture1->gr_context_created()); + ASSERT_TRUE(mock_texture2->gr_context_created()); - // Called from GPU thread. - void Paint(SkCanvas& canvas, - const SkRect& bounds, - bool freeze, - GrContext* context) override {} + registry.UnregisterTexture(0); + registry.OnGrContextDestroyed(); + ASSERT_FALSE(mock_texture1->gr_context_destroyed()); + ASSERT_TRUE(mock_texture2->gr_context_created()); +} - void OnGrContextCreated() override {} +TEST(TextureRegistryTest, RegisterTextureTwice) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto mock_texture2 = std::make_shared(0); - void OnGrContextDestroyed() override {} + 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()); +} - void MarkNewFrameAvailable() override {} +TEST(TextureRegistryTest, ReuseSameTextureSlot) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto mock_texture2 = std::make_shared(0); - void OnTextureUnregistered() override { unregistered_ = true; } + registry.RegisterTexture(mock_texture1); + ASSERT_EQ(registry.GetTexture(0), mock_texture1); - bool unregistered() { return unregistered_; } + registry.UnregisterTexture(0); + ASSERT_EQ(registry.GetTexture(0), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); - private: - bool unregistered_ = false; -}; + registry.RegisterTexture(mock_texture2); + ASSERT_EQ(registry.GetTexture(0), mock_texture2); -TEST(TextureRegistry, UnregisterTextureCallbackTriggered) { - TextureRegistry textureRegistry; - std::shared_ptr mockTexture = std::make_shared(0); - textureRegistry.RegisterTexture(mockTexture); - textureRegistry.UnregisterTexture(0); - ASSERT_TRUE(mockTexture->unregistered()); + 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 68b9725fa6b2e..21ce86c58ceed 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/runtime/runtime_test.cc b/runtime/runtime_test.cc index 455a52c95a67a..295d3daf2bc0e 100644 --- a/runtime/runtime_test.cc +++ b/runtime/runtime_test.cc @@ -11,9 +11,10 @@ namespace flutter { namespace testing { RuntimeTest::RuntimeTest() - : native_resolver_(std::make_shared()) {} - -RuntimeTest::~RuntimeTest() = default; + : native_resolver_(std::make_shared()), + assets_dir_(fml::OpenDirectory(GetFixturesPath(), + false, + fml::FilePermission::kRead)) {} void RuntimeTest::SetSnapshotsAndAssets(Settings& settings) { if (!assets_dir_.is_valid()) { @@ -67,19 +68,6 @@ Settings RuntimeTest::CreateSettingsForFixture() { return settings; } -// |testing::ThreadTest| -void RuntimeTest::SetUp() { - assets_dir_ = - fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead); - ThreadTest::SetUp(); -} - -// |testing::ThreadTest| -void RuntimeTest::TearDown() { - ThreadTest::TearDown(); - assets_dir_.reset(); -} - void RuntimeTest::AddNativeCallback(std::string name, Dart_NativeFunction callback) { native_resolver_->AddNativeCallback(std::move(name), callback); diff --git a/runtime/runtime_test.h b/runtime/runtime_test.h index 8c60a6e858a6c..abce2e94970f9 100644 --- a/runtime/runtime_test.h +++ b/runtime/runtime_test.h @@ -19,24 +19,17 @@ class RuntimeTest : public ThreadTest { public: RuntimeTest(); - ~RuntimeTest(); - Settings CreateSettingsForFixture(); void AddNativeCallback(std::string name, Dart_NativeFunction callback); - protected: - // |testing::ThreadTest| - void SetUp() override; - - // |testing::ThreadTest| - void TearDown() override; - private: - fml::UniqueFD assets_dir_; + void SetSnapshotsAndAssets(Settings& settings); + std::shared_ptr native_resolver_; + fml::UniqueFD assets_dir_; - void SetSnapshotsAndAssets(Settings& settings); + FML_DISALLOW_COPY_AND_ASSIGN(RuntimeTest); }; } // namespace testing diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index ed7f1febaf51c..97a382dcacbbd 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -19,9 +19,13 @@ namespace flutter { namespace testing { ShellTest::ShellTest() - : native_resolver_(std::make_shared()) {} - -ShellTest::~ShellTest() = default; + : native_resolver_(std::make_shared()), + thread_host_("io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::Platform | ThreadHost::Type::IO | + ThreadHost::Type::UI | ThreadHost::Type::GPU), + assets_dir_(fml::OpenDirectory(GetFixturesPath(), + false, + fml::FilePermission::kRead)) {} void ShellTest::SendEnginePlatformMessage( Shell* shell, @@ -225,10 +229,10 @@ Settings ShellTest::CreateSettingsForFixture() { TaskRunners ShellTest::GetTaskRunnersForFixture() { return { "test", - thread_host_->platform_thread->GetTaskRunner(), // platform - thread_host_->gpu_thread->GetTaskRunner(), // gpu - thread_host_->ui_thread->GetTaskRunner(), // ui - thread_host_->io_thread->GetTaskRunner() // io + thread_host_.platform_thread->GetTaskRunner(), // platform + thread_host_.gpu_thread->GetTaskRunner(), // gpu + thread_host_.ui_thread->GetTaskRunner(), // ui + thread_host_.io_thread->GetTaskRunner() // io }; } @@ -267,24 +271,6 @@ void ShellTest::DestroyShell(std::unique_ptr shell, latch.Wait(); } -// |testing::ThreadTest| -void ShellTest::SetUp() { - ThreadTest::SetUp(); - assets_dir_ = - fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead); - thread_host_ = std::make_unique( - "io.flutter.test." + GetCurrentTestName() + ".", - ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI | - ThreadHost::Type::GPU); -} - -// |testing::ThreadTest| -void ShellTest::TearDown() { - ThreadTest::TearDown(); - assets_dir_.reset(); - thread_host_.reset(); -} - void ShellTest::AddNativeCallback(std::string name, Dart_NativeFunction callback) { native_resolver_->AddNativeCallback(std::move(name), callback); diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index 270ea812dbf8a..0ae092a1c91bf 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -26,8 +26,6 @@ class ShellTest : public ThreadTest { public: ShellTest(); - ~ShellTest(); - Settings CreateSettingsForFixture(); std::unique_ptr CreateShell(Settings settings, bool simulate_vsync = false); @@ -78,19 +76,14 @@ class ShellTest : public ThreadTest { // is unpredictive. static int UnreportedTimingsCount(Shell* shell); - protected: - // |testing::ThreadTest| - void SetUp() override; - - // |testing::ThreadTest| - void TearDown() override; - private: - fml::UniqueFD assets_dir_; + void SetSnapshotsAndAssets(Settings& settings); + std::shared_ptr native_resolver_; - std::unique_ptr thread_host_; + ThreadHost thread_host_; + fml::UniqueFD assets_dir_; - void SetSnapshotsAndAssets(Settings& settings); + FML_DISALLOW_COPY_AND_ASSIGN(ShellTest); }; class ShellTestVsyncClock { diff --git a/shell/platform/embedder/tests/embedder_test.cc b/shell/platform/embedder/tests/embedder_test.cc index cc699c26e463a..bca2e0eb5f258 100644 --- a/shell/platform/embedder/tests/embedder_test.cc +++ b/shell/platform/embedder/tests/embedder_test.cc @@ -9,14 +9,12 @@ namespace testing { EmbedderTest::EmbedderTest() = default; -EmbedderTest::~EmbedderTest() = default; - std::string EmbedderTest::GetFixturesDirectory() const { return GetFixturesPath(); } EmbedderTestContext& EmbedderTest::GetEmbedderContext() { - // Setup the embedder context lazily instead of in the SetUp method because we + // Setup the embedder context lazily instead of in the constructor because we // don't to do all the work if the test won't end up using context. if (!embedder_context_) { embedder_context_ = @@ -25,16 +23,5 @@ EmbedderTestContext& EmbedderTest::GetEmbedderContext() { return *embedder_context_; } -// |testing::Test| -void EmbedderTest::SetUp() { - ThreadTest::SetUp(); -} - -// |testing::Test| -void EmbedderTest::TearDown() { - embedder_context_.reset(); - ThreadTest::TearDown(); -} - } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test.h b/shell/platform/embedder/tests/embedder_test.h index f170a106553dc..15cc101ae24a6 100644 --- a/shell/platform/embedder/tests/embedder_test.h +++ b/shell/platform/embedder/tests/embedder_test.h @@ -19,8 +19,6 @@ class EmbedderTest : public ThreadTest { public: EmbedderTest(); - ~EmbedderTest() override; - std::string GetFixturesDirectory() const; EmbedderTestContext& GetEmbedderContext(); @@ -28,12 +26,6 @@ class EmbedderTest : public ThreadTest { private: std::unique_ptr embedder_context_; - // |testing::Test| - void SetUp() override; - - // |testing::Test| - void TearDown() override; - FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTest); }; diff --git a/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx index bdfec3cd2e4d0..244761694a8dc 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx @@ -16,6 +16,7 @@ "fuchsia.feedback.CrashReporter", "fuchsia.fonts.Provider", "fuchsia.intl.PropertyProvider", + "fuchsia.logger.LogSink", "fuchsia.net.NameLookup", "fuchsia.netstack.Netstack", "fuchsia.posix.socket.Provider", diff --git a/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx index 912b534df93a2..1119b279ddd6c 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx @@ -16,6 +16,7 @@ "fuchsia.feedback.CrashReporter", "fuchsia.fonts.Provider", "fuchsia.intl.PropertyProvider", + "fuchsia.logger.LogSink", "fuchsia.net.NameLookup", "fuchsia.netstack.Netstack", "fuchsia.posix.socket.Provider", diff --git a/testing/BUILD.gn b/testing/BUILD.gn index a1028b0f18952..8bf9d7bc3922f 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -54,7 +54,11 @@ source_set("skia") { testonly = true sources = [ + "$flutter_root/testing/assertions_skia.cc", "$flutter_root/testing/assertions_skia.h", + "$flutter_root/testing/canvas_test.h", + "$flutter_root/testing/mock_canvas.cc", + "$flutter_root/testing/mock_canvas.h", ] public_deps = [ @@ -118,7 +122,8 @@ if (current_toolchain == host_toolchain) { testonly = true sources = [ - "$flutter_root/testing/test_metal_surface_unittests.cc", + "mock_canvas_unittests.cc", + "test_metal_surface_unittests.cc", ] deps = [ diff --git a/testing/assertions_skia.cc b/testing/assertions_skia.cc new file mode 100644 index 0000000000000..e8b7ce992b8a8 --- /dev/null +++ b/testing/assertions_skia.cc @@ -0,0 +1,118 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/testing/assertions_skia.h" + +namespace flutter { +namespace testing { + +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; +} + +std::ostream& operator<<(std::ostream& os, const SkMatrix& m) { + os << std::endl; + os << "Scale X: " << m[SkMatrix::kMScaleX] << ", "; + os << "Skew X: " << m[SkMatrix::kMSkewX] << ", "; + os << "Trans X: " << m[SkMatrix::kMTransX] << std::endl; + os << "Skew Y: " << m[SkMatrix::kMSkewY] << ", "; + os << "Scale Y: " << m[SkMatrix::kMScaleY] << ", "; + os << "Trans Y: " << m[SkMatrix::kMTransY] << std::endl; + os << "Persp X: " << m[SkMatrix::kMPersp0] << ", "; + os << "Persp Y: " << m[SkMatrix::kMPersp1] << ", "; + os << "Persp Z: " << m[SkMatrix::kMPersp2]; + os << std::endl; + return os; +} + +std::ostream& operator<<(std::ostream& os, const SkMatrix44& m) { + os << m.get(0, 0) << ", " << m.get(0, 1) << ", " << m.get(0, 2) << ", " + << m.get(0, 3) << std::endl; + os << m.get(1, 0) << ", " << m.get(1, 1) << ", " << m.get(1, 2) << ", " + << m.get(1, 3) << std::endl; + os << m.get(2, 0) << ", " << m.get(2, 1) << ", " << m.get(2, 2) << ", " + << m.get(2, 3) << std::endl; + os << m.get(3, 0) << ", " << m.get(3, 1) << ", " << m.get(3, 2) << ", " + << m.get(3, 3); + return os; +} + +std::ostream& operator<<(std::ostream& os, const SkVector3& v) { + return os << v.x() << ", " << v.y() << ", " << v.z(); +} + +std::ostream& operator<<(std::ostream& os, const SkVector4& v) { + return os << v.fData[0] << ", " << v.fData[1] << ", " << v.fData[2] << ", " + << v.fData[3]; +} + +std::ostream& operator<<(std::ostream& os, const SkRect& r) { + return os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", " + << r.fBottom; +} + +std::ostream& operator<<(std::ostream& os, const SkRRect& r) { + return os << "LTRB: " << r.rect().fLeft << ", " << r.rect().fTop << ", " + << r.rect().fRight << ", " << r.rect().fBottom; +} + +std::ostream& operator<<(std::ostream& os, const SkPath& r) { + return os << "Valid: " << r.isValid() << ", FillType: " << r.getFillType() + << ", Bounds: " << r.getBounds(); +} + +std::ostream& operator<<(std::ostream& os, const SkPoint& r) { + return os << "XY: " << r.fX << ", " << r.fY; +} + +std::ostream& operator<<(std::ostream& os, const SkISize& size) { + return os << size.width() << ", " << size.height(); +} + +std::ostream& operator<<(std::ostream& os, const SkColor4f& r) { + return os << r.fR << ", " << r.fG << ", " << r.fB << ", " << r.fA; +} + +std::ostream& operator<<(std::ostream& os, const SkPaint& r) { + return os << "Color: " << r.getColor4f() << ", Style: " << r.getStyle() + << ", AA: " << r.isAntiAlias() << ", Shader: " << r.getShader(); +} + +} // namespace testing +} // namespace flutter diff --git a/testing/assertions_skia.h b/testing/assertions_skia.h index 2b501189a23ae..f1eec1897c426 100644 --- a/testing/assertions_skia.h +++ b/testing/assertions_skia.h @@ -7,73 +7,31 @@ #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" -//------------------------------------------------------------------------------ -// Printing -//------------------------------------------------------------------------------ - -inline std::ostream& operator<<(std::ostream& os, const SkMatrix& m) { - os << std::endl; - os << "Scale X: " << m[SkMatrix::kMScaleX] << ", "; - os << "Skew X: " << m[SkMatrix::kMSkewX] << ", "; - os << "Trans X: " << m[SkMatrix::kMTransX] << std::endl; - os << "Skew Y: " << m[SkMatrix::kMSkewY] << ", "; - os << "Scale Y: " << m[SkMatrix::kMScaleY] << ", "; - os << "Trans Y: " << m[SkMatrix::kMTransY] << std::endl; - os << "Persp X: " << m[SkMatrix::kMPersp0] << ", "; - os << "Persp Y: " << m[SkMatrix::kMPersp1] << ", "; - os << "Persp Z: " << m[SkMatrix::kMPersp2]; - os << std::endl; - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const SkMatrix44& m) { - os << m.get(0, 0) << ", " << m.get(0, 1) << ", " << m.get(0, 2) << ", " - << m.get(0, 3) << std::endl; - os << m.get(1, 0) << ", " << m.get(1, 1) << ", " << m.get(1, 2) << ", " - << m.get(1, 3) << std::endl; - os << m.get(2, 0) << ", " << m.get(2, 1) << ", " << m.get(2, 2) << ", " - << m.get(2, 3) << std::endl; - os << m.get(3, 0) << ", " << m.get(3, 1) << ", " << m.get(3, 2) << ", " - << m.get(3, 3); - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const SkVector3& v) { - os << v.x() << ", " << v.y() << ", " << v.z(); - return os; -} - -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; -} - -inline std::ostream& operator<<(std::ostream& os, const SkRect& r) { - os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", " - << r.fBottom; - return os; -} - -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; -} - -inline std::ostream& operator<<(std::ostream& os, const SkPoint& r) { - os << "XY: " << r.fX << ", " << r.fY; - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const SkISize& size) { - os << size.width() << ", " << size.height(); - return os; -} +namespace flutter { +namespace testing { + +extern std::ostream& operator<<(std::ostream& os, const SkClipOp& o); +extern std::ostream& operator<<(std::ostream& os, const SkMatrix& m); +extern std::ostream& operator<<(std::ostream& os, const SkMatrix44& m); +extern std::ostream& operator<<(std::ostream& os, const SkVector3& v); +extern std::ostream& operator<<(std::ostream& os, const SkVector4& v); +extern std::ostream& operator<<(std::ostream& os, const SkRect& r); +extern std::ostream& operator<<(std::ostream& os, const SkRRect& r); +extern std::ostream& operator<<(std::ostream& os, const SkPath& r); +extern std::ostream& operator<<(std::ostream& os, const SkPoint& r); +extern std::ostream& operator<<(std::ostream& os, const SkISize& size); +extern std::ostream& operator<<(std::ostream& os, const SkColor4f& r); +extern std::ostream& operator<<(std::ostream& os, const SkPaint& r); + +} // namespace testing +} // namespace flutter #endif // FLUTTER_TESTING_ASSERTIONS_SKIA_H_ diff --git a/testing/canvas_test.h b/testing/canvas_test.h new file mode 100644 index 0000000000000..80c157a305dc4 --- /dev/null +++ b/testing/canvas_test.h @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TESTING_CANVAS_TEST_H_ +#define TESTING_CANVAS_TEST_H_ + +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +// This fixture allows creating tests that make use of a mock |SkCanvas|. +template +class CanvasTestBase : public BaseT { + public: + CanvasTestBase() = default; + + MockCanvas& mock_canvas() { return canvas_; } + + private: + MockCanvas canvas_; + + FML_DISALLOW_COPY_AND_ASSIGN(CanvasTestBase); +}; +using CanvasTest = CanvasTestBase<::testing::Test>; + +} // namespace testing +} // namespace flutter + +#endif // TESTING_CANVAS_TEST_H_ diff --git a/testing/mock_canvas.cc b/testing/mock_canvas.cc new file mode 100644 index 0000000000000..6782ffdeda733 --- /dev/null +++ b/testing/mock_canvas.cc @@ -0,0 +1,457 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/testing/mock_canvas.h" + +#include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkSerialProcs.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/core/SkTextBlob.h" + +namespace flutter { +namespace testing { + +constexpr SkISize kSize = SkISize::Make(64, 64); + +MockCanvas::MockCanvas() + : SkCanvasVirtualEnforcer(kSize.fWidth, kSize.fHeight), + internal_canvas_(imageInfo().width(), imageInfo().height()), + current_layer_(0) { + internal_canvas_.addCanvas(this); +} + +MockCanvas::~MockCanvas() { + EXPECT_EQ(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::onDrawTextBlob(const SkTextBlob* text, + SkScalar x, + SkScalar y, + const SkPaint& paint) { + // This duplicates existing logic in SkCanvas::onDrawPicture + // that should probably be split out so it doesn't need to be here as well. + SkRect storage; + const SkRect* bounds = nullptr; + if (paint.canComputeFastBounds()) { + storage = text->bounds().makeOffset(x, y); + SkRect tmp; + if (this->quickReject(paint.computeFastBounds(storage, &tmp))) { + return; + } + bounds = &storage; + } + + draw_calls_.emplace_back(DrawCall{ + current_layer_, DrawTextData{text ? text->serialize(SkSerialProcs{}) + : SkData::MakeUninitialized(0), + paint, SkPoint::Make(x, y)}}); +} + +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) { + // This duplicates existing logic in SkCanvas::onDrawPicture + // that should probably be split out so it doesn't need to be here as well. + 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{ + picture ? picture->serialize() : SkData::MakeUninitialized(0), + paint ? *paint : SkPaint(), matrix ? *matrix : SkMatrix()}}); +} + +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::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::onDrawImage(const SkImage*, + SkScalar, + SkScalar, + const SkPaint*) { + FML_DCHECK(false); +} + +void MockCanvas::onDrawBitmapRect(const SkBitmap&, + const SkRect*, + const SkRect&, + const SkPaint*, + SrcRectConstraint) { + 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); +} + +bool operator==(const MockCanvas::SaveData& a, const MockCanvas::SaveData& b) { + return a.save_to_layer == b.save_to_layer; +} + +std::ostream& operator<<(std::ostream& os, const MockCanvas::SaveData& data) { + return os << data.save_to_layer; +} + +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; +} + +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; +} + +bool operator==(const MockCanvas::RestoreData& a, + const MockCanvas::RestoreData& b) { + return a.restore_to_layer == b.restore_to_layer; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::RestoreData& data) { + return os << data.restore_to_layer; +} + +bool operator==(const MockCanvas::ConcatMatrixData& a, + const MockCanvas::ConcatMatrixData& b) { + return a.matrix == b.matrix; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::ConcatMatrixData& data) { + return os << data.matrix; +} + +bool operator==(const MockCanvas::SetMatrixData& a, + const MockCanvas::SetMatrixData& b) { + return a.matrix == b.matrix; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::SetMatrixData& data) { + return os << data.matrix; +} + +bool operator==(const MockCanvas::DrawRectData& a, + const MockCanvas::DrawRectData& b) { + return a.rect == b.rect && a.paint == b.paint; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawRectData& data) { + return os << data.rect << " " << data.paint; +} + +bool operator==(const MockCanvas::DrawPathData& a, + const MockCanvas::DrawPathData& b) { + return a.path == b.path && a.paint == b.paint; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawPathData& data) { + return os << data.path << " " << data.paint; +} + +bool operator==(const MockCanvas::DrawTextData& a, + const MockCanvas::DrawTextData& b) { + return a.serialized_text->equals(b.serialized_text.get()) && + a.paint == b.paint && a.offset == b.offset; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawTextData& data) { + return os << data.serialized_text << " " << data.paint << " " << data.offset; +} + +bool operator==(const MockCanvas::DrawPictureData& a, + const MockCanvas::DrawPictureData& b) { + return a.serialized_picture->equals(b.serialized_picture.get()) && + a.paint == b.paint && a.matrix == b.matrix; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawPictureData& data) { + return os << data.serialized_picture << " " << data.paint << " " + << data.matrix; +} + +bool operator==(const MockCanvas::DrawShadowData& a, + const MockCanvas::DrawShadowData& b) { + return a.path == b.path; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawShadowData& data) { + return os << data.path; +} + +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; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipRectData& data) { + return os << data.rect << " " << data.clip_op << " " << data.style; +} + +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; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipRRectData& data) { + return os << data.rrect << " " << data.clip_op << " " << data.style; +} + +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; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipPathData& data) { + return os << data.path << " " << data.clip_op << " " << data.style; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawCallData& data) { + std::visit([&os](auto& d) { os << d; }, data); + return os; +} + +bool operator==(const MockCanvas::DrawCall& a, const MockCanvas::DrawCall& b) { + return a.layer == b.layer && a.data == b.data; +} + +std::ostream& operator<<(std::ostream& os, const MockCanvas::DrawCall& draw) { + return os << "[Layer: " << draw.layer << ", Data: " << draw.data << "]"; +} + +} // namespace testing +} // namespace flutter diff --git a/testing/mock_canvas.h b/testing/mock_canvas.h new file mode 100644 index 0000000000000..cc4c2b11a9c2e --- /dev/null +++ b/testing/mock_canvas.h @@ -0,0 +1,318 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TESTING_MOCK_CANVAS_H_ +#define TESTING_MOCK_CANVAS_H_ + +#include +#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/SkData.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkPath.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 { + +static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); + +// Mock |SkCanvas|, useful for writing tests that use Skia but do not interact +// with the GPU. +// +// The |MockCanvas| stores a list of |DrawCall| that the test can later verify +// against the expected list of primitives to be drawn. +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 DrawTextData { + sk_sp serialized_text; + SkPaint paint; + SkPoint offset; + }; + + struct DrawPictureData { + sk_sp serialized_picture; + SkPaint paint; + SkMatrix matrix; + }; + + 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; + }; + + // Discriminated union of all the different |DrawCall| types. It is roughly + // equivalent to the different methods in |SkCanvas|' public API. + using DrawCallData = std::variant; + + // A single call made against this canvas. + struct DrawCall { + int layer; + DrawCallData data; + }; + + MockCanvas(); + ~MockCanvas() override; + + SkNWayCanvas* internal_canvas() { return &internal_canvas_; } + + 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 onDrawTextBlob(const SkTextBlob* text, + SkScalar x, + SkScalar y, + 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 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 onDrawBitmapRect(const SkBitmap&, + const SkRect*, + const SkRect&, + const SkPaint*, + SrcRectConstraint) override; + void onDrawImage(const SkImage* image, + SkScalar x, + SkScalar y, + const SkPaint* paint) 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 onDrawBitmap(const SkBitmap& bitmap, + SkScalar x, + SkScalar y, + const SkPaint* paint) 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_; +}; + +extern bool operator==(const MockCanvas::SaveData& a, + const MockCanvas::SaveData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::SaveData& data); +extern bool operator==(const MockCanvas::SaveLayerData& a, + const MockCanvas::SaveLayerData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::SaveLayerData& data); +extern bool operator==(const MockCanvas::RestoreData& a, + const MockCanvas::RestoreData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::RestoreData& data); +extern bool operator==(const MockCanvas::ConcatMatrixData& a, + const MockCanvas::ConcatMatrixData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::ConcatMatrixData& data); +extern bool operator==(const MockCanvas::SetMatrixData& a, + const MockCanvas::SetMatrixData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::SetMatrixData& data); +extern bool operator==(const MockCanvas::DrawRectData& a, + const MockCanvas::DrawRectData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawRectData& data); +extern bool operator==(const MockCanvas::DrawPathData& a, + const MockCanvas::DrawPathData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawPathData& data); +extern bool operator==(const MockCanvas::DrawTextData& a, + const MockCanvas::DrawTextData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawTextData& data); +extern bool operator==(const MockCanvas::DrawPictureData& a, + const MockCanvas::DrawPictureData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawPictureData& data); +extern bool operator==(const MockCanvas::DrawShadowData& a, + const MockCanvas::DrawShadowData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawShadowData& data); +extern bool operator==(const MockCanvas::ClipRectData& a, + const MockCanvas::ClipRectData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipRectData& data); +extern bool operator==(const MockCanvas::ClipRRectData& a, + const MockCanvas::ClipRRectData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipRRectData& data); +extern bool operator==(const MockCanvas::ClipPathData& a, + const MockCanvas::ClipPathData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipPathData& data); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawCallData& data); +extern bool operator==(const MockCanvas::DrawCall& a, + const MockCanvas::DrawCall& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawCall& draw); + +} // namespace testing +} // namespace flutter + +#endif // TESTING_MOCK_CANVAS_H_ diff --git a/testing/mock_canvas_unittests.cc b/testing/mock_canvas_unittests.cc new file mode 100644 index 0000000000000..7e8d624642064 --- /dev/null +++ b/testing/mock_canvas_unittests.cc @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/testing/mock_canvas.h" + +#include "flutter/testing/canvas_test.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +using MockCanvasTest = CanvasTest; + +#ifndef NDEBUG +TEST_F(MockCanvasTest, DrawRRectDies) { + EXPECT_DEATH_IF_SUPPORTED(mock_canvas().drawRRect(SkRRect(), SkPaint()), ""); +} +#endif + +TEST_F(MockCanvasTest, DrawCalls) { + const SkRect rect = SkRect::MakeWH(5.0f, 5.0f); + const SkPaint paint = SkPaint(SkColors::kGreen); + const auto expected_draw_calls = std::vector{ + MockCanvas::DrawCall{0, MockCanvas::DrawRectData{rect, paint}}}; + + mock_canvas().drawRect(rect, paint); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +} // namespace testing +} // namespace flutter diff --git a/testing/thread_test.cc b/testing/thread_test.cc index 88415169a3c70..2f67b6ee18368 100644 --- a/testing/thread_test.cc +++ b/testing/thread_test.cc @@ -8,18 +8,16 @@ namespace flutter { namespace testing { +namespace { -// |testing::Test| -void ThreadTest::SetUp() { +fml::RefPtr GetDefaultTaskRunner() { fml::MessageLoop::EnsureInitializedForCurrentThread(); - current_task_runner_ = fml::MessageLoop::GetCurrent().GetTaskRunner(); + return fml::MessageLoop::GetCurrent().GetTaskRunner(); } -// |testing::Test| -void ThreadTest::TearDown() { - current_task_runner_ = nullptr; - extra_threads_.clear(); -} +} // namespace + +ThreadTest::ThreadTest() : current_task_runner_(GetDefaultTaskRunner()) {} fml::RefPtr ThreadTest::GetCurrentTaskRunner() { return current_task_runner_; diff --git a/testing/thread_test.h b/testing/thread_test.h index 8c55dbf80ce68..4a7d60fb0312c 100644 --- a/testing/thread_test.h +++ b/testing/thread_test.h @@ -21,14 +21,16 @@ namespace testing { /// @brief A fixture that creates threads with running message loops that /// are terminated when the test is done (the threads are joined /// then as well). While this fixture may be used on it's own, it is -/// often sub-classed but other fixtures whose functioning requires +/// often sub-classed by other fixtures whose functioning requires /// threads to be created as necessary. /// class ThreadTest : public ::testing::Test { public: + ThreadTest(); + //---------------------------------------------------------------------------- /// @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,16 +58,11 @@ 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_; + + FML_DISALLOW_COPY_AND_ASSIGN(ThreadTest); }; } // namespace testing