diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index da4791076b283..af92cf36e27df 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -777,6 +777,9 @@ FILE: ../../../flutter/flow/layers/layer.cc FILE: ../../../flutter/flow/layers/layer.h FILE: ../../../flutter/flow/layers/layer_raster_cache_item.cc FILE: ../../../flutter/flow/layers/layer_raster_cache_item.h +FILE: ../../../flutter/flow/layers/layer_state_stack.cc +FILE: ../../../flutter/flow/layers/layer_state_stack.h +FILE: ../../../flutter/flow/layers/layer_state_stack_unittests.cc FILE: ../../../flutter/flow/layers/layer_tree.cc FILE: ../../../flutter/flow/layers/layer_tree.h FILE: ../../../flutter/flow/layers/layer_tree_unittests.cc diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc index 11a990e27f404..c2d44f682bc30 100644 --- a/display_list/display_list_builder.cc +++ b/display_list/display_list_builder.cc @@ -709,6 +709,21 @@ SkRect DisplayListBuilder::getLocalClipBounds() { return kMaxCullRect_; } +bool DisplayListBuilder::quickReject(const SkRect& bounds) const { + if (bounds.isEmpty()) { + return true; + } + SkMatrix matrix = getTransform(); + // We don't need the inverse, but this method tells us if the matrix + // is singular in which case we can reject all rendering. + if (!matrix.invert(nullptr)) { + return true; + } + SkRect dev_bounds; + matrix.mapRect(bounds).roundOut(&dev_bounds); + return !current_layer_->clip_bounds.intersects(dev_bounds); +} + void DisplayListBuilder::drawPaint() { Push(0, 1); CheckLayerOpacityCompatibility(); diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h index 8bcc3ffa56db6..3b575153d7ded 100644 --- a/display_list/display_list_builder.h +++ b/display_list/display_list_builder.h @@ -206,7 +206,7 @@ class DisplayListBuilder final : public virtual Dispatcher, /// Returns the 3x3 partial perspective transform representing all transform /// operations executed so far in this DisplayList within the enclosing /// save stack. - SkMatrix getTransform() { return current_layer_->matrix.asM33(); } + SkMatrix getTransform() const { return current_layer_->matrix.asM33(); } void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override; void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override; @@ -221,6 +221,11 @@ class DisplayListBuilder final : public virtual Dispatcher, /// recorded rendering operations are interpreted. SkRect getLocalClipBounds(); + /// Return true iff the supplied bounds are easily shown to be outside + /// of the current clip bounds. This method may conservatively return + /// false if it cannot make the determination. + bool quickReject(const SkRect& bounds) const; + void drawPaint() override; void drawPaint(const DlPaint& paint); void drawColor(DlColor color, DlBlendMode mode) override; diff --git a/display_list/display_list_canvas_unittests.cc b/display_list/display_list_canvas_unittests.cc index 0c285c1fde4e4..a1df7d6e8942b 100644 --- a/display_list/display_list_canvas_unittests.cc +++ b/display_list/display_list_canvas_unittests.cc @@ -2040,6 +2040,9 @@ class CanvasCompareTester { const uint32_t* test_row = test_pixels->addr32(0, y); for (int x = 0; x < test_pixels->width(); x++) { if (ref_row[x] != test_row[x]) { + if (should_match) { + FML_LOG(ERROR) << std::hex << ref_row[x] << " != " << test_row[x]; + } pixels_different++; } } @@ -3238,5 +3241,175 @@ TEST_F(DisplayListCanvas, DrawShadowDpr) { CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3)); } +TEST_F(DisplayListCanvas, SaveLayerConsolidation) { + float commutable_color_matrix[]{ + // clang-format off + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + // clang-format on + }; + float non_commutable_color_matrix[]{ + // clang-format off + 0, 1, 0, .1, 0, + 0, 0, 1, .1, 0, + 1, 0, 0, .1, 0, + 0, 0, 0, .7, 0, + // clang-format on + }; + SkMatrix contract_matrix; + contract_matrix.setScale(0.9f, 0.9f, kRenderCenterX, kRenderCenterY); + + std::vector opacities = { + 0, + 0.5f, + SK_Scalar1, + }; + std::vector> color_filters = { + std::make_shared(DlColor::kCyan(), + DlBlendMode::kSrcATop), + std::make_shared(commutable_color_matrix), + std::make_shared(non_commutable_color_matrix), + DlSrgbToLinearGammaColorFilter::instance, + DlLinearToSrgbGammaColorFilter::instance, + }; + std::vector> image_filters = { + std::make_shared(5.0f, 5.0f, DlTileMode::kDecal), + std::make_shared(5.0f, 5.0f), + std::make_shared(5.0f, 5.0f), + std::make_shared(contract_matrix, + DlImageSampling::kLinear), + }; + RenderEnvironment env = RenderEnvironment::MakeN32(); + + auto render_content = [](DisplayListBuilder& builder) { + builder.drawRect( + SkRect{kRenderLeft, kRenderTop, kRenderCenterX, kRenderCenterY}, + DlPaint().setColor(DlColor::kYellow())); + builder.drawRect( + SkRect{kRenderCenterX, kRenderTop, kRenderRight, kRenderCenterY}, + DlPaint().setColor(DlColor::kRed())); + builder.drawRect( + SkRect{kRenderLeft, kRenderCenterY, kRenderCenterX, kRenderBottom}, + DlPaint().setColor(DlColor::kBlue())); + builder.drawRect( + SkRect{kRenderCenterX, kRenderCenterY, kRenderRight, kRenderBottom}, + DlPaint().setColor(DlColor::kRed().modulateOpacity(0.5f))); + }; + + // clang-format off + auto test_attributes = [&env, render_content] + (DlPaint& paint1, DlPaint& paint2, DlPaint paint_both, + bool same, bool rev_same, std::string desc1, std::string desc2) { + // clang-format on + DisplayListBuilder nested_builder; + nested_builder.saveLayer(&kTestBounds, &paint1); + nested_builder.saveLayer(&kTestBounds, &paint2); + render_content(nested_builder); + auto nested_surface = env.MakeSurface(); + nested_builder.Build()->RenderTo(nested_surface->canvas()); + + DisplayListBuilder reverse_builder; + reverse_builder.saveLayer(&kTestBounds, &paint2); + reverse_builder.saveLayer(&kTestBounds, &paint1); + render_content(reverse_builder); + auto reverse_surface = env.MakeSurface(); + reverse_builder.Build()->RenderTo(reverse_surface->canvas()); + + DisplayListBuilder combined_builder; + combined_builder.saveLayer(&kTestBounds, &paint_both); + render_content(combined_builder); + auto combined_surface = env.MakeSurface(); + combined_builder.Build()->RenderTo(combined_surface->canvas()); + + // Set this boolean to true to test if combinations that are marked + // as incompatible actually are compatible despite our predictions. + // Some of the combinations that we treat as incompatible actually + // are compatible with swapping the order of the operations, but + // it would take a bit of new infrastructure to really identify + // those combinations. The only hard constraint to test here is + // when we claim that they are compatible and they aren't. + const bool always = false; + + if (always || same) { + CanvasCompareTester::quickCompareToReference( + nested_surface->pixmap(), combined_surface->pixmap(), same, + "nested " + desc1 + " then " + desc2); + } + if (always || rev_same) { + CanvasCompareTester::quickCompareToReference( + reverse_surface->pixmap(), combined_surface->pixmap(), rev_same, + "nested " + desc2 + " then " + desc1); + } + }; + + // CF then Opacity should always work. + // The reverse sometimes works. + for (size_t cfi = 0; cfi < color_filters.size(); cfi++) { + auto color_filter = color_filters[cfi]; + std::string cf_desc = "color filter #" + std::to_string(cfi + 1); + DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter); + + for (size_t oi = 0; oi < opacities.size(); oi++) { + SkScalar opacity = opacities[oi]; + std::string op_desc = "opacity " + std::to_string(opacity); + DlPaint nested_paint2 = DlPaint().setOpacity(opacity); + + DlPaint combined_paint = nested_paint1; + combined_paint.setOpacity(opacity); + + bool op_then_cf_works = opacity <= 0.0 || opacity >= 1.0 || + color_filter->can_commute_with_opacity(); + + test_attributes(nested_paint1, nested_paint2, combined_paint, true, + op_then_cf_works, cf_desc, op_desc); + } + } + + // Opacity then IF should always work. + // The reverse can also work for some values of opacity. + // The reverse should also theoretically work for some IFs, but we + // get some rounding errors that are more than just trivial. + for (size_t oi = 0; oi < opacities.size(); oi++) { + SkScalar opacity = opacities[oi]; + std::string op_desc = "opacity " + std::to_string(opacity); + DlPaint nested_paint1 = DlPaint().setOpacity(opacity); + + for (size_t ifi = 0; ifi < image_filters.size(); ifi++) { + auto image_filter = image_filters[ifi]; + std::string if_desc = "image filter #" + std::to_string(ifi + 1); + DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter); + + DlPaint combined_paint = nested_paint1; + combined_paint.setImageFilter(image_filter); + + bool if_then_op_works = opacity <= 0.0 || opacity >= 1.0; + test_attributes(nested_paint1, nested_paint2, combined_paint, true, + if_then_op_works, op_desc, if_desc); + } + } + + // CF then IF should always work. + // The reverse might work, but we lack the infrastructure to check it. + for (size_t cfi = 0; cfi < color_filters.size(); cfi++) { + auto color_filter = color_filters[cfi]; + std::string cf_desc = "color filter #" + std::to_string(cfi + 1); + DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter); + + for (size_t ifi = 0; ifi < image_filters.size(); ifi++) { + auto image_filter = image_filters[ifi]; + std::string if_desc = "image filter #" + std::to_string(ifi + 1); + DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter); + + DlPaint combined_paint = nested_paint1; + combined_paint.setImageFilter(image_filter); + + test_attributes(nested_paint1, nested_paint2, combined_paint, true, false, + cf_desc, if_desc); + } + } +} + } // namespace testing } // namespace flutter diff --git a/display_list/display_list_color.h b/display_list/display_list_color.h index 38ad332bd36bf..d84ccd4342a93 100644 --- a/display_list/display_list_color.h +++ b/display_list/display_list_color.h @@ -14,6 +14,9 @@ struct DlColor { constexpr DlColor() : argb(0xFF000000) {} constexpr DlColor(uint32_t argb) : argb(argb) {} + static constexpr uint8_t toAlpha(SkScalar opacity) { return toC(opacity); } + static constexpr SkScalar toOpacity(uint8_t alpha) { return toF(alpha); } + // clang-format off static constexpr DlColor kTransparent() {return 0x00000000;}; static constexpr DlColor kBlack() {return 0xFF000000;}; diff --git a/display_list/display_list_color_filter.h b/display_list/display_list_color_filter.h index 7f03fbf5cb1df..ee536b30eb215 100644 --- a/display_list/display_list_color_filter.h +++ b/display_list/display_list_color_filter.h @@ -57,6 +57,11 @@ class DlColorFilter // pixels non-transparent and therefore expand the bounds. virtual bool modifies_transparent_black() const = 0; + // Return a boolean indicating whether the color filtering operation can + // be applied either before or after modulating the pixels with an opacity + // value without changing the operation. + virtual bool can_commute_with_opacity() const { return false; } + // Return a DlBlendColorFilter pointer to this object iff it is a Blend // type of ColorFilter, otherwise return nullptr. virtual const DlBlendColorFilter* asBlend() const { return nullptr; } @@ -150,6 +155,12 @@ class DlMatrixColorFilter final : public DlColorFilter { sk_filter->filterColor(SK_ColorTRANSPARENT) != SK_ColorTRANSPARENT; } + bool can_commute_with_opacity() const override { + return matrix_[3] == 0 && matrix_[8] == 0 && matrix_[13] == 0 && + matrix_[15] == 0 && matrix_[16] == 0 && matrix_[17] == 0 && + (matrix_[18] >= 0.0 && matrix_[18] <= 1.0) && matrix_[19] == 0; + } + std::shared_ptr shared() const override { return std::make_shared(this); } @@ -193,6 +204,7 @@ class DlSrgbToLinearGammaColorFilter final : public DlColorFilter { } size_t size() const override { return sizeof(*this); } bool modifies_transparent_black() const override { return false; } + bool can_commute_with_opacity() const override { return true; } std::shared_ptr shared() const override { return instance; } sk_sp skia_object() const override { return sk_filter_; } @@ -225,6 +237,7 @@ class DlLinearToSrgbGammaColorFilter final : public DlColorFilter { } size_t size() const override { return sizeof(*this); } bool modifies_transparent_black() const override { return false; } + bool can_commute_with_opacity() const override { return true; } std::shared_ptr shared() const override { return instance; } sk_sp skia_object() const override { return sk_filter_; } diff --git a/display_list/display_list_paint.h b/display_list/display_list_paint.h index b260180294eba..9eb0ad0de2b2a 100644 --- a/display_list/display_list_paint.h +++ b/display_list/display_list_paint.h @@ -104,6 +104,10 @@ class DlPaint { color_.argb = alpha << 24 | (color_.argb & 0x00FFFFFF); return *this; } + DlPaint& setOpacity(SkScalar opacity) { + setAlpha(SkScalarRoundToInt(opacity * 0xff)); + return *this; + } DlBlendMode getBlendMode() const { return static_cast(blendMode_); @@ -166,7 +170,7 @@ class DlPaint { return colorFilter_; } const DlColorFilter* getColorFilterPtr() const { return colorFilter_.get(); } - DlPaint& setColorFilter(std::shared_ptr filter) { + DlPaint& setColorFilter(const std::shared_ptr filter) { colorFilter_ = filter ? filter->shared() : nullptr; return *this; } @@ -179,7 +183,7 @@ class DlPaint { return imageFilter_; } const DlImageFilter* getImageFilterPtr() const { return imageFilter_.get(); } - DlPaint& setImageFilter(std::shared_ptr filter) { + DlPaint& setImageFilter(const std::shared_ptr filter) { imageFilter_ = filter; return *this; } diff --git a/flow/BUILD.gn b/flow/BUILD.gn index c42fa8e7ab18c..09ed594157cab 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -45,6 +45,8 @@ source_set("flow") { "layers/layer.h", "layers/layer_raster_cache_item.cc", "layers/layer_raster_cache_item.h", + "layers/layer_state_stack.cc", + "layers/layer_state_stack.h", "layers/layer_tree.cc", "layers/layer_tree.h", "layers/offscreen_surface.cc", @@ -156,6 +158,7 @@ if (enable_unittests) { "layers/container_layer_unittests.cc", "layers/display_list_layer_unittests.cc", "layers/image_filter_layer_unittests.cc", + "layers/layer_state_stack_unittests.cc", "layers/layer_tree_unittests.cc", "layers/offscreen_surface_unittests.cc", "layers/opacity_layer_unittests.cc", @@ -170,7 +173,6 @@ if (enable_unittests) { "rtree_unittests.cc", "skia_gpu_object_unittests.cc", "surface_frame_unittests.cc", - "testing/auto_save_layer_unittests.cc", "testing/mock_layer_unittests.cc", "testing/mock_texture_unittests.cc", "texture_unittests.cc", diff --git a/flow/embedded_view_params_unittests.cc b/flow/embedded_view_params_unittests.cc index aa9604cae9922..fb301d0009946 100644 --- a/flow/embedded_view_params_unittests.cc +++ b/flow/embedded_view_params_unittests.cc @@ -56,7 +56,6 @@ TEST(EmbeddedViewParams, GetBoundingRectAfterMutationsWithRotation90) { EmbeddedViewParams params(matrix, SkSize::Make(1, 1), stack); const SkRect& rect = params.finalBoundingRect(); - FML_DLOG(ERROR) << rect.x(); ASSERT_TRUE(SkScalarNearlyEqual(rect.x(), -1)); ASSERT_TRUE(SkScalarNearlyEqual(rect.y(), 0)); ASSERT_TRUE(SkScalarNearlyEqual(rect.width(), 1)); diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index ebc1812755488..2982cb40824b9 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -109,6 +109,12 @@ void MutatorsStack::Pop() { vector_.pop_back(); }; +void MutatorsStack::PopTo(size_t stack_count) { + while (vector_.size() > stack_count) { + Pop(); + } +} + const std::vector>::const_reverse_iterator MutatorsStack::Top() const { return vector_.rend(); diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 14ca2ba12b348..4627c7d880a06 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -160,6 +160,8 @@ class MutatorsStack { // and destroys it. void Pop(); + void PopTo(size_t stack_count); + // Returns a reverse iterator pointing to the top of the stack, which is the // mutator that is furtherest from the leaf node. const std::vector>::const_reverse_iterator Top() @@ -178,6 +180,7 @@ class MutatorsStack { const std::vector>::const_iterator End() const; bool is_empty() const { return vector_.empty(); } + size_t stack_count() const { return vector_.size(); } bool operator==(const MutatorsStack& other) const { if (vector_.size() != other.vector_.size()) { diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index 2c9f673a5a4f0..07a596662d730 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -39,48 +39,26 @@ void BackdropFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void BackdropFilterLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void BackdropFilterLayer::Preroll(PrerollContext* context) { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context, true, bool(filter_)); if (context->view_embedder != nullptr) { context->view_embedder->PushFilterToVisitedPlatformViews(filter_); } SkRect child_paint_bounds = SkRect::MakeEmpty(); - PrerollChildren(context, matrix, &child_paint_bounds); - child_paint_bounds.join(context->cull_rect); + PrerollChildren(context, &child_paint_bounds); + child_paint_bounds.join(context->state_stack.local_cull_rect()); set_paint_bounds(child_paint_bounds); - context->subtree_can_inherit_opacity = true; + context->renderable_state_flags = kSaveLayerRenderFlags; } void BackdropFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); - AutoCachePaint save_paint(context); - save_paint.setBlendMode(blend_mode_); - if (context.leaf_nodes_builder) { - // Note that we perform a saveLayer directly on the - // leaf_nodes_builder here similar to how the SkCanvas - // path specifies the kLeafNodesCanvas below. - // See https:://flutter.dev/go/backdrop-filter-with-overlay-canvas - context.leaf_nodes_builder->saveLayer(&paint_bounds(), - save_paint.dl_paint(), filter_.get()); + auto mutator = context.state_stack.save(); + mutator.applyBackdropFilter(paint_bounds(), filter_, blend_mode_); - PaintChildren(context); - - context.leaf_nodes_builder->restore(); - } else { - auto sk_filter = filter_ ? filter_->skia_object() : nullptr; - Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( - context, - SkCanvas::SaveLayerRec{&paint_bounds(), save_paint.sk_paint(), - sk_filter.get(), 0}, - // BackdropFilter should only happen on the leaf nodes canvas. - // See https:://flutter.dev/go/backdrop-filter-with-overlay-canvas - AutoSaveLayer::SaveMode::kLeafNodesCanvas); - - PaintChildren(context); - } + PaintChildren(context); } } // namespace flutter diff --git a/flow/layers/backdrop_filter_layer.h b/flow/layers/backdrop_filter_layer.h index 8584413698a75..d17fed110d5e9 100644 --- a/flow/layers/backdrop_filter_layer.h +++ b/flow/layers/backdrop_filter_layer.h @@ -17,7 +17,7 @@ class BackdropFilterLayer : public ContainerLayer { void Diff(DiffContext* context, const Layer* old_layer) override; - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) 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 index 49a214bd76e73..398ee2133333b 100644 --- a/flow/layers/backdrop_filter_layer_unittests.cc +++ b/flow/layers/backdrop_filter_layer_unittests.cc @@ -28,7 +28,7 @@ TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies) { auto parent = std::make_shared(kEmptyRect, Clip::hardEdge); parent->Add(layer); - parent->Preroll(preroll_context(), SkMatrix()); + parent->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -65,7 +65,8 @@ TEST_F(BackdropFilterLayerTest, EmptyFilter) { auto parent = std::make_shared(child_bounds, Clip::hardEdge); parent->Add(layer); - parent->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + parent->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -95,7 +96,8 @@ TEST_F(BackdropFilterLayerTest, SimpleFilter) { auto parent = std::make_shared(child_bounds, Clip::hardEdge); parent->Add(layer); - parent->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + parent->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -125,7 +127,8 @@ TEST_F(BackdropFilterLayerTest, NonSrcOverBlend) { auto parent = std::make_shared(child_bounds, Clip::hardEdge); parent->Add(layer); - parent->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + parent->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -166,7 +169,8 @@ TEST_F(BackdropFilterLayerTest, MultipleChildren) { std::make_shared(children_bounds, Clip::hardEdge); parent->Add(layer); - parent->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + parent->Preroll(preroll_context()); 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); @@ -215,7 +219,9 @@ TEST_F(BackdropFilterLayerTest, Nested) { std::make_shared(children_bounds, Clip::hardEdge); parent->Add(layer1); - parent->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + parent->Preroll(preroll_context()); + 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); @@ -253,19 +259,20 @@ TEST_F(BackdropFilterLayerTest, Readback) { auto layer1 = std::make_shared(layer_filter.shared(), DlBlendMode::kSrcOver); preroll_context()->surface_needs_readback = false; - layer1->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer1->Preroll(preroll_context()); EXPECT_TRUE(preroll_context()->surface_needs_readback); // BDF with no filter does not read from surface itself auto layer2 = std::make_shared(no_filter, DlBlendMode::kSrcOver); preroll_context()->surface_needs_readback = false; - layer2->Preroll(preroll_context(), initial_transform); + layer2->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); // BDF with no filter does not block prior readback value preroll_context()->surface_needs_readback = true; - layer2->Preroll(preroll_context(), initial_transform); + layer2->Preroll(preroll_context()); EXPECT_TRUE(preroll_context()->surface_needs_readback); // BDF with no filter blocks child with readback @@ -273,7 +280,7 @@ TEST_F(BackdropFilterLayerTest, Readback) { mock_layer->set_fake_reads_surface(true); layer2->Add(mock_layer); preroll_context()->surface_needs_readback = false; - layer2->Preroll(preroll_context(), initial_transform); + layer2->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); } @@ -292,7 +299,7 @@ TEST_F(BackdropFilterLayerTest, OpacityInheritance) { parent->Add(layer); clip->Add(parent); - clip->Preroll(preroll_context(), SkMatrix::I()); + clip->Preroll(preroll_context()); clip->Paint(display_list_paint_context()); diff --git a/flow/layers/cacheable_layer.h b/flow/layers/cacheable_layer.h index aac445b4bf054..c4aa392460cb4 100644 --- a/flow/layers/cacheable_layer.h +++ b/flow/layers/cacheable_layer.h @@ -27,7 +27,7 @@ class AutoCache { inline bool IsCacheEnabled(); RasterCacheItem* raster_cache_item_ = nullptr; PrerollContext* context_ = nullptr; - const SkMatrix& matrix_; + const SkMatrix matrix_; }; class CacheableContainerLayer : public ContainerLayer { diff --git a/flow/layers/checkerboard_layertree_unittests.cc b/flow/layers/checkerboard_layertree_unittests.cc index 1923b77278334..2b176b97a8e82 100644 --- a/flow/layers/checkerboard_layertree_unittests.cc +++ b/flow/layers/checkerboard_layertree_unittests.cc @@ -18,40 +18,35 @@ namespace testing { using CheckerBoardLayerTest = LayerTest; #ifndef NDEBUG -TEST_F(CheckerBoardLayerTest, ClipRectSaveLayerNotCheckBoard) { +TEST_F(CheckerBoardLayerTest, ClipRectSaveLayerCheckBoard) { const SkMatrix initial_matrix = SkMatrix::Translate(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 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); - const SkPaint clip_paint; - auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(layer_bounds, Clip::antiAliasWithSaveLayer); 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 + preroll_context()->state_stack.set_initial_transform(initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - 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_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(mock_layer->needs_painting(paint_context())); EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + 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( @@ -60,49 +55,17 @@ TEST_F(CheckerBoardLayerTest, ClipRectSaveLayerNotCheckBoard) { 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}}, MockCanvas::DrawCall{ - 1, MockCanvas::SaveLayerData{layer->paint_bounds(), clip_paint, - nullptr, 2}}, + 1, + MockCanvas::SaveLayerData{child_bounds, SkPaint(), nullptr, 2}}, MockCanvas::DrawCall{ 2, MockCanvas::DrawPathData{child_path, child_paint}}, MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); -} - -TEST_F(CheckerBoardLayerTest, ClipRectSaveLayerCheckBoard) { - const SkMatrix initial_matrix = SkMatrix::Translate(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); - const SkPaint clip_paint; - - auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = std::make_shared(layer_bounds, - Clip::antiAliasWithSaveLayer); - 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(paint_context())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - 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(check_board_context()); + mock_canvas().reset_draw_calls(); - EXPECT_NE( + layer->Paint(checkerboard_context()); + EXPECT_EQ( mock_canvas().draw_calls(), std::vector( {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, @@ -110,20 +73,25 @@ TEST_F(CheckerBoardLayerTest, ClipRectSaveLayerCheckBoard) { 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}}, MockCanvas::DrawCall{ - 1, MockCanvas::SaveLayerData{layer->paint_bounds(), clip_paint, - nullptr, 2}}, + 1, + MockCanvas::SaveLayerData{child_bounds, SkPaint(), nullptr, 2}}, MockCanvas::DrawCall{ 2, MockCanvas::DrawPathData{child_path, child_paint}}, + // start DrawCheckerboard calls + MockCanvas::DrawCall{ + 2, MockCanvas::DrawRectData{child_bounds, checkerboard_paint()}}, + // end DrawCheckerboard calls MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } -TEST_F(CheckerBoardLayerTest, ClipPathSaveLayerNotCheckBoard) { +TEST_F(CheckerBoardLayerTest, ClipPathSaveLayerCheckBoard) { const SkMatrix initial_matrix = SkMatrix::Translate(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 SkPath layer_path = + SkPath().addRect(layer_bounds).addRect(layer_bounds); const SkPaint child_paint = SkPaint(SkColors::kYellow); const SkPaint clip_paint; auto mock_layer = std::make_shared(child_path, child_paint); @@ -131,9 +99,13 @@ TEST_F(CheckerBoardLayerTest, ClipPathSaveLayerNotCheckBoard) { std::make_shared(layer_path, Clip::antiAliasWithSaveLayer); 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 + preroll_context()->state_stack.set_initial_transform(initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); EXPECT_TRUE(mock_layer->needs_painting(paint_context())); @@ -148,7 +120,7 @@ TEST_F(CheckerBoardLayerTest, ClipPathSaveLayerNotCheckBoard) { std::vector( {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, MockCanvas::DrawCall{ - 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + 1, MockCanvas::ClipPathData{layer_path, SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}}, MockCanvas::DrawCall{ 1, @@ -157,55 +129,36 @@ TEST_F(CheckerBoardLayerTest, ClipPathSaveLayerNotCheckBoard) { 2, MockCanvas::DrawPathData{child_path, child_paint}}, MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); -} -TEST_F(CheckerBoardLayerTest, ClipPathSaveLayerCheckBoard) { - const SkMatrix initial_matrix = SkMatrix::Translate(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); - const SkPaint clip_paint; - auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = - std::make_shared(layer_path, Clip::antiAliasWithSaveLayer); - 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(paint_context())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - 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)})); + mock_canvas().reset_draw_calls(); - layer->Paint(check_board_context()); - EXPECT_NE( + layer->Paint(checkerboard_context()); + EXPECT_EQ( mock_canvas().draw_calls(), std::vector( {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, MockCanvas::DrawCall{ - 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + 1, MockCanvas::ClipPathData{layer_path, SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}}, MockCanvas::DrawCall{ 1, MockCanvas::SaveLayerData{child_bounds, clip_paint, nullptr, 2}}, MockCanvas::DrawCall{ 2, MockCanvas::DrawPathData{child_path, child_paint}}, + // start DrawCheckerboard calls + MockCanvas::DrawCall{ + 2, MockCanvas::DrawRectData{child_bounds, checkerboard_paint()}}, + // end DrawCheckerboard calls MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } -TEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerNotCheckBoard) { +TEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerCheckBoard) { const SkMatrix initial_matrix = SkMatrix::Translate(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 SkRRect layer_rrect = SkRRect::MakeRectXY(layer_bounds, .1, .1); const SkPaint child_paint = SkPaint(SkColors::kYellow); const SkPaint clip_paint; auto mock_layer = std::make_shared(child_path, child_paint); @@ -213,9 +166,13 @@ TEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerNotCheckBoard) { Clip::antiAliasWithSaveLayer); 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 + preroll_context()->state_stack.set_initial_transform(initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); EXPECT_TRUE(mock_layer->needs_painting(paint_context())); @@ -230,8 +187,8 @@ TEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerNotCheckBoard) { std::vector( {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, MockCanvas::DrawCall{ - 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, - MockCanvas::kSoft_ClipEdgeStyle}}, + 1, MockCanvas::ClipRRectData{layer_rrect, SkClipOp::kIntersect, + MockCanvas::kSoft_ClipEdgeStyle}}, MockCanvas::DrawCall{ 1, MockCanvas::SaveLayerData{child_bounds, clip_paint, nullptr, 2}}, @@ -239,50 +196,31 @@ TEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerNotCheckBoard) { 2, MockCanvas::DrawPathData{child_path, child_paint}}, MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); -} -TEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerCheckBoard) { - const SkMatrix initial_matrix = SkMatrix::Translate(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); - const SkPaint clip_paint; - auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = std::make_shared(layer_rrect, - Clip::antiAliasWithSaveLayer); - 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(paint_context())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - 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)})); + mock_canvas().reset_draw_calls(); - layer->Paint(check_board_context()); - EXPECT_NE( + layer->Paint(checkerboard_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::kSoft_ClipEdgeStyle}}, + 1, MockCanvas::ClipRRectData{layer_rrect, SkClipOp::kIntersect, + MockCanvas::kSoft_ClipEdgeStyle}}, MockCanvas::DrawCall{ 1, MockCanvas::SaveLayerData{child_bounds, clip_paint, nullptr, 2}}, MockCanvas::DrawCall{ 2, MockCanvas::DrawPathData{child_path, child_paint}}, + // start DrawCheckerboard calls + MockCanvas::DrawCall{ + 2, MockCanvas::DrawRectData{child_bounds, checkerboard_paint()}}, + // end DrawCheckerboard calls MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } -TEST_F(CheckerBoardLayerTest, PhysicalSaveLayerNotCheckBoard) { +TEST_F(CheckerBoardLayerTest, PhysicalSaveLayerCheckBoard) { constexpr float initial_elevation = 20.0f; SkPath layer_path; layer_path.addRect(0, 0, 8, 8).close(); @@ -290,7 +228,7 @@ TEST_F(CheckerBoardLayerTest, PhysicalSaveLayerNotCheckBoard) { SK_ColorGREEN, SK_ColorBLACK, initial_elevation, layer_path, Clip::antiAliasWithSaveLayer); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and // their shadows , so we do not do any painting there. EXPECT_EQ(layer->paint_bounds(), @@ -319,32 +257,11 @@ TEST_F(CheckerBoardLayerTest, PhysicalSaveLayerNotCheckBoard) { MockCanvas::DrawCall{2, MockCanvas::DrawPaint{layer_paint}}, MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); -} -TEST_F(CheckerBoardLayerTest, PhysicalSaveLayerCheckBoard) { - 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, initial_elevation, layer_path, - Clip::antiAliasWithSaveLayer); + mock_canvas().reset_draw_calls(); - layer->Preroll(preroll_context(), SkMatrix()); - // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and - // their shadows , so we do not do any painting there. - EXPECT_EQ(layer->paint_bounds(), - DisplayListCanvasDispatcher::ComputeShadowBounds( - layer_path, initial_elevation, 1.0f, SkMatrix())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(layer->elevation(), initial_elevation); - - const SkRect paint_bounds = SkRect::MakeXYWH(0, 0, 8, 8); - const SkPaint clip_paint; - SkPaint layer_paint; - layer_paint.setColor(SK_ColorGREEN); - layer_paint.setAntiAlias(true); - layer->Paint(check_board_context()); - EXPECT_NE( + layer->Paint(checkerboard_context()); + EXPECT_EQ( mock_canvas().draw_calls(), std::vector( {MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, @@ -356,6 +273,11 @@ TEST_F(CheckerBoardLayerTest, PhysicalSaveLayerCheckBoard) { 1, MockCanvas::SaveLayerData{layer->paint_bounds(), clip_paint, nullptr, 2}}, MockCanvas::DrawCall{2, MockCanvas::DrawPaint{layer_paint}}, + // start DrawCheckerboard calls + MockCanvas::DrawCall{2, + MockCanvas::DrawRectData{layer->paint_bounds(), + checkerboard_paint()}}, + // end DrawCheckerboard calls MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } diff --git a/flow/layers/clip_path_layer.cc b/flow/layers/clip_path_layer.cc index 65c7f898ea211..5a8961fced9c6 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -9,25 +9,12 @@ namespace flutter { ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior) : ClipShapeLayer(clip_path, clip_behavior) {} -void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { - ClipShapeLayer::Preroll(context, matrix); -} - -void ClipPathLayer::Paint(PaintContext& context) const { - ClipShapeLayer::Paint(context); -} - const SkRect& ClipPathLayer::clip_shape_bounds() const { return clip_shape().getBounds(); } -void ClipPathLayer::OnMutatorsStackPushClipShape( - MutatorsStack& mutators_stack) { - mutators_stack.PushClipPath(clip_shape()); -} - -void ClipPathLayer::OnCanvasClipShape(SkCanvas* canvas) const { - canvas->clipPath(clip_shape(), clip_behavior() != Clip::hardEdge); +void ClipPathLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const { + mutator.clipPath(clip_shape(), clip_behavior() != Clip::hardEdge); } } // namespace flutter diff --git a/flow/layers/clip_path_layer.h b/flow/layers/clip_path_layer.h index c8ff1bad743a6..5a1cbffcb6f92 100644 --- a/flow/layers/clip_path_layer.h +++ b/flow/layers/clip_path_layer.h @@ -14,16 +14,10 @@ class ClipPathLayer : public ClipShapeLayer { explicit ClipPathLayer(const SkPath& clip_path, Clip clip_behavior = Clip::antiAlias); - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; - - void Paint(PaintContext& context) const override; - protected: const SkRect& clip_shape_bounds() const override; - void OnMutatorsStackPushClipShape(MutatorsStack& mutators_stack) override; - - void OnCanvasClipShape(SkCanvas* canvas) const override; + void ApplyClip(LayerStateStack::MutatorContext& mutator) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(ClipPathLayer); diff --git a/flow/layers/clip_path_layer_unittests.cc b/flow/layers/clip_path_layer_unittests.cc index 4408185aed5e2..adc52b6549e50 100644 --- a/flow/layers/clip_path_layer_unittests.cc +++ b/flow/layers/clip_path_layer_unittests.cc @@ -29,9 +29,12 @@ TEST_F(ClipPathLayerTest, ClipNoneBehaviorDies) { 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 + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -63,11 +66,15 @@ TEST_F(ClipPathLayerTest, PaintingCulledLayerDies) { auto layer = std::make_shared(layer_path, Clip::hardEdge); layer->Add(mock_layer); - preroll_context()->cull_rect = distant_bounds; // Cull these children + // Cull these children + preroll_context()->state_stack.set_initial_state(distant_bounds, + initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - layer->Preroll(preroll_context(), initial_matrix); - EXPECT_EQ(preroll_context()->cull_rect, distant_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_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); @@ -77,7 +84,8 @@ TEST_F(ClipPathLayerTest, PaintingCulledLayerDies) { EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); - paint_context().internal_nodes_canvas->clipRect(distant_bounds, false); + auto mutator = paint_context().state_stack.save(); + mutator.clipRect(distant_bounds, false); EXPECT_FALSE(mock_layer->needs_painting(paint_context())); EXPECT_FALSE(layer->needs_painting(paint_context())); EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), @@ -87,45 +95,47 @@ TEST_F(ClipPathLayerTest, PaintingCulledLayerDies) { TEST_F(ClipPathLayerTest, ChildOutsideBounds) { const SkMatrix initial_matrix = SkMatrix::Translate(0.5f, 1.0f); - const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect local_cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect device_cull_bounds = initial_matrix.mapRect(local_cull_bounds); 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 SkRect clip_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 SkPath clip_path = SkPath().addRect(clip_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); + auto layer = std::make_shared(clip_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 + SkRect clip_cull_rect = local_cull_bounds; + ASSERT_TRUE(clip_cull_rect.intersect(clip_bounds)); + SkRect clip_layer_bounds = child_bounds; + ASSERT_TRUE(clip_layer_bounds.intersect(clip_bounds)); + + // Set up both contexts to cull clipped child + preroll_context()->state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + paint_context().state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + + layer->Preroll(preroll_context()); + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), + device_cull_bounds); + EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(), + local_cull_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - 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_EQ(layer->paint_bounds(), clip_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); - EXPECT_TRUE(mock_layer->needs_painting(paint_context())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect); EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); - EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_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}}})); + EXPECT_FALSE(layer->needs_painting(paint_context())); + EXPECT_FALSE(mock_layer->needs_painting(paint_context())); + // Top level layer not visible so calling layer->Paint() + // would trip an FML_DCHECK } TEST_F(ClipPathLayerTest, FullyContainedChild) { @@ -139,9 +149,13 @@ TEST_F(ClipPathLayerTest, FullyContainedChild) { 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 + preroll_context()->state_stack.set_initial_transform(initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); @@ -166,33 +180,42 @@ TEST_F(ClipPathLayerTest, FullyContainedChild) { TEST_F(ClipPathLayerTest, PartiallyContainedChild) { const SkMatrix initial_matrix = SkMatrix::Translate(0.5f, 1.0f); - const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect local_cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect device_cull_bounds = initial_matrix.mapRect(local_cull_bounds); 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 SkRect clip_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 SkPath clip_path = SkPath().addRect(clip_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); + auto layer = std::make_shared(clip_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 + SkRect clip_cull_rect = local_cull_bounds; + ASSERT_TRUE(clip_cull_rect.intersect(clip_bounds)); + SkRect clip_layer_bounds = child_bounds; + ASSERT_TRUE(clip_layer_bounds.intersect(clip_bounds)); + + // Cull child + preroll_context()->state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), + device_cull_bounds); + EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(), + local_cull_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - 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_EQ(layer->paint_bounds(), clip_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(mock_layer->needs_painting(paint_context())); EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect); EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); - EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_path)})); layer->Paint(paint_context()); EXPECT_EQ( @@ -200,7 +223,7 @@ TEST_F(ClipPathLayerTest, PartiallyContainedChild) { std::vector( {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, MockCanvas::DrawCall{ - 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + 1, MockCanvas::ClipRectData{clip_bounds, SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}}, MockCanvas::DrawCall{ 1, MockCanvas::DrawPathData{child_path, child_paint}}, @@ -211,7 +234,6 @@ static bool ReadbackResult(PrerollContext* context, Clip clip_behavior, const std::shared_ptr& child, bool before) { - const SkMatrix initial_matrix = SkMatrix(); const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); const SkPath layer_path = SkPath().addRect(layer_bounds); auto layer = std::make_shared(layer_path, clip_behavior); @@ -219,7 +241,7 @@ static bool ReadbackResult(PrerollContext* context, layer->Add(child); } context->surface_needs_readback = before; - layer->Preroll(context, initial_matrix); + layer->Preroll(context); return context->surface_needs_readback; } @@ -280,9 +302,9 @@ TEST_F(ClipPathLayerTest, OpacityInheritance) { // ClipRectLayer will pass through compatibility from a compatible child PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_path_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path2 = SkPath().addRect({40, 40, 50, 50}); auto mock2 = MockLayer::MakeOpacityCompatible(path2); @@ -290,9 +312,9 @@ TEST_F(ClipPathLayerTest, OpacityInheritance) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children - context->subtree_can_inherit_opacity = false; - clip_path_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path3 = SkPath().addRect({20, 20, 40, 40}); auto mock3 = MockLayer::MakeOpacityCompatible(path3); @@ -300,9 +322,8 @@ TEST_F(ClipPathLayerTest, OpacityInheritance) { // ClipRectLayer will not pass through compatibility from multiple // overlapping children even if they are individually compatible - context->subtree_can_inherit_opacity = false; - clip_path_layer->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + clip_path_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); { // ClipRectLayer(aa with saveLayer) will always be compatible @@ -312,15 +333,13 @@ TEST_F(ClipPathLayerTest, OpacityInheritance) { clip_path_savelayer->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_path_savelayer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); // Now add the overlapping child and test again, should still be compatible clip_path_savelayer->Add(mock3); - context->subtree_can_inherit_opacity = false; - clip_path_savelayer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); } // An incompatible, but non-overlapping child for the following tests @@ -335,17 +354,16 @@ TEST_F(ClipPathLayerTest, OpacityInheritance) { clip_path_bad_child->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_path_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); clip_path_bad_child->Add(mock4); // The third child is non-overlapping, but not compatible so the // TransformLayer should end up incompatible - context->subtree_can_inherit_opacity = false; - clip_path_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + clip_path_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); } { @@ -356,15 +374,13 @@ TEST_F(ClipPathLayerTest, OpacityInheritance) { clip_path_savelayer_bad_child->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_path_savelayer_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); // Now add the incompatible child and test again, should still be compatible clip_path_savelayer_bad_child->Add(mock4); - context->subtree_can_inherit_opacity = false; - clip_path_savelayer_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); } } @@ -375,7 +391,7 @@ TEST_F(ClipPathLayerTest, OpacityInheritancePainting) { auto mock2 = MockLayer::MakeOpacityCompatible(path2); auto layer_clip = SkPath() .addRect(SkRect::MakeLTRB(5, 5, 25, 25)) - .addOval(SkRect::MakeLTRB(20, 20, 40, 50)); + .addOval(SkRect::MakeLTRB(45, 45, 55, 55)); auto clip_path_layer = std::make_shared(layer_clip, Clip::antiAlias); clip_path_layer->Add(mock1); @@ -384,16 +400,15 @@ TEST_F(ClipPathLayerTest, OpacityInheritancePainting) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_path_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(clip_path_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -405,22 +420,10 @@ TEST_F(ClipPathLayerTest, OpacityInheritancePainting) { expected_builder.save(); expected_builder.clipPath(layer_clip, SkClipOp::kIntersect, true); /* child layer1 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path1.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path1); - } - expected_builder.restore(); + expected_builder.drawPath(path1, DlPaint().setAlpha(opacity_alpha)); } /* child layer2 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path2.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path2); - } - expected_builder.restore(); + expected_builder.drawPath(path2, DlPaint().setAlpha(opacity_alpha)); } expected_builder.restore(); } @@ -429,7 +432,7 @@ TEST_F(ClipPathLayerTest, OpacityInheritancePainting) { } opacity_layer->Paint(display_list_paint_context()); - EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build())); } TEST_F(ClipPathLayerTest, OpacityInheritanceSaveLayerPainting) { @@ -450,16 +453,14 @@ TEST_F(ClipPathLayerTest, OpacityInheritanceSaveLayerPainting) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_path_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(clip_path_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -505,23 +506,24 @@ TEST_F(ClipPathLayerTest, LayerCached) { cache_canvas.setMatrix(cache_ctm); use_mock_raster_cache(); + preroll_context()->state_stack.set_initial_transform(initial_transform); const auto* clip_cache_item = layer->raster_cache_item(); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); EXPECT_EQ(clip_cache_item->cache_state(), diff --git a/flow/layers/clip_rect_layer.cc b/flow/layers/clip_rect_layer.cc index 6b7ff2002f0a8..b141ec83e3a01 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -9,25 +9,12 @@ namespace flutter { ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior) : ClipShapeLayer(clip_rect, clip_behavior) {} -void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { - ClipShapeLayer::Preroll(context, matrix); -} - -void ClipRectLayer::Paint(PaintContext& context) const { - ClipShapeLayer::Paint(context); -} - const SkRect& ClipRectLayer::clip_shape_bounds() const { return clip_shape(); } -void ClipRectLayer::OnMutatorsStackPushClipShape( - MutatorsStack& mutators_stack) { - mutators_stack.PushClipRect(clip_shape()); -} - -void ClipRectLayer::OnCanvasClipShape(SkCanvas* canvas) const { - canvas->clipRect(clip_shape(), clip_behavior() != Clip::hardEdge); +void ClipRectLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const { + mutator.clipRect(clip_shape(), clip_behavior() != Clip::hardEdge); } } // namespace flutter diff --git a/flow/layers/clip_rect_layer.h b/flow/layers/clip_rect_layer.h index 967bd9b349cd7..5f8f11d2bf023 100644 --- a/flow/layers/clip_rect_layer.h +++ b/flow/layers/clip_rect_layer.h @@ -13,16 +13,10 @@ class ClipRectLayer : public ClipShapeLayer { public: ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior); - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; - - void Paint(PaintContext& context) const override; - protected: const SkRect& clip_shape_bounds() const override; - void OnMutatorsStackPushClipShape(MutatorsStack& mutators_stack) override; - - void OnCanvasClipShape(SkCanvas* canvas) const override; + void ApplyClip(LayerStateStack::MutatorContext& mutator) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(ClipRectLayer); diff --git a/flow/layers/clip_rect_layer_unittests.cc b/flow/layers/clip_rect_layer_unittests.cc index 2b11cb588dd65..cd152f6218380 100644 --- a/flow/layers/clip_rect_layer_unittests.cc +++ b/flow/layers/clip_rect_layer_unittests.cc @@ -26,9 +26,12 @@ TEST_F(ClipRectLayerTest, ClipNoneBehaviorDies) { 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 + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -58,11 +61,15 @@ TEST_F(ClipRectLayerTest, PaintingCulledLayerDies) { auto layer = std::make_shared(layer_bounds, Clip::hardEdge); layer->Add(mock_layer); - preroll_context()->cull_rect = distant_bounds; // Cull these children + // Cull these children + preroll_context()->state_stack.set_initial_state(distant_bounds, + initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - layer->Preroll(preroll_context(), initial_matrix); - EXPECT_EQ(preroll_context()->cull_rect, distant_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_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); @@ -73,7 +80,8 @@ TEST_F(ClipRectLayerTest, PaintingCulledLayerDies) { EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_bounds)})); - paint_context().internal_nodes_canvas->clipRect(distant_bounds, false); + auto mutator = paint_context().state_stack.save(); + mutator.clipRect(distant_bounds, false); EXPECT_FALSE(mock_layer->needs_painting(paint_context())); EXPECT_FALSE(layer->needs_painting(paint_context())); EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), @@ -83,45 +91,46 @@ TEST_F(ClipRectLayerTest, PaintingCulledLayerDies) { TEST_F(ClipRectLayerTest, ChildOutsideBounds) { const SkMatrix initial_matrix = SkMatrix::Translate(0.5f, 1.0f); - const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect local_cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect device_cull_bounds = initial_matrix.mapRect(local_cull_bounds); 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 SkRect clip_rect = 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); + auto layer = std::make_shared(clip_rect, 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 + SkRect clip_cull_rect = local_cull_bounds; + ASSERT_TRUE(clip_cull_rect.intersect(clip_rect)); + SkRect clip_layer_bounds = child_bounds; + ASSERT_TRUE(clip_layer_bounds.intersect(clip_rect)); + + // Set up both contexts to cull clipped child + preroll_context()->state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + paint_context().state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + + layer->Preroll(preroll_context()); + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), + device_cull_bounds); + EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(), + local_cull_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - 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_EQ(layer->paint_bounds(), clip_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); - EXPECT_TRUE(mock_layer->needs_painting(paint_context())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect); EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); - EXPECT_EQ(mock_layer->parent_mutators(), - std::vector({Mutator(layer_bounds)})); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_rect)})); - 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}}})); + EXPECT_FALSE(layer->needs_painting(paint_context())); + EXPECT_FALSE(mock_layer->needs_painting(paint_context())); + // Top level layer not visible so calling layer->Paint() + // would trip an FML_DCHECK } TEST_F(ClipRectLayerTest, FullyContainedChild) { @@ -134,9 +143,13 @@ TEST_F(ClipRectLayerTest, FullyContainedChild) { 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 + preroll_context()->state_stack.set_initial_transform(initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); @@ -162,41 +175,49 @@ TEST_F(ClipRectLayerTest, FullyContainedChild) { TEST_F(ClipRectLayerTest, PartiallyContainedChild) { const SkMatrix initial_matrix = SkMatrix::Translate(0.5f, 1.0f); - const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect local_cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect device_cull_bounds = initial_matrix.mapRect(local_cull_bounds); 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 SkRect clip_rect = 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); + auto layer = std::make_shared(clip_rect, 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 + SkRect clip_cull_rect = clip_rect; + ASSERT_TRUE(clip_cull_rect.intersect(local_cull_bounds)); + SkRect clip_layer_bounds = clip_rect; + ASSERT_TRUE(clip_layer_bounds.intersect(child_bounds)); + + // Cull child + preroll_context()->state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), + device_cull_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - 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_EQ(layer->paint_bounds(), clip_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(mock_layer->needs_painting(paint_context())); EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect); EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); - EXPECT_EQ(mock_layer->parent_mutators(), - std::vector({Mutator(layer_bounds)})); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_rect)})); + paint_context().state_stack.set_initial_state(device_cull_bounds, + initial_matrix); 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, + 1, MockCanvas::ClipRectData{clip_rect, SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}}, MockCanvas::DrawCall{ 1, MockCanvas::DrawPathData{child_path, child_paint}}, @@ -207,14 +228,13 @@ static bool ReadbackResult(PrerollContext* context, Clip clip_behavior, const std::shared_ptr& child, bool before) { - const SkMatrix initial_matrix = SkMatrix(); const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); auto layer = std::make_shared(layer_bounds, clip_behavior); if (child != nullptr) { layer->Add(child); } context->surface_needs_readback = before; - layer->Preroll(context, initial_matrix); + layer->Preroll(context); return context->surface_needs_readback; } @@ -273,9 +293,9 @@ TEST_F(ClipRectLayerTest, OpacityInheritance) { // ClipRectLayer will pass through compatibility from a compatible child PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_rect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path2 = SkPath().addRect({40, 40, 50, 50}); auto mock2 = MockLayer::MakeOpacityCompatible(path2); @@ -283,9 +303,9 @@ TEST_F(ClipRectLayerTest, OpacityInheritance) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children - context->subtree_can_inherit_opacity = false; - clip_rect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path3 = SkPath().addRect({20, 20, 40, 40}); auto mock3 = MockLayer::MakeOpacityCompatible(path3); @@ -293,9 +313,8 @@ TEST_F(ClipRectLayerTest, OpacityInheritance) { // ClipRectLayer will not pass through compatibility from multiple // overlapping children even if they are individually compatible - context->subtree_can_inherit_opacity = false; - clip_rect_layer->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + clip_rect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); { // ClipRectLayer(aa with saveLayer) will always be compatible @@ -305,15 +324,13 @@ TEST_F(ClipRectLayerTest, OpacityInheritance) { clip_path_savelayer->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_path_savelayer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); // Now add the overlapping child and test again, should still be compatible clip_path_savelayer->Add(mock3); - context->subtree_can_inherit_opacity = false; - clip_path_savelayer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); } // An incompatible, but non-overlapping child for the following tests @@ -328,17 +345,16 @@ TEST_F(ClipRectLayerTest, OpacityInheritance) { clip_rect_bad_child->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_rect_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rect_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); clip_rect_bad_child->Add(mock4); // The third child is non-overlapping, but not compatible so the // TransformLayer should end up incompatible - context->subtree_can_inherit_opacity = false; - clip_rect_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + clip_rect_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); } { @@ -349,15 +365,13 @@ TEST_F(ClipRectLayerTest, OpacityInheritance) { clip_path_savelayer_bad_child->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_path_savelayer_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); // Now add the incompatible child and test again, should still be compatible clip_path_savelayer_bad_child->Add(mock4); - context->subtree_can_inherit_opacity = false; - clip_path_savelayer_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_path_savelayer_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); } } @@ -375,16 +389,15 @@ TEST_F(ClipRectLayerTest, OpacityInheritancePainting) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_rect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(clip_rect_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -396,22 +409,10 @@ TEST_F(ClipRectLayerTest, OpacityInheritancePainting) { expected_builder.save(); expected_builder.clipRect(clip_rect, SkClipOp::kIntersect, true); /* child layer1 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path1.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path1); - } - expected_builder.restore(); + expected_builder.drawPath(path1, DlPaint().setAlpha(opacity_alpha)); } /* child layer2 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path2.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path2); - } - expected_builder.restore(); + expected_builder.drawPath(path2, DlPaint().setAlpha(opacity_alpha)); } expected_builder.restore(); } @@ -439,16 +440,14 @@ TEST_F(ClipRectLayerTest, OpacityInheritanceSaveLayerPainting) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_rect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(clip_rect_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -492,21 +491,22 @@ TEST_F(ClipRectLayerTest, LayerCached) { cache_canvas.setMatrix(cache_ctm); use_mock_raster_cache(); + preroll_context()->state_stack.set_initial_transform(initial_transform); const auto* clip_cache_item = layer->raster_cache_item(); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); EXPECT_EQ(clip_cache_item->cache_state(), diff --git a/flow/layers/clip_rrect_layer.cc b/flow/layers/clip_rrect_layer.cc index 2bf5b069332f0..735d027203303 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -9,25 +9,12 @@ namespace flutter { ClipRRectLayer::ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior) : ClipShapeLayer(clip_rrect, clip_behavior) {} -void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { - ClipShapeLayer::Preroll(context, matrix); -} - -void ClipRRectLayer::Paint(PaintContext& context) const { - ClipShapeLayer::Paint(context); -} - const SkRect& ClipRRectLayer::clip_shape_bounds() const { return clip_shape().getBounds(); } -void ClipRRectLayer::OnMutatorsStackPushClipShape( - MutatorsStack& mutators_stack) { - mutators_stack.PushClipRRect(clip_shape()); -} - -void ClipRRectLayer::OnCanvasClipShape(SkCanvas* canvas) const { - canvas->clipRRect(clip_shape(), clip_behavior() != Clip::hardEdge); +void ClipRRectLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const { + mutator.clipRRect(clip_shape(), clip_behavior() != Clip::hardEdge); } } // namespace flutter diff --git a/flow/layers/clip_rrect_layer.h b/flow/layers/clip_rrect_layer.h index 15c71a732e467..53b240abe2975 100644 --- a/flow/layers/clip_rrect_layer.h +++ b/flow/layers/clip_rrect_layer.h @@ -13,16 +13,10 @@ class ClipRRectLayer : public ClipShapeLayer { public: ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior); - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; - - void Paint(PaintContext& context) const override; - protected: const SkRect& clip_shape_bounds() const override; - void OnMutatorsStackPushClipShape(MutatorsStack& mutators_stack) override; - - void OnCanvasClipShape(SkCanvas* canvas) const override; + void ApplyClip(LayerStateStack::MutatorContext& mutator) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(ClipRRectLayer); diff --git a/flow/layers/clip_rrect_layer_unittests.cc b/flow/layers/clip_rrect_layer_unittests.cc index 918c36ec47e44..abb9b600099d7 100644 --- a/flow/layers/clip_rrect_layer_unittests.cc +++ b/flow/layers/clip_rrect_layer_unittests.cc @@ -28,9 +28,12 @@ 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 + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -63,11 +66,15 @@ TEST_F(ClipRRectLayerTest, PaintingCulledLayerDies) { auto layer = std::make_shared(layer_rrect, Clip::hardEdge); layer->Add(mock_layer); - preroll_context()->cull_rect = distant_bounds; // Cull these children + // Cull these children + preroll_context()->state_stack.set_initial_state(distant_bounds, + initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - layer->Preroll(preroll_context(), initial_matrix); - EXPECT_EQ(preroll_context()->cull_rect, distant_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_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); @@ -77,7 +84,8 @@ TEST_F(ClipRRectLayerTest, PaintingCulledLayerDies) { EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); - paint_context().internal_nodes_canvas->clipRect(distant_bounds, false); + auto mutator = paint_context().state_stack.save(); + mutator.clipRect(distant_bounds, false); EXPECT_FALSE(mock_layer->needs_painting(paint_context())); EXPECT_FALSE(layer->needs_painting(paint_context())); EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), @@ -87,45 +95,47 @@ TEST_F(ClipRRectLayerTest, PaintingCulledLayerDies) { TEST_F(ClipRRectLayerTest, ChildOutsideBounds) { const SkMatrix initial_matrix = SkMatrix::Translate(0.5f, 1.0f); - const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect local_cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect device_cull_bounds = initial_matrix.mapRect(local_cull_bounds); 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 SkRect clip_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 SkRRect clip_rrect = SkRRect::MakeRect(clip_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); + auto layer = std::make_shared(clip_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 + SkRect clip_cull_rect = clip_bounds; + ASSERT_TRUE(clip_cull_rect.intersect(local_cull_bounds)); + SkRect clip_layer_bounds = child_bounds; + ASSERT_TRUE(clip_layer_bounds.intersect(clip_bounds)); + + // Set up both contexts to cull clipped child + preroll_context()->state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + paint_context().state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + + layer->Preroll(preroll_context()); + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), + device_cull_bounds); + EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(), + local_cull_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - 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_EQ(layer->paint_bounds(), clip_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); - EXPECT_TRUE(mock_layer->needs_painting(paint_context())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect); EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); - EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_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}}})); + EXPECT_FALSE(mock_layer->needs_painting(paint_context())); + ASSERT_FALSE(layer->needs_painting(paint_context())); + // Top level layer not visible so calling layer->Paint() + // would trip an FML_DCHECK } TEST_F(ClipRRectLayerTest, FullyContainedChild) { @@ -139,9 +149,13 @@ TEST_F(ClipRRectLayerTest, FullyContainedChild) { 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 + preroll_context()->state_stack.set_initial_transform(initial_matrix); + layer->Preroll(preroll_context()); + + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); @@ -166,41 +180,51 @@ TEST_F(ClipRRectLayerTest, FullyContainedChild) { TEST_F(ClipRRectLayerTest, PartiallyContainedChild) { const SkMatrix initial_matrix = SkMatrix::Translate(0.5f, 1.0f); - const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect local_cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect device_cull_bounds = initial_matrix.mapRect(local_cull_bounds); 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 SkRect clip_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 SkRRect clip_rrect = SkRRect::MakeRect(clip_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); + auto layer = std::make_shared(clip_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 + SkRect clip_cull_rect = clip_bounds; + ASSERT_TRUE(clip_cull_rect.intersect(local_cull_bounds)); + SkRect clip_layer_bounds = child_bounds; + ASSERT_TRUE(clip_layer_bounds.intersect(clip_bounds)); + + preroll_context()->state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + paint_context().state_stack.set_initial_state(device_cull_bounds, + initial_matrix); + + layer->Preroll(preroll_context()); + // Untouched + EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), + device_cull_bounds); + EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(), + local_cull_bounds); + EXPECT_TRUE(preroll_context()->state_stack.is_empty()); - 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_EQ(layer->paint_bounds(), clip_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); - EXPECT_TRUE(mock_layer->needs_painting(paint_context())); - EXPECT_TRUE(layer->needs_painting(paint_context())); - EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect); EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); - EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_rrect)})); + EXPECT_TRUE(mock_layer->needs_painting(paint_context())); + EXPECT_TRUE(layer->needs_painting(paint_context())); 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, + 1, MockCanvas::ClipRectData{clip_bounds, SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}}, MockCanvas::DrawCall{ 1, MockCanvas::DrawPathData{child_path, child_paint}}, @@ -211,7 +235,6 @@ static bool ReadbackResult(PrerollContext* context, Clip clip_behavior, const std::shared_ptr& child, bool before) { - const SkMatrix initial_matrix = SkMatrix(); 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_behavior); @@ -219,7 +242,7 @@ static bool ReadbackResult(PrerollContext* context, layer->Add(child); } context->surface_needs_readback = before; - layer->Preroll(context, initial_matrix); + layer->Preroll(context); return context->surface_needs_readback; } @@ -279,9 +302,9 @@ TEST_F(ClipRRectLayerTest, OpacityInheritance) { // ClipRectLayer will pass through compatibility from a compatible child PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_rrect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path2 = SkPath().addRect({40, 40, 50, 50}); auto mock2 = MockLayer::MakeOpacityCompatible(path2); @@ -289,9 +312,9 @@ TEST_F(ClipRRectLayerTest, OpacityInheritance) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children - context->subtree_can_inherit_opacity = false; - clip_rrect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path3 = SkPath().addRect({20, 20, 40, 40}); auto mock3 = MockLayer::MakeOpacityCompatible(path3); @@ -299,9 +322,8 @@ TEST_F(ClipRRectLayerTest, OpacityInheritance) { // ClipRectLayer will not pass through compatibility from multiple // overlapping children even if they are individually compatible - context->subtree_can_inherit_opacity = false; - clip_rrect_layer->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + clip_rrect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); { // ClipRectLayer(aa with saveLayer) will always be compatible @@ -311,15 +333,13 @@ TEST_F(ClipRRectLayerTest, OpacityInheritance) { clip_rrect_savelayer->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_rrect_savelayer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_savelayer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); // Now add the overlapping child and test again, should still be compatible clip_rrect_savelayer->Add(mock3); - context->subtree_can_inherit_opacity = false; - clip_rrect_savelayer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_savelayer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); } // An incompatible, but non-overlapping child for the following tests @@ -334,17 +354,16 @@ TEST_F(ClipRRectLayerTest, OpacityInheritance) { clip_rrect_bad_child->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_rrect_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); clip_rrect_bad_child->Add(mock4); // The third child is non-overlapping, but not compatible so the // TransformLayer should end up incompatible - context->subtree_can_inherit_opacity = false; - clip_rrect_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + clip_rrect_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); } { @@ -355,15 +374,13 @@ TEST_F(ClipRRectLayerTest, OpacityInheritance) { clip_rrect_savelayer_bad_child->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - clip_rrect_savelayer_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_savelayer_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); // Now add the incompatible child and test again, should still be compatible clip_rrect_savelayer_bad_child->Add(mock4); - context->subtree_can_inherit_opacity = false; - clip_rrect_savelayer_bad_child->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_savelayer_bad_child->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); } } @@ -382,16 +399,15 @@ TEST_F(ClipRRectLayerTest, OpacityInheritancePainting) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_rect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(clip_rect_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -403,22 +419,10 @@ TEST_F(ClipRRectLayerTest, OpacityInheritancePainting) { expected_builder.save(); expected_builder.clipRRect(clip_rrect, SkClipOp::kIntersect, true); /* child layer1 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path1.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path1); - } - expected_builder.restore(); + expected_builder.drawPath(path1, DlPaint().setAlpha(opacity_alpha)); } /* child layer2 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path2.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path2); - } - expected_builder.restore(); + expected_builder.drawPath(path2, DlPaint().setAlpha(opacity_alpha)); } expected_builder.restore(); } @@ -447,16 +451,14 @@ TEST_F(ClipRRectLayerTest, OpacityInheritanceSaveLayerPainting) { // ClipRectLayer will pass through compatibility from multiple // non-overlapping compatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - clip_rrect_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + clip_rrect_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(clip_rrect_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -502,21 +504,22 @@ TEST_F(ClipRRectLayerTest, LayerCached) { cache_canvas.setMatrix(cache_ctm); use_mock_raster_cache(); + preroll_context()->state_stack.set_initial_transform(initial_transform); const auto* clip_cache_item = layer->raster_cache_item(); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); EXPECT_EQ(clip_cache_item->cache_state(), @@ -540,21 +543,22 @@ TEST_F(ClipRRectLayerTest, NoSaveLayerShouldNotCache) { cache_canvas.setMatrix(cache_ctm); use_mock_raster_cache(); + preroll_context()->state_stack.set_initial_transform(initial_transform); const auto* clip_cache_item = layer->raster_cache_item(); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone); diff --git a/flow/layers/clip_shape_layer.h b/flow/layers/clip_shape_layer.h index 85f6b4393dada..39b7acc6b642a 100644 --- a/flow/layers/clip_shape_layer.h +++ b/flow/layers/clip_shape_layer.h @@ -42,68 +42,64 @@ class ClipShapeLayer : public CacheableContainerLayer { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } - void Preroll(PrerollContext* context, const SkMatrix& matrix) override { - SkRect previous_cull_rect = context->cull_rect; + void Preroll(PrerollContext* context) override { bool uses_save_layer = UsesSaveLayer(); - if (!context->cull_rect.intersect(clip_shape_bounds())) { - context->cull_rect.setEmpty(); - } - SkMatrix child_matrix = matrix; // We can use the raster_cache for children only when the use_save_layer is // true so if use_save_layer is false we pass the layer_raster_item is // nullptr which mean we don't do raster cache logic. AutoCache cache = AutoCache(uses_save_layer ? layer_raster_cache_item_.get() : nullptr, - context, child_matrix); + context, context->state_stack.transform_3x3()); Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); - OnMutatorsStackPushClipShape(context->mutators_stack); - // Collect inheritance information on our children in Preroll so that - // we can pass it along by default. - context->subtree_can_inherit_opacity = true; + auto mutator = context->state_stack.save(); + ApplyClip(mutator); SkRect child_paint_bounds = SkRect::MakeEmpty(); - PrerollChildren(context, matrix, &child_paint_bounds); + PrerollChildren(context, &child_paint_bounds); if (child_paint_bounds.intersect(clip_shape_bounds())) { set_paint_bounds(child_paint_bounds); + } else { + set_paint_bounds(SkRect::MakeEmpty()); } // If we use a SaveLayer then we can accept opacity on behalf // of our children and apply it in the saveLayer. if (uses_save_layer) { - context->subtree_can_inherit_opacity = true; + context->renderable_state_flags = kSaveLayerRenderFlags; } - - context->mutators_stack.Pop(); - context->cull_rect = previous_cull_rect; } void Paint(PaintContext& context) const override { FML_DCHECK(needs_painting(context)); - SkAutoCanvasRestore save(context.internal_nodes_canvas, true); - OnCanvasClipShape(context.internal_nodes_canvas); + auto mutator = context.state_stack.save(); + ApplyClip(mutator); + if (context.state_stack.content_culled(child_paint_bounds())) { + return; + } if (!UsesSaveLayer()) { PaintChildren(context); return; } - AutoCachePaint cache_paint(context); if (context.raster_cache) { - context.internal_nodes_canvas->setMatrix( - RasterCacheUtil::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); - if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { + mutator.integralTransform(); + auto restore_apply = context.state_stack.applyState( + paint_bounds(), LayerStateStack::kCallerCanApplyOpacity); + + SkPaint paint; + if (layer_raster_cache_item_->Draw(context, + context.state_stack.fill(paint))) { return; } } - Layer::AutoSaveLayer save_layer = Layer::AutoSaveLayer::Create( - context, paint_bounds(), cache_paint.sk_paint()); + mutator.saveLayer(paint_bounds()); PaintChildren(context); } @@ -113,8 +109,7 @@ class ClipShapeLayer : public CacheableContainerLayer { protected: virtual const SkRect& clip_shape_bounds() const = 0; - virtual void OnMutatorsStackPushClipShape(MutatorsStack& mutators_stack) = 0; - virtual void OnCanvasClipShape(SkCanvas* canvas) const = 0; + virtual void ApplyClip(LayerStateStack::MutatorContext& mutator) const = 0; virtual ~ClipShapeLayer() = default; const ClipShape& clip_shape() const { return clip_shape_; } diff --git a/flow/layers/color_filter_layer.cc b/flow/layers/color_filter_layer.cc index d3da4763fce77..a5966e4542c36 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -37,49 +37,65 @@ void ColorFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void ColorFilterLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void ColorFilterLayer::Preroll(PrerollContext* context) { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); - SkMatrix child_matrix = matrix; - AutoCache cache = - AutoCache(layer_raster_cache_item_.get(), context, child_matrix); - - ContainerLayer::Preroll(context, child_matrix); - // We always use a saveLayer (or a cached rendering), so we - // can always apply opacity in those cases. - context->subtree_can_inherit_opacity = true; + AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context, + context->state_stack.transform_3x3()); + + ContainerLayer::Preroll(context); + + // Our saveLayer would apply any outstanding opacity or any outstanding + // image filter before it applies our color filter, but that is in the + // wrong order compared to how these attributes were applied to the tree + // (they would have come from one of our ancestors). So we cannot apply + // those attributes with our saveLayer normally. + // However, some color filters can commute themselves with an opacity + // modulation so in that case we can apply the opacity on behalf of our + // ancestors - otherwise we can apply no attributes. + if (filter_) { + context->renderable_state_flags = + filter_->can_commute_with_opacity() + ? LayerStateStack::kCallerCanApplyOpacity + : 0; + } + // else - we can apply whatever our children can apply. } void ColorFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); + auto mutator = context.state_stack.save(); + if (context.raster_cache) { - context.internal_nodes_canvas->setMatrix( - RasterCacheUtil::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); - AutoCachePaint cache_paint(context); - if (layer_raster_cache_item_->IsCacheChildren()) { - cache_paint.setColorFilter(filter_.get()); + // Always apply the integral transform in the presence of a raster cache + // whether or not we will draw from the cache + mutator.integralTransform(); + + // Try drawing the layer cache item from the cache before applying the + // color filter if it was cached with the filter applied. + if (!layer_raster_cache_item_->IsCacheChildren()) { + SkPaint sk_paint; + if (layer_raster_cache_item_->Draw(context, + context.state_stack.fill(sk_paint))) { + return; + } } - if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { + } + + // Now apply the color filter and then try rendering children either from + // cache or directly. + mutator.applyColorFilter(paint_bounds(), filter_); + + if (context.raster_cache && layer_raster_cache_item_->IsCacheChildren()) { + SkPaint sk_paint; + if (layer_raster_cache_item_->Draw(context, + context.state_stack.fill(sk_paint))) { return; } } - AutoCachePaint cache_paint(context); - cache_paint.setColorFilter(filter_.get()); - if (context.leaf_nodes_builder) { - FML_DCHECK(context.builder_multiplexer); - context.builder_multiplexer->saveLayer(&paint_bounds(), - cache_paint.dl_paint()); - PaintChildren(context); - context.builder_multiplexer->restore(); - } else { - Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( - context, paint_bounds(), cache_paint.sk_paint()); - PaintChildren(context); - } + PaintChildren(context); } } // namespace flutter diff --git a/flow/layers/color_filter_layer.h b/flow/layers/color_filter_layer.h index 62f702b33e01a..e105e12550029 100644 --- a/flow/layers/color_filter_layer.h +++ b/flow/layers/color_filter_layer.h @@ -17,7 +17,7 @@ class ColorFilterLayer : public CacheableContainerLayer { void Diff(DiffContext* context, const Layer* old_layer) override; - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) 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 index e45604a45b939..433606428f6bb 100644 --- a/flow/layers/color_filter_layer_unittests.cc +++ b/flow/layers/color_filter_layer_unittests.cc @@ -31,7 +31,7 @@ TEST_F(ColorFilterLayerTest, PaintingEmptyLayerDies) { auto layer = std::make_shared( std::make_shared(sk_sp())); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -65,7 +65,8 @@ TEST_F(ColorFilterLayerTest, EmptyFilter) { auto layer = std::make_shared(nullptr); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -74,14 +75,11 @@ TEST_F(ColorFilterLayerTest, EmptyFilter) { 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}}})); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({ + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path, child_paint}}, + })); } TEST_F(ColorFilterLayerTest, SimpleFilter) { @@ -95,7 +93,8 @@ TEST_F(ColorFilterLayerTest, SimpleFilter) { auto layer = std::make_shared(dl_color_filter); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -137,7 +136,8 @@ TEST_F(ColorFilterLayerTest, MultipleChildren) { SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); 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); @@ -191,7 +191,8 @@ TEST_F(ColorFilterLayerTest, Nested) { SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); - layer1->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer1->Preroll(preroll_context()); 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); @@ -244,7 +245,8 @@ TEST_F(ColorFilterLayerTest, Readback) { auto layer = std::make_shared( DlLinearToSrgbGammaColorFilter::instance); preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); // ColorFilterLayer blocks child with readback @@ -252,7 +254,7 @@ TEST_F(ColorFilterLayerTest, Readback) { mock_layer->set_fake_reads_surface(true); layer->Add(mock_layer); preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); } @@ -280,7 +282,8 @@ TEST_F(ColorFilterLayerTest, CacheChild) { RasterCacheItem::CacheState::kNone); EXPECT_FALSE(cacheable_color_filter_item->GetId().has_value()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); @@ -325,7 +328,8 @@ TEST_F(ColorFilterLayerTest, CacheChildren) { RasterCacheItem::CacheState::kNone); EXPECT_FALSE(cacheable_color_filter_item->GetId().has_value()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); @@ -361,13 +365,14 @@ TEST_F(ColorFilterLayerTest, CacheColorFilterLayerSelf) { other_canvas.setMatrix(other_transform); use_mock_raster_cache(); + preroll_context()->state_stack.set_initial_transform(initial_transform); const auto* cacheable_color_filter_item = layer->raster_cache_item(); // frame 1. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); // frame 2. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); // ColorFilterLayer default cache children. EXPECT_EQ(cacheable_color_filter_item->cache_state(), @@ -379,7 +384,7 @@ TEST_F(ColorFilterLayerTest, CacheColorFilterLayerSelf) { layer->Paint(paint_context()); // frame 3. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); @@ -416,18 +421,19 @@ TEST_F(ColorFilterLayerTest, OpacityInheritance) { color_filter_layer->Add(mock_layer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - color_filter_layer->Preroll(preroll_context(), initial_transform); + context->state_stack.set_initial_transform(initial_transform); + color_filter_layer->Preroll(preroll_context()); // ColorFilterLayer can always inherit opacity whether or not their // children are compatible. - EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(color_filter_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + preroll_context()->state_stack.set_initial_transform(SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index cd1157565ddb9..74829417f2432 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -107,9 +107,9 @@ void ContainerLayer::Add(std::shared_ptr layer) { layers_.emplace_back(std::move(layer)); } -void ContainerLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { +void ContainerLayer::Preroll(PrerollContext* context) { SkRect child_paint_bounds = SkRect::MakeEmpty(); - PrerollChildren(context, matrix, &child_paint_bounds); + PrerollChildren(context, &child_paint_bounds); set_paint_bounds(child_paint_bounds); } @@ -127,7 +127,6 @@ static bool safe_intersection_test(const SkRect* rect1, const SkRect& rect2) { } void ContainerLayer::PrerollChildren(PrerollContext* context, - const SkMatrix& child_matrix, SkRect* child_paint_bounds) { // Platform views have no children, so context->has_platform_view should // always be false. @@ -136,7 +135,7 @@ void ContainerLayer::PrerollChildren(PrerollContext* context, bool child_has_platform_view = false; bool child_has_texture_layer = false; - bool subtree_can_inherit_opacity = context->subtree_can_inherit_opacity; + bool all_renderable_state_flags = LayerStateStack::kCallerCanApplyAnything; for (auto& layer : layers_) { // Reset context->has_platform_view and context->has_texture_layer to false @@ -145,20 +144,18 @@ void ContainerLayer::PrerollChildren(PrerollContext* context, context->has_platform_view = false; context->has_texture_layer = false; - // Initialize the "inherit opacity" flag to false and allow the layer to - // override the answer during its |Preroll| - context->subtree_can_inherit_opacity = false; + // Initialize the renderable state flags to false to force the layer to + // opt-in to applying state attributes during its |Preroll| + context->renderable_state_flags = 0; - layer->Preroll(context, child_matrix); + layer->Preroll(context); - subtree_can_inherit_opacity = - subtree_can_inherit_opacity && context->subtree_can_inherit_opacity; - if (subtree_can_inherit_opacity && - safe_intersection_test(child_paint_bounds, layer->paint_bounds())) { + all_renderable_state_flags &= context->renderable_state_flags; + if (safe_intersection_test(child_paint_bounds, layer->paint_bounds())) { // This will allow inheritance by a linear sequence of non-overlapping // children, but will fail with a grid or other arbitrary 2D layout. // See https://github.com/flutter/flutter/issues/93899 - subtree_can_inherit_opacity = false; + all_renderable_state_flags = 0; } child_paint_bounds->join(layer->paint_bounds()); @@ -170,9 +167,10 @@ void ContainerLayer::PrerollChildren(PrerollContext* context, context->has_platform_view = child_has_platform_view; context->has_texture_layer = child_has_texture_layer; - context->subtree_can_inherit_opacity = subtree_can_inherit_opacity; + context->renderable_state_flags = all_renderable_state_flags; set_subtree_has_platform_view(child_has_platform_view); - child_paint_bounds_ = *child_paint_bounds; + set_children_renderable_state_flags(all_renderable_state_flags); + set_child_paint_bounds(*child_paint_bounds); } void ContainerLayer::PaintChildren(PaintContext& context) const { @@ -182,6 +180,11 @@ void ContainerLayer::PaintChildren(PaintContext& context) const { // layer calls PaintChildren(), though, it may have modified the // PaintContext so the test doesn't work in this "context". + // Apply any outstanding state that the children cannot individually + // and collectively handle. + auto restore = context.state_stack.applyState( + child_paint_bounds(), children_renderable_state_flags()); + // Intentionally not tracing here as there should be no self-time // and the trace event on this common function has a small overhead. for (auto& layer : layers_) { diff --git a/flow/layers/container_layer.h b/flow/layers/container_layer.h index cb3d497aa642a..638d951e1568f 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -20,7 +20,7 @@ class ContainerLayer : public Layer { virtual void Add(std::shared_ptr layer); - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; const std::vector>& layers() const { return layers_; } @@ -33,15 +33,24 @@ class ContainerLayer : public Layer { const ContainerLayer* as_container_layer() const override { return this; } const SkRect& child_paint_bounds() const { return child_paint_bounds_; } + void set_child_paint_bounds(const SkRect& bounds) { + child_paint_bounds_ = bounds; + } + + int children_renderable_state_flags() const { + return children_renderable_state_flags_; + } + void set_children_renderable_state_flags(int flags) { + children_renderable_state_flags_ = flags; + } protected: - void PrerollChildren(PrerollContext* context, - const SkMatrix& child_matrix, - SkRect* child_paint_bounds); + void PrerollChildren(PrerollContext* context, SkRect* child_paint_bounds); private: std::vector> layers_; SkRect child_paint_bounds_; + int children_renderable_state_flags_ = 0; FML_DISALLOW_COPY_AND_ASSIGN(ContainerLayer); }; diff --git a/flow/layers/container_layer_unittests.cc b/flow/layers/container_layer_unittests.cc index b2b4adc936ccd..fea3a66f58e63 100644 --- a/flow/layers/container_layer_unittests.cc +++ b/flow/layers/container_layer_unittests.cc @@ -25,7 +25,7 @@ TEST_F(ContainerLayerTest, LayerWithParentHasPlatformView) { auto layer = std::make_shared(); preroll_context()->has_platform_view = true; - EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()), "!context->has_platform_view"); } @@ -33,14 +33,14 @@ TEST_F(ContainerLayerTest, LayerWithParentHasTextureLayer) { auto layer = std::make_shared(); preroll_context()->has_texture_layer = true; - EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()), "!context->has_texture_layer"); } TEST_F(ContainerLayerTest, PaintingEmptyLayerDies) { auto layer = std::make_shared(); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty()); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -84,7 +84,7 @@ TEST_F(ContainerLayerTest, LayerWithParentHasTextureLayerNeedsResetFlag) { container_layer2->Add(mock_layer2); EXPECT_EQ(preroll_context()->has_texture_layer, false); - root->Preroll(preroll_context(), SkMatrix()); + root->Preroll(preroll_context()); EXPECT_EQ(preroll_context()->has_texture_layer, true); // The flag for holding texture layer from parent needs to be clear EXPECT_EQ(mock_layer2->parent_has_texture_layer(), false); @@ -100,7 +100,8 @@ TEST_F(ContainerLayerTest, Simple) { auto layer = std::make_shared(); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->has_platform_view); EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); EXPECT_EQ(layer->paint_bounds(), child_path.getBounds()); @@ -134,7 +135,8 @@ TEST_F(ContainerLayerTest, Multiple) { SkRect expected_total_bounds = child_path1.getBounds(); expected_total_bounds.join(child_path2.getBounds()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); 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()); @@ -171,7 +173,8 @@ TEST_F(ContainerLayerTest, MultipleWithEmpty) { layer->Add(mock_layer1); layer->Add(mock_layer2); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->has_platform_view); EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); EXPECT_EQ(mock_layer2->paint_bounds(), SkPath().getBounds()); @@ -209,7 +212,8 @@ TEST_F(ContainerLayerTest, NeedsSystemComposite) { SkRect expected_total_bounds = child_path1.getBounds(); expected_total_bounds.join(child_path2.getBounds()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); 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()); @@ -286,7 +290,7 @@ TEST_F(ContainerLayerTest, RasterCacheTest) { cache_canvas.setMatrix(SkMatrix::I()); // Initial Preroll for check the layer paint bounds - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer1->paint_bounds(), SkRect::MakeLTRB(5.f, 6.f, 20.5f, 21.5f)); @@ -304,7 +308,7 @@ TEST_F(ContainerLayerTest, RasterCacheTest) { // frame1 use_mock_raster_cache(); preroll_context()->raster_cache->BeginFrame(); - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); preroll_context()->raster_cache->EvictUnusedCacheEntries(); // Cache the cacheable entries LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries), @@ -344,7 +348,7 @@ TEST_F(ContainerLayerTest, RasterCacheTest) { // clear the cached_entries preroll_context()->raster_cached_entries->clear(); preroll_context()->raster_cache->BeginFrame(); - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); preroll_context()->raster_cache->EvictUnusedCacheEntries(); // Cache the cacheable entries @@ -388,7 +392,7 @@ TEST_F(ContainerLayerTest, RasterCacheTest) { // clear the cached_entries preroll_context()->raster_cache->BeginFrame(); preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); preroll_context()->raster_cache->EvictUnusedCacheEntries(); // Cache the cacheable entries LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries), @@ -429,7 +433,7 @@ TEST_F(ContainerLayerTest, RasterCacheTest) { preroll_context()->raster_cache->BeginFrame(); // frame4 preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); preroll_context()->raster_cache->EvictUnusedCacheEntries(); LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries), &paint_context()); @@ -438,7 +442,7 @@ TEST_F(ContainerLayerTest, RasterCacheTest) { // frame5 preroll_context()->raster_cache->BeginFrame(); preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries), &paint_context()); preroll_context()->raster_cache->EndFrame(); @@ -446,7 +450,7 @@ TEST_F(ContainerLayerTest, RasterCacheTest) { // frame6 preroll_context()->raster_cache->BeginFrame(); preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries), &paint_context()); preroll_context()->raster_cache->EndFrame(); @@ -459,22 +463,21 @@ TEST_F(ContainerLayerTest, OpacityInheritance) { auto container1 = std::make_shared(); container1->Add(mock1); - // ContainerLayer will not pass through compatibility on its own - // Subclasses must explicitly enable this in their own Preroll + // ContainerLayer will pass through compatibility PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - container1->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + container1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path2 = SkPath().addRect({40, 40, 50, 50}); auto mock2 = MockLayer::MakeOpacityCompatible(path2); container1->Add(mock2); // ContainerLayer will pass through compatibility from multiple - // non-overlapping compatible children if the caller enables it - context->subtree_can_inherit_opacity = true; - container1->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + // non-overlapping compatible children + container1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path3 = SkPath().addRect({20, 20, 40, 40}); auto mock3 = MockLayer::MakeOpacityCompatible(path3); @@ -482,31 +485,28 @@ TEST_F(ContainerLayerTest, OpacityInheritance) { // ContainerLayer will not pass through compatibility from multiple // overlapping children even if they are individually compatible - // and the caller requests it - context->subtree_can_inherit_opacity = true; - container1->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + container1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); auto container2 = std::make_shared(); container2->Add(mock1); container2->Add(mock2); // Double check first two children are compatible and non-overlapping - // if the caller requests it - context->subtree_can_inherit_opacity = true; - container2->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + container2->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path4 = SkPath().addRect({60, 60, 70, 70}); auto mock4 = MockLayer::Make(path4); container2->Add(mock4); // The third child is non-overlapping, but not compatible so the - // TransformLayer should end up incompatible - context->subtree_can_inherit_opacity = true; - container2->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + // ContainerLayer should end up incompatible + container2->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); } + TEST_F(ContainerLayerTest, CollectionCacheableLayer) { SkPath child_path; child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); @@ -529,14 +529,15 @@ TEST_F(ContainerLayerTest, CollectionCacheableLayer) { layer->Add(mock_cacheable_container_layer1); layer->Add(mock_layer1); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); // raster cache is null, so no entry ASSERT_EQ(preroll_context()->raster_cached_entries->size(), static_cast(0)); use_mock_raster_cache(); // preroll_context()->raster_cache = raster_cache(); - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); ASSERT_EQ(preroll_context()->raster_cached_entries->size(), static_cast(2)); } diff --git a/flow/layers/display_list_layer.cc b/flow/layers/display_list_layer.cc index 0d75bd88ff457..cf2b314d8e359 100644 --- a/flow/layers/display_list_layer.cc +++ b/flow/layers/display_list_layer.cc @@ -95,15 +95,13 @@ bool DisplayListLayer::Compare(DiffContext::Statistics& statistics, return res; } -void DisplayListLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void DisplayListLayer::Preroll(PrerollContext* context) { DisplayList* disp_list = display_list(); - SkMatrix child_matrix = matrix; - AutoCache cache = - AutoCache(display_list_raster_cache_item_.get(), context, child_matrix); + AutoCache cache = AutoCache(display_list_raster_cache_item_.get(), context, + context->state_stack.transform_3x3()); if (disp_list->can_apply_group_opacity()) { - context->subtree_can_inherit_opacity = true; + context->renderable_state_flags = LayerStateStack::kCallerCanApplyOpacity; } set_paint_bounds(bounds_); } @@ -112,28 +110,32 @@ void DisplayListLayer::Paint(PaintContext& context) const { FML_DCHECK(display_list_.skia_object()); FML_DCHECK(needs_painting(context)); - SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); - context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); - if (context.raster_cache) { - context.leaf_nodes_canvas->setMatrix(RasterCacheUtil::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); - } + auto mutator = context.state_stack.save(); + mutator.translate(offset_.x(), offset_.y()); - if (context.raster_cache && display_list_raster_cache_item_) { - AutoCachePaint cache_paint(context); - if (display_list_raster_cache_item_->Draw(context, - cache_paint.sk_paint())) { - TRACE_EVENT_INSTANT0("flutter", "raster cache hit"); - return; + if (context.raster_cache) { + // Always apply the integral transform in the presence of a raster cache + // whether or not we successfully draw from the cache + mutator.integralTransform(); + + if (display_list_raster_cache_item_) { + SkPaint paint; + if (display_list_raster_cache_item_->Draw( + context, context.state_stack.fill(paint))) { + TRACE_EVENT_INSTANT0("flutter", "raster cache hit"); + return; + } } } + SkScalar opacity = context.state_stack.outstanding_opacity(); + if (context.enable_leaf_layer_tracing) { - const auto canvas_size = context.leaf_nodes_canvas->getBaseLayerSize(); + const auto canvas_size = context.canvas->getBaseLayerSize(); auto offscreen_surface = std::make_unique(context.gr_context, canvas_size); - const auto& ctm = context.leaf_nodes_canvas->getTotalMatrix(); + const auto& ctm = context.canvas->getTotalMatrix(); const auto start_time = fml::TimePoint::Now(); { @@ -142,7 +144,7 @@ void DisplayListLayer::Paint(PaintContext& context) const { SkAutoCanvasRestore save(canvas, true); canvas->clear(SK_ColorTRANSPARENT); canvas->setMatrix(ctm); - display_list()->RenderTo(canvas, context.inherited_opacity); + display_list()->RenderTo(canvas, opacity); canvas->flush(); } const fml::TimeDelta offscreen_render_time = @@ -156,18 +158,12 @@ void DisplayListLayer::Paint(PaintContext& context) const { context.layer_snapshot_store->Add(snapshot_data); } - if (context.leaf_nodes_builder) { - AutoCachePaint save_paint(context); - int restore_count = context.leaf_nodes_builder->getSaveCount(); - if (save_paint.dl_paint() != nullptr) { - context.leaf_nodes_builder->saveLayer(&paint_bounds(), - save_paint.dl_paint()); - } - context.leaf_nodes_builder->drawDisplayList(display_list_.skia_object()); - context.leaf_nodes_builder->restoreToCount(restore_count); + if (context.builder) { + auto display_list = display_list_.skia_object(); + auto restore = context.state_stack.applyState(display_list->bounds(), 0); + context.builder->drawDisplayList(display_list); } else { - display_list()->RenderTo(context.leaf_nodes_canvas, - context.inherited_opacity); + display_list()->RenderTo(context.canvas, opacity); } } diff --git a/flow/layers/display_list_layer.h b/flow/layers/display_list_layer.h index c8066d0d43baf..fd981e167a556 100644 --- a/flow/layers/display_list_layer.h +++ b/flow/layers/display_list_layer.h @@ -36,7 +36,7 @@ class DisplayListLayer : public Layer { return this; } - void Preroll(PrerollContext* frame, const SkMatrix& matrix) override; + void Preroll(PrerollContext* frame) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/display_list_layer_unittests.cc b/flow/layers/display_list_layer_unittests.cc index 4db87af8d0451..300d9d6eaf08a 100644 --- a/flow/layers/display_list_layer_unittests.cc +++ b/flow/layers/display_list_layer_unittests.cc @@ -52,7 +52,7 @@ TEST_F(DisplayListLayerTest, PaintingEmptyLayerDies) { layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -66,7 +66,7 @@ TEST_F(DisplayListLayerTest, InvalidDisplayListDies) { layer_offset, SkiaGPUObject(), false, false); // Crashes reading a nullptr. - EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), ""); + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()), ""); } #endif @@ -81,7 +81,7 @@ TEST_F(DisplayListLayerTest, SimpleDisplayList) { auto layer = std::make_shared( layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), picture_bounds.makeOffset(layer_offset.fX, layer_offset.fY)); EXPECT_EQ(layer->display_list(), display_list.get()); @@ -109,25 +109,22 @@ TEST_F(DisplayListLayerTest, SimpleDisplayListOpacityInheritance) { EXPECT_TRUE(display_list->can_apply_group_opacity()); auto context = preroll_context(); - context->subtree_can_inherit_opacity = false; - display_list_layer->Preroll(preroll_context(), SkMatrix()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + display_list_layer->Preroll(preroll_context()); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); int opacity_alpha = 0x7F; SkPoint opacity_offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, opacity_offset); opacity_layer->Add(display_list_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder child_builder; child_builder.drawRect(picture_bounds); auto child_display_list = child_builder.Build(); - auto save_layer_bounds = - picture_bounds.makeOffset(layer_offset.fX, layer_offset.fY); DisplayListBuilder expected_builder; /* opacity_layer::Paint() */ { expected_builder.save(); @@ -138,7 +135,7 @@ TEST_F(DisplayListLayerTest, SimpleDisplayListOpacityInheritance) { { expected_builder.translate(layer_offset.fX, layer_offset.fY); expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&save_layer_bounds, true); + expected_builder.saveLayer(&picture_bounds, true); /* display_list contents */ { // expected_builder.drawDisplayList(child_display_list); } @@ -152,7 +149,7 @@ TEST_F(DisplayListLayerTest, SimpleDisplayListOpacityInheritance) { opacity_layer->Paint(display_list_paint_context()); EXPECT_TRUE( - DisplayListsEQ_Verbose(expected_builder.Build(), this->display_list())); + DisplayListsEQ_Verbose(this->display_list(), expected_builder.Build())); } TEST_F(DisplayListLayerTest, IncompatibleDisplayListOpacityInheritance) { @@ -168,17 +165,15 @@ TEST_F(DisplayListLayerTest, IncompatibleDisplayListOpacityInheritance) { EXPECT_FALSE(display_list->can_apply_group_opacity()); auto context = preroll_context(); - context->subtree_can_inherit_opacity = false; - display_list_layer->Preroll(preroll_context(), SkMatrix()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + display_list_layer->Preroll(preroll_context()); + EXPECT_EQ(context->renderable_state_flags, 0); int opacity_alpha = 0x7F; SkPoint opacity_offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, opacity_offset); opacity_layer->Add(display_list_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_FALSE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder child_builder; @@ -190,7 +185,6 @@ TEST_F(DisplayListLayerTest, IncompatibleDisplayListOpacityInheritance) { display_list_bounds.join(picture2_bounds); auto save_layer_bounds = display_list_bounds.makeOffset(layer_offset.fX, layer_offset.fY); - save_layer_bounds.roundOut(&save_layer_bounds); DisplayListBuilder expected_builder; /* opacity_layer::Paint() */ { expected_builder.save(); @@ -215,7 +209,7 @@ TEST_F(DisplayListLayerTest, IncompatibleDisplayListOpacityInheritance) { opacity_layer->Paint(display_list_paint_context()); EXPECT_TRUE( - DisplayListsEQ_Verbose(expected_builder.Build(), this->display_list())); + DisplayListsEQ_Verbose(this->display_list(), expected_builder.Build())); } TEST_F(DisplayListLayerTest, CachedIncompatibleDisplayListOpacityInheritance) { @@ -233,22 +227,20 @@ TEST_F(DisplayListLayerTest, CachedIncompatibleDisplayListOpacityInheritance) { use_skia_raster_cache(); auto context = preroll_context(); - context->subtree_can_inherit_opacity = false; - display_list_layer->Preroll(preroll_context(), SkMatrix()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + display_list_layer->Preroll(preroll_context()); + EXPECT_EQ(context->renderable_state_flags, 0); // Pump the DisplayListLayer until it is ready to cache its DL - display_list_layer->Preroll(preroll_context(), SkMatrix()); - display_list_layer->Preroll(preroll_context(), SkMatrix()); - display_list_layer->Preroll(preroll_context(), SkMatrix()); + display_list_layer->Preroll(preroll_context()); + display_list_layer->Preroll(preroll_context()); + display_list_layer->Preroll(preroll_context()); int opacity_alpha = 0x7F; SkPoint opacity_offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, opacity_offset); opacity_layer->Add(display_list_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); // The following would be a great test of the painting of the above @@ -387,7 +379,7 @@ TEST_F(DisplayListLayerTest, LayerTreeSnapshotsWhenEnabled) { auto layer = std::make_shared( layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); enable_leaf_layer_tracing(); layer->Paint(paint_context()); @@ -406,7 +398,7 @@ TEST_F(DisplayListLayerTest, NoLayerTreeSnapshotsWhenDisabledByDefault) { auto layer = std::make_shared( layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); auto& snapshot_store = layer_snapshot_store(); @@ -430,10 +422,10 @@ TEST_F(DisplayListLayerTest, DisplayListAccessCountDependsOnVisibility) { // First Preroll the DisplayListLayer a few times where it does not intersect // the cull rect. No caching progress should occur during this time, the // access_count should remain 0 because the DisplayList was never "visible". - preroll_context()->cull_rect = missed_cull_rect; + preroll_context()->state_stack.set_initial_cull_rect(missed_cull_rect); for (int i = 0; i < 10; i++) { preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix::I()); + layer->Preroll(preroll_context()); ASSERT_EQ(raster_cache_item->cache_state(), RasterCacheItem::kNone); ASSERT_TRUE(raster_cache_item->GetId().has_value()); ASSERT_EQ(preroll_context()->raster_cache->GetAccessCount( @@ -450,9 +442,9 @@ TEST_F(DisplayListLayerTest, DisplayListAccessCountDependsOnVisibility) { // the cull rect. No caching progress should occur during this time // since this is the first frame in which it was visible, but the // count should start incrementing. - preroll_context()->cull_rect = hit_cull_rect; + preroll_context()->state_stack.set_initial_cull_rect(hit_cull_rect); preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); ASSERT_EQ(raster_cache_item->cache_state(), RasterCacheItem::kNone); ASSERT_TRUE(raster_cache_item->GetId().has_value()); ASSERT_EQ(preroll_context()->raster_cache->GetAccessCount( @@ -468,10 +460,10 @@ TEST_F(DisplayListLayerTest, DisplayListAccessCountDependsOnVisibility) { // it does not intersect and it should continue to count these operations // even though it is not visible. No actual caching should occur yet, // even though we will surpass its threshold. - preroll_context()->cull_rect = missed_cull_rect; + preroll_context()->state_stack.set_initial_cull_rect(missed_cull_rect); for (int i = 0; i < 10; i++) { preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); ASSERT_EQ(raster_cache_item->cache_state(), RasterCacheItem::kNone); ASSERT_TRUE(raster_cache_item->GetId().has_value()); ASSERT_EQ(preroll_context()->raster_cache->GetAccessCount( @@ -488,9 +480,9 @@ TEST_F(DisplayListLayerTest, DisplayListAccessCountDependsOnVisibility) { // the cull rect. Since we should have exhausted our access count // threshold in the loop above, these operations should result in the // DisplayList being cached. - preroll_context()->cull_rect = hit_cull_rect; + preroll_context()->state_stack.set_initial_cull_rect(hit_cull_rect); preroll_context()->raster_cached_entries->clear(); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); ASSERT_EQ(raster_cache_item->cache_state(), RasterCacheItem::kCurrent); ASSERT_TRUE(raster_cache_item->GetId().has_value()); ASSERT_EQ(preroll_context()->raster_cache->GetAccessCount( diff --git a/flow/layers/display_list_raster_cache_item.cc b/flow/layers/display_list_raster_cache_item.cc index 10a64cff3a45e..cb774e4498dd8 100644 --- a/flow/layers/display_list_raster_cache_item.cc +++ b/flow/layers/display_list_raster_cache_item.cc @@ -108,12 +108,12 @@ void DisplayListRasterCacheItem::PrerollFinalize(PrerollContext* context, // if the rect is intersect we will get the entry access_count to confirm if // it great than the threshold. Otherwise we only increase the entry // access_count. - bool visible = context->cull_rect.intersect(bounds); + bool visible = !context->state_stack.content_culled(bounds); int accesses = raster_cache->MarkSeen(key_id_, matrix, visible); if (!visible || accesses <= raster_cache->access_threshold()) { cache_state_ = kNone; } else { - context->subtree_can_inherit_opacity = true; + context->renderable_state_flags |= LayerStateStack::kCallerCanApplyOpacity; cache_state_ = kCurrent; } return; @@ -121,7 +121,7 @@ void DisplayListRasterCacheItem::PrerollFinalize(PrerollContext* context, bool DisplayListRasterCacheItem::Draw(const PaintContext& context, const SkPaint* paint) const { - return Draw(context, context.leaf_nodes_canvas, paint); + return Draw(context, context.canvas, paint); } bool DisplayListRasterCacheItem::Draw(const PaintContext& context, diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index b08baad68f85e..79a3c4c27329f 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -46,28 +46,29 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void ImageFilterLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void ImageFilterLayer::Preroll(PrerollContext* context) { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); - SkMatrix child_matrix = matrix; - AutoCache cache = - AutoCache(layer_raster_cache_item_.get(), context, child_matrix); + AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context, + context->state_stack.transform_3x3()); SkRect child_bounds = SkRect::MakeEmpty(); - PrerollChildren(context, child_matrix, &child_bounds); - - // We always paint with a saveLayer (or a cached rendering), - // so we can always apply opacity in any of those cases. - context->subtree_can_inherit_opacity = true; + PrerollChildren(context, &child_bounds); if (!filter_) { set_paint_bounds(child_bounds); return; } + // Our saveLayer would apply any outstanding opacity or any outstanding + // color filter after it applies our image filter. So we can apply either + // of those attributes with our saveLayer. + context->renderable_state_flags = + (LayerStateStack::kCallerCanApplyOpacity | + LayerStateStack::kCallerCanApplyColorFilter); + const SkIRect filter_in_bounds = child_bounds.roundOut(); SkIRect filter_out_bounds; filter_->map_device_bounds(filter_in_bounds, SkMatrix::I(), @@ -80,7 +81,8 @@ void ImageFilterLayer::Preroll(PrerollContext* context, // So in here we reset the LayerRasterCacheItem cache state. layer_raster_cache_item_->MarkNotCacheChildren(); - transformed_filter_ = filter_->makeWithLocalMatrix(child_matrix); + transformed_filter_ = + filter_->makeWithLocalMatrix(context->state_stack.transform_3x3()); if (transformed_filter_) { layer_raster_cache_item_->MarkCacheChildren(); } @@ -89,35 +91,41 @@ void ImageFilterLayer::Preroll(PrerollContext* context, void ImageFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); - AutoCachePaint cache_paint(context); + auto mutator = context.state_stack.save(); + if (context.raster_cache) { - context.internal_nodes_canvas->setMatrix( - RasterCacheUtil::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); - if (layer_raster_cache_item_->IsCacheChildren()) { - cache_paint.setImageFilter(transformed_filter_.get()); + // Always apply the integral transform in the presence of a raster cache + // whether or not we will draw from the cache + mutator.integralTransform(); + + // Try drawing the layer cache item from the cache before applying the + // image filter if it was cached with the filter applied. + if (!layer_raster_cache_item_->IsCacheChildren()) { + SkPaint sk_paint; + if (layer_raster_cache_item_->Draw(context, + context.state_stack.fill(sk_paint))) { + return; + } } - if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { + } + + if (context.raster_cache && layer_raster_cache_item_->IsCacheChildren()) { + // If we render the children from cache then we need the special + // transformed version of the filter so we must process it into the + // cache paint object manually. + FML_DCHECK(transformed_filter_ != nullptr); + SkPaint sk_paint; + context.state_stack.fill(sk_paint); + sk_paint.setImageFilter(transformed_filter_->skia_object()); + if (layer_raster_cache_item_->Draw(context, &sk_paint)) { return; } } - cache_paint.setImageFilter(filter_.get()); - if (context.leaf_nodes_builder) { - FML_DCHECK(context.builder_multiplexer); - context.builder_multiplexer->saveLayer(&child_paint_bounds(), - cache_paint.dl_paint()); - PaintChildren(context); - context.builder_multiplexer->restore(); - } else { - // Normally a save_layer is sized to the current layer bounds, but in this - // case the bounds of the child may not be the same as the filtered version - // so we use the bounds of the child container which do not include any - // modifications that the filter might apply. - Layer::AutoSaveLayer save_layer = Layer::AutoSaveLayer::Create( - context, child_paint_bounds(), cache_paint.sk_paint()); - PaintChildren(context); - } + // Now apply the image filter and then try rendering the children. + mutator.applyImageFilter(child_paint_bounds(), filter_); + + PaintChildren(context); } } // namespace flutter diff --git a/flow/layers/image_filter_layer.h b/flow/layers/image_filter_layer.h index e4987bd9fb75b..894b5d2e98187 100644 --- a/flow/layers/image_filter_layer.h +++ b/flow/layers/image_filter_layer.h @@ -19,7 +19,7 @@ class ImageFilterLayer : public CacheableContainerLayer { void Diff(DiffContext* context, const Layer* old_layer) override; - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc index dc855affc15fe..82e5fd93b70b7 100644 --- a/flow/layers/image_filter_layer_unittests.cc +++ b/flow/layers/image_filter_layer_unittests.cc @@ -26,7 +26,7 @@ using ImageFilterLayerTest = LayerTest; TEST_F(ImageFilterLayerTest, PaintingEmptyLayerDies) { auto layer = std::make_shared(nullptr); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -57,7 +57,8 @@ TEST_F(ImageFilterLayerTest, EmptyFilter) { auto layer = std::make_shared(nullptr); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -69,11 +70,7 @@ TEST_F(ImageFilterLayerTest, EmptyFilter) { 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}}, + 0, MockCanvas::DrawPathData{child_path, child_paint}}, })); } @@ -91,7 +88,8 @@ TEST_F(ImageFilterLayerTest, SimpleFilter) { const SkRect child_rounded_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 21.0f, 22.0f); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_rounded_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -131,7 +129,8 @@ TEST_F(ImageFilterLayerTest, SimpleFilterBounds) { const SkRect filter_bounds = SkRect::MakeLTRB(10.0f, 12.0f, 42.0f, 44.0f); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), filter_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -176,7 +175,8 @@ TEST_F(ImageFilterLayerTest, MultipleChildren) { children_bounds.join(child_path2.getBounds()); SkRect children_rounded_bounds = SkRect::Make(children_bounds.roundOut()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); EXPECT_EQ(layer->paint_bounds(), children_rounded_bounds); @@ -237,7 +237,8 @@ TEST_F(ImageFilterLayerTest, Nested) { const SkRect mock_layer2_rounded_bounds = SkRect::Make(child_path2.getBounds().roundOut()); - layer1->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer1->Preroll(preroll_context()); EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); EXPECT_EQ(layer1->paint_bounds(), children_rounded_bounds); @@ -284,12 +285,10 @@ TEST_F(ImageFilterLayerTest, Readback) { auto dl_image_filter = std::make_shared( SkMatrix(), DlImageSampling::kLinear); - auto initial_transform = SkMatrix(); - // ImageFilterLayer does not read from surface auto layer = std::make_shared(dl_image_filter); preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); // ImageFilterLayer blocks child with readback @@ -297,7 +296,7 @@ TEST_F(ImageFilterLayerTest, Readback) { mock_layer->set_fake_reads_surface(true); layer->Add(mock_layer); preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); } @@ -327,7 +326,8 @@ TEST_F(ImageFilterLayerTest, CacheChild) { RasterCacheItem::CacheState::kNone); EXPECT_FALSE(cacheable_image_filter_item->Draw(paint_context(), &paint)); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); @@ -371,7 +371,8 @@ TEST_F(ImageFilterLayerTest, CacheChildren) { RasterCacheItem::CacheState::kNone); EXPECT_FALSE(cacheable_image_filter_item->Draw(paint_context(), &paint)); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); @@ -405,15 +406,16 @@ TEST_F(ImageFilterLayerTest, CacheImageFilterLayerSelf) { SkPaint paint = SkPaint(); use_mock_raster_cache(); + preroll_context()->state_stack.set_initial_transform(initial_transform); const auto* cacheable_image_filter_item = layer->raster_cache_item(); // frame 1. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); // frame 2. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); // frame 3. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); @@ -446,18 +448,20 @@ TEST_F(ImageFilterLayerTest, OpacityInheritance) { image_filter_layer->Add(mock_layer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - image_filter_layer->Preroll(preroll_context(), initial_transform); + context->state_stack.set_initial_transform(initial_transform); + image_filter_layer->Preroll(preroll_context()); // ImageFilterLayers can always inherit opacity whether or not their // children are compatible. - EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity | + LayerStateStack::kCallerCanApplyColorFilter); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(image_filter_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + context->state_stack.set_initial_transform(SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index e96033776ff03..2e8beec7e6289 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -26,8 +26,6 @@ uint64_t Layer::NextUniqueID() { return id; } -void Layer::Preroll(PrerollContext* context, const SkMatrix& matrix) {} - Layer::AutoPrerollSaveLayerState::AutoPrerollSaveLayerState( PrerollContext* preroll_context, bool save_layer_is_active, @@ -56,51 +54,4 @@ Layer::AutoPrerollSaveLayerState::~AutoPrerollSaveLayerState() { } } -Layer::AutoSaveLayer::AutoSaveLayer(const PaintContext& paint_context, - const SkRect& bounds, - const SkPaint* paint, - SaveMode save_mode) - : paint_context_(paint_context), - bounds_(bounds), - canvas_(save_mode == SaveMode::kInternalNodesCanvas - ? *(paint_context.internal_nodes_canvas) - : *(paint_context.leaf_nodes_canvas)) { - TRACE_EVENT0("flutter", "Canvas::saveLayer"); - canvas_.saveLayer(bounds_, paint); -} - -Layer::AutoSaveLayer::AutoSaveLayer(const PaintContext& paint_context, - const SkCanvas::SaveLayerRec& layer_rec, - SaveMode save_mode) - : paint_context_(paint_context), - bounds_(*layer_rec.fBounds), - canvas_(save_mode == SaveMode::kInternalNodesCanvas - ? *(paint_context.internal_nodes_canvas) - : *(paint_context.leaf_nodes_canvas)) { - TRACE_EVENT0("flutter", "Canvas::saveLayer"); - canvas_.saveLayer(layer_rec); -} - -Layer::AutoSaveLayer Layer::AutoSaveLayer::Create( - const PaintContext& paint_context, - const SkRect& bounds, - const SkPaint* paint, - SaveMode save_mode) { - return Layer::AutoSaveLayer(paint_context, bounds, paint, save_mode); -} - -Layer::AutoSaveLayer Layer::AutoSaveLayer::Create( - const PaintContext& paint_context, - const SkCanvas::SaveLayerRec& layer_rec, - SaveMode save_mode) { - return Layer::AutoSaveLayer(paint_context, layer_rec, save_mode); -} - -Layer::AutoSaveLayer::~AutoSaveLayer() { - if (paint_context_.checkerboard_offscreen_layers) { - DrawCheckerboard(&canvas_, bounds_); - } - canvas_.restore(); -} - } // namespace flutter diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 09f47b5de4d50..ca568a3916e37 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -16,6 +16,7 @@ #include "flutter/flow/embedded_views.h" #include "flutter/flow/instrumentation.h" #include "flutter/flow/layer_snapshot_store.h" +#include "flutter/flow/layers/layer_state_stack.h" #include "flutter/flow/raster_cache.h" #include "flutter/fml/build_config.h" #include "flutter/fml/compiler_specific.h" @@ -52,16 +53,14 @@ struct PrerollContext { RasterCache* raster_cache; GrDirectContext* gr_context; ExternalViewEmbedder* view_embedder; - MutatorsStack& mutators_stack; + LayerStateStack& state_stack; SkColorSpace* dst_color_space; - SkRect cull_rect; bool surface_needs_readback; // These allow us to paint in the end of subtree Preroll. const Stopwatch& raster_time; const Stopwatch& ui_time; std::shared_ptr texture_registry; - const bool checkerboard_offscreen_layers; const float frame_device_pixel_ratio = 1.0f; // These allow us to track properties like elevation, opacity, and the @@ -71,42 +70,15 @@ struct PrerollContext { // presence of a texture layer during Preroll. bool has_texture_layer = false; - // This field indicates whether the subtree rooted at this layer can - // inherit an opacity value and modulate its visibility accordingly. - // - // Any layer is free to ignore this flag. Its value will be false upon - // entry into its Preroll method, it will remain false if it calls - // PrerollChildren on any children it might have, and it will remain - // false upon exit from the Preroll method unless it takes specific - // action compute if it should be true. Thus, this property is "opt-in". - // - // If the value is false when the Preroll method exits, then the - // |PaintContext::inherited_opacity| value should always be set to - // 1.0 when its |Paint| method is called. - // - // Leaf layers need only be concerned with their own rendering and - // can set the value according to whether they can apply the opacity. - // - // For containers, there are 3 ways to interact with this field: - // - // 1. If you need to know whether your children are compatible, then - // set the field to true before you call PrerollChildren. That - // method will then reset the field to false if it detects any - // incompatible children. - // - // 2. If your decision on whether to inherit the opacity depends on - // the answer from the children, then remember the value of the - // field when PrerollChildren returns. (eg. OpacityLayer remembers - // this value to control whether to set the opacity value into the - // |PaintContext::inherited_opacity| field in |Paint| before - // recursing to its children in Paint) - // - // 3. If you want to indicate to your parents that you can accept - // inherited opacity regardless of whether your children were - // compatible then set this field to true before returning - // from your Preroll method. (eg. layers that always apply a - // saveLayer when rendering anyway can apply the opacity there) - bool subtree_can_inherit_opacity = false; + // The list of flags that describe which rendering state attributes + // (such as opacity, ColorFilter, ImageFilter) a given layer can + // render itself without requiring the parent to perform a protective + // saveLayer with those attributes. + // For containers, the flags will be set to the intersection (logical + // and) of all of the state bits that all of the children can render + // or to 0 if some of the children overlap and, as such, cannot apply + // those attributes individually and separately. + int renderable_state_flags = 0; std::vector* raster_cached_entries; @@ -119,17 +91,21 @@ struct PrerollContext { struct PaintContext { // When splitting the scene into multiple canvases (e.g when embedding - // a platform view on iOS) during the paint traversal we apply the non leaf - // flow layers to all canvases, and leaf layers just to the "current" - // canvas. Applying the non leaf layers to all canvases ensures that when - // we switch a canvas (when painting a PlatformViewLayer) the next canvas - // has the exact same state as the current canvas. - // The internal_nodes_canvas is a SkNWayCanvas which is used by non leaf - // and applies the operations to all canvases. - // The leaf_nodes_canvas is the "current" canvas and is used by leaf - // layers. - SkCanvas* internal_nodes_canvas; - SkCanvas* leaf_nodes_canvas; + // a platform view on iOS) during the paint traversal we apply any state + // changes which affect children (i.e. saveLayer attributes) to the + // state_stack and any local rendering state changes for leaf layers to + // the canvas or builder. + // When we switch a canvas or builder (when painting a PlatformViewLayer) + // the new canvas receives all of the stateful changes from the state_stack + // to put it into the exact same state that the outgoing canvas had at the + // time it was swapped out. + // The state stack lazily applies saveLayer calls to its current canvas, + // allowing leaf layers to report that they can handle rendering some of + // its state attributes themselves via the |applyState| method. + LayerStateStack& state_stack; + SkCanvas* canvas; + DisplayListBuilder* builder = nullptr; + GrDirectContext* gr_context; SkColorSpace* dst_color_space; ExternalViewEmbedder* view_embedder; @@ -137,29 +113,32 @@ struct PaintContext { const Stopwatch& ui_time; std::shared_ptr texture_registry; const RasterCache* raster_cache; - const bool checkerboard_offscreen_layers; const float frame_device_pixel_ratio = 1.0f; // Snapshot store to collect leaf layer snapshots. The store is non-null // only when leaf layer tracing is enabled. LayerSnapshotStore* layer_snapshot_store = nullptr; bool enable_leaf_layer_tracing = false; - - // The following value should be used to modulate the opacity of the - // layer during |Paint|. If the layer does not set the corresponding - // |layer_can_inherit_opacity()| flag, then this value should always - // be |SK_Scalar1|. The value is to be applied as if by using a - // |saveLayer| with an |SkPaint| initialized to this alphaf value and - // a |kSrcOver| blend mode. - SkScalar inherited_opacity = SK_Scalar1; - DisplayListBuilder* leaf_nodes_builder = nullptr; - DisplayListBuilderMultiplexer* builder_multiplexer = nullptr; }; // Represents a single composited layer. Created on the UI thread but then // subsequently used on the Rasterizer thread. class Layer { public: + // The state attribute flags that represent which attributes a + // layer can render if it plans to use a saveLayer call in its + // |Paint| method. + static constexpr int kSaveLayerRenderFlags = + LayerStateStack::kCallerCanApplyOpacity | + LayerStateStack::kCallerCanApplyColorFilter | + LayerStateStack::kCallerCanApplyImageFilter; + + // The state attribute flags that represent which attributes a + // layer can render if it will be rendering its content/children + // from a cached representation. + static constexpr int kRasterCacheRenderFlags = + LayerStateStack::kCallerCanApplyOpacity; + Layer(); virtual ~Layer(); @@ -186,7 +165,7 @@ class Layer { context->SetLayerPaintRegion(this, context->GetOldLayerPaintRegion(this)); } - virtual void Preroll(PrerollContext* context, const SkMatrix& matrix); + virtual void Preroll(PrerollContext* context) = 0; // Used during Preroll by layers that employ a saveLayer to manage the // PrerollContext settings with values affected by the saveLayer mechanism. @@ -214,114 +193,6 @@ class Layer { bool prev_surface_needs_readback_; }; - class AutoCachePaint { - public: - explicit AutoCachePaint(PaintContext& context) : context_(context) { - needs_paint_ = context.inherited_opacity < SK_Scalar1; - if (needs_paint_) { - sk_paint_.setAlphaf(context.inherited_opacity); - dl_paint_.setAlpha(SkScalarRoundToInt(context.inherited_opacity * 255)); - context.inherited_opacity = SK_Scalar1; - } - } - - ~AutoCachePaint() { context_.inherited_opacity = sk_paint_.getAlphaf(); } - - void setImageFilter(const DlImageFilter* filter) { - sk_paint_.setImageFilter(!filter ? nullptr : filter->skia_object()); - dl_paint_.setImageFilter(filter); - update_needs_paint(); - } - - void setColorFilter(const DlColorFilter* filter) { - sk_paint_.setColorFilter(!filter ? nullptr : filter->skia_object()); - dl_paint_.setColorFilter(filter); - update_needs_paint(); - } - - void setBlendMode(DlBlendMode mode) { - sk_paint_.setBlendMode(ToSk(mode)); - dl_paint_.setBlendMode(mode); - update_needs_paint(); - } - - const SkPaint* sk_paint() { return needs_paint_ ? &sk_paint_ : nullptr; } - const DlPaint* dl_paint() { return needs_paint_ ? &dl_paint_ : nullptr; } - - private: - PaintContext& context_; - SkPaint sk_paint_; - DlPaint dl_paint_; - bool needs_paint_; - - void update_needs_paint() { - needs_paint_ = sk_paint_.getImageFilter() != nullptr || - sk_paint_.getColorFilter() != nullptr || - !sk_paint_.isSrcOver() || - sk_paint_.getAlphaf() < SK_Scalar1; - } - }; - - // Calls SkCanvas::saveLayer and restores the layer upon destruction. Also - // draws a checkerboard over the layer if that is enabled in the PaintContext. - class AutoSaveLayer { - public: - // Indicates which canvas the layer should be saved on. - // - // Usually layers are saved on the internal_nodes_canvas, so that all - // the canvas keep track of the current state of the layer tree. - // In some special cases, layers should only save on the leaf_nodes_canvas, - // See https:://flutter.dev/go/backdrop-filter-with-overlay-canvas for why - // it is the case for Backdrop filter layer. - enum SaveMode { - // The layer is saved on the internal_nodes_canvas. - kInternalNodesCanvas, - // The layer is saved on the leaf_nodes_canvas. - kLeafNodesCanvas - }; - - // Create a layer and save it on the canvas. - // - // The layer is restored from the canvas in destructor. - // - // By default, the layer is saved on and restored from - // `internal_nodes_canvas`. The `save_mode` parameter can be modified to - // save the layer on other canvases. - [[nodiscard]] static AutoSaveLayer Create( - const PaintContext& paint_context, - const SkRect& bounds, - const SkPaint* paint, - SaveMode save_mode = SaveMode::kInternalNodesCanvas); - // Create a layer and save it on the canvas. - // - // The layer is restored from the canvas in destructor. - // - // By default, the layer is saved on and restored from - // `internal_nodes_canvas`. The `save_mode` parameter can be modified to - // save the layer on other canvases. - [[nodiscard]] static AutoSaveLayer Create( - const PaintContext& paint_context, - const SkCanvas::SaveLayerRec& layer_rec, - SaveMode save_mode = SaveMode::kInternalNodesCanvas); - - ~AutoSaveLayer(); - - private: - AutoSaveLayer(const PaintContext& paint_context, - const SkRect& bounds, - const SkPaint* paint, - SaveMode save_mode = SaveMode::kInternalNodesCanvas); - - AutoSaveLayer(const PaintContext& paint_context, - const SkCanvas::SaveLayerRec& layer_rec, - SaveMode save_mode = SaveMode::kInternalNodesCanvas); - - const PaintContext& paint_context_; - const SkRect bounds_; - // The canvas that this layer is saved on and popped from. - SkCanvas& canvas_; - }; - virtual void Paint(PaintContext& context) const = 0; virtual void PaintChildren(PaintContext& context) const { FML_DCHECK(false); } @@ -368,15 +239,8 @@ class Layer { // See https://github.com/flutter/flutter/issues/81419 return true; } - if (context.inherited_opacity == 0) { - return false; - } - // Workaround for Skia bug (quickReject does not reject empty bounds). - // https://bugs.chromium.org/p/skia/issues/detail?id=10951 - if (paint_bounds_.isEmpty()) { - return false; - } - return !context.leaf_nodes_canvas->quickReject(paint_bounds_); + return !context.state_stack.painting_is_nop() && + !context.state_stack.content_culled(paint_bounds_); } // Propagated unique_id of the first layer in "chain" of replacement layers diff --git a/flow/layers/layer_raster_cache_item.cc b/flow/layers/layer_raster_cache_item.cc index 63c2ef70be774..9794fad46a970 100644 --- a/flow/layers/layer_raster_cache_item.cc +++ b/flow/layers/layer_raster_cache_item.cc @@ -47,7 +47,7 @@ void LayerRasterCacheItem::PrerollFinalize(PrerollContext* context, // alive, but if the following conditions apply then we need to set our // state back to kDoNotCache so that we don't populate the entry later. if (context->has_platform_view || context->has_texture_layer || - !SkRect::Intersects(context->cull_rect, layer_->paint_bounds())) { + context->state_stack.content_culled(layer_->paint_bounds())) { return; } child_items_ = context->raster_cached_entries->size() - child_items_; @@ -103,23 +103,24 @@ bool Rasterize(RasterCacheItem::CacheState cache_state, const PaintContext& paint_context, SkCanvas* canvas) { FML_DCHECK(cache_state != RasterCacheItem::CacheState::kNone); - SkISize canvas_size = canvas->getBaseLayerSize(); - SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height()); - internal_nodes_canvas.setMatrix(canvas->getTotalMatrix()); - internal_nodes_canvas.addCanvas(canvas); + LayerStateStack state_stack; + state_stack.set_delegate(canvas); + state_stack.set_checkerboard_func( + paint_context.state_stack.checkerboard_func()); + auto mutator = state_stack.save(); + mutator.transform(canvas->getTotalMatrix()); PaintContext context = { // clang-format off - .internal_nodes_canvas = static_cast(&internal_nodes_canvas), - .leaf_nodes_canvas = canvas, - .gr_context = paint_context.gr_context, - .dst_color_space = paint_context.dst_color_space, - .view_embedder = paint_context.view_embedder, - .raster_time = paint_context.raster_time, - .ui_time = paint_context.ui_time, - .texture_registry = paint_context.texture_registry, - .raster_cache = paint_context.raster_cache, - .checkerboard_offscreen_layers = paint_context.checkerboard_offscreen_layers, - .frame_device_pixel_ratio = paint_context.frame_device_pixel_ratio, + .state_stack = state_stack, + .canvas = canvas, + .gr_context = paint_context.gr_context, + .dst_color_space = paint_context.dst_color_space, + .view_embedder = paint_context.view_embedder, + .raster_time = paint_context.raster_time, + .ui_time = paint_context.ui_time, + .texture_registry = paint_context.texture_registry, + .raster_cache = paint_context.raster_cache, + .frame_device_pixel_ratio = paint_context.frame_device_pixel_ratio, // clang-format on }; @@ -169,7 +170,7 @@ bool LayerRasterCacheItem::TryToPrepareRasterCache(const PaintContext& context, bool LayerRasterCacheItem::Draw(const PaintContext& context, const SkPaint* paint) const { - return Draw(context, context.leaf_nodes_canvas, paint); + return Draw(context, context.canvas, paint); } bool LayerRasterCacheItem::Draw(const PaintContext& context, diff --git a/flow/layers/layer_state_stack.cc b/flow/layers/layer_state_stack.cc new file mode 100644 index 0000000000000..9ea44644590dc --- /dev/null +++ b/flow/layers/layer_state_stack.cc @@ -0,0 +1,796 @@ +// 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_state_stack.h" +#include "flutter/flow/layers/layer.h" +#include "flutter/flow/paint_utils.h" +#include "flutter/flow/raster_cache_util.h" + +namespace flutter { + +using AutoRestore = LayerStateStack::AutoRestore; +using MutatorContext = LayerStateStack::MutatorContext; + +static inline bool has_perspective(const SkM44& matrix) { + return (matrix.rc(3, 0) != 0 || // + matrix.rc(3, 1) != 0 || // + matrix.rc(3, 2) != 0 || // + matrix.rc(3, 3) != 1); +} + +LayerStateStack::LayerStateStack(const SkRect* cull_rect) { + if (cull_rect) { + initial_cull_rect_ = cull_rect_ = *cull_rect; + } else { + initial_cull_rect_ = cull_rect_ = kGiantRect; + } +} + +void LayerStateStack::clear_delegate() { + if (canvas_) { + canvas_->restoreToCount(restore_count_); + canvas_ = nullptr; + } + if (builder_) { + builder_->restoreToCount(restore_count_); + builder_ = nullptr; + } + if (mutators_) { + mutators_->PopTo(restore_count_); + mutators_ = nullptr; + } +} + +void LayerStateStack::set_delegate(SkCanvas* canvas) { + if (canvas == canvas_) { + return; + } + clear_delegate(); + if (canvas) { + restore_count_ = canvas->getSaveCount(); + canvas_ = canvas; + reapply_all(); + } +} + +void LayerStateStack::set_delegate(DisplayListBuilder* builder) { + if (builder == builder_) { + return; + } + clear_delegate(); + if (builder) { + restore_count_ = builder->getSaveCount(); + builder_ = builder; + reapply_all(); + } +} + +void LayerStateStack::set_delegate(MutatorsStack* stack) { + if (stack == mutators_) { + return; + } + clear_delegate(); + if (stack) { + restore_count_ = stack->stack_count(); + mutators_ = stack; + reapply_all(); + } +} + +void LayerStateStack::set_initial_cull_rect(const SkRect& cull_rect) { + FML_DCHECK(is_empty()) << "set_initial_cull_rect() must be called before any " + "state is pushed onto the state stack"; + initial_cull_rect_ = cull_rect_ = cull_rect; +} + +void LayerStateStack::set_initial_transform(const SkMatrix& matrix) { + FML_DCHECK(is_empty()) << "set_initial_transform() must be called before any " + "state is pushed onto the state stack"; + initial_matrix_ = matrix_ = SkM44(matrix); +} + +void LayerStateStack::set_initial_transform(const SkM44& matrix) { + FML_DCHECK(is_empty()) << "set_initial_transform() must be called before any " + "state is pushed onto the state stack"; + initial_matrix_ = matrix_ = matrix; +} + +void LayerStateStack::set_initial_state(const SkRect& cull_rect, + const SkMatrix& matrix) { + FML_DCHECK(is_empty()) << "set_initial_state() must be called before any " + "state is pushed onto the state stack"; + initial_cull_rect_ = cull_rect_ = cull_rect; + initial_matrix_ = matrix_ = SkM44(matrix); +} + +void LayerStateStack::set_initial_state(const SkRect& cull_rect, + const SkM44& matrix) { + FML_DCHECK(is_empty()) << "set_initial_state() must be called before any " + "state is pushed onto the state stack"; + initial_cull_rect_ = cull_rect_ = cull_rect; + initial_matrix_ = matrix_ = matrix; +} + +void LayerStateStack::reapply_all() { + // We use a local RenderingAttributes instance so that it can track the + // necessary state changes independently as they occur in the stack. + // Reusing |outstanding_| would wreak havoc on the current state of + // the stack. When we are finished, though, the local attributes + // contents should match the current outstanding_ values; + RenderingAttributes attributes = outstanding_; + SkM44 matrix = matrix_; + SkRect cull_rect = cull_rect_; + outstanding_ = {}; + matrix_ = initial_matrix_; + cull_rect_ = initial_cull_rect_; + for (auto& state : state_stack_) { + state->reapply(this); + } + FML_DCHECK(attributes == outstanding_); + FML_DCHECK(matrix == matrix_); + FML_DCHECK(cull_rect == cull_rect_); +} + +AutoRestore::AutoRestore(LayerStateStack* stack) + : layer_state_stack_(stack), stack_restore_count_(stack->stack_count()) {} + +AutoRestore::~AutoRestore() { + layer_state_stack_->restore_to_count(stack_restore_count_); +} + +AutoRestore LayerStateStack::applyState(const SkRect& bounds, + int can_apply_flags) { + auto ret = AutoRestore(this); + if (needs_save_layer(can_apply_flags)) { + save_layer(bounds); + } + return ret; +} + +SkPaint* LayerStateStack::RenderingAttributes::fill(SkPaint& paint, + DlBlendMode mode) const { + SkPaint* ret = nullptr; + if (opacity < SK_Scalar1) { + paint.setAlphaf(std::max(opacity, 0.0f)); + ret = &paint; + } else { + paint.setAlphaf(SK_Scalar1); + } + if (color_filter) { + paint.setColorFilter(color_filter->skia_object()); + ret = &paint; + } else { + paint.setColorFilter(nullptr); + } + if (image_filter) { + paint.setImageFilter(image_filter->skia_object()); + ret = &paint; + } else { + paint.setImageFilter(nullptr); + } + paint.setBlendMode(ToSk(mode)); + if (mode != DlBlendMode::kSrcOver) { + ret = &paint; + } + return ret; +} + +DlPaint* LayerStateStack::RenderingAttributes::fill(DlPaint& paint, + DlBlendMode mode) const { + DlPaint* ret = nullptr; + if (opacity < SK_Scalar1) { + paint.setOpacity(std::max(opacity, 0.0f)); + ret = &paint; + } else { + paint.setOpacity(SK_Scalar1); + } + paint.setColorFilter(color_filter); + if (color_filter) { + ret = &paint; + } + paint.setImageFilter(image_filter); + if (image_filter) { + ret = &paint; + } + paint.setBlendMode(mode); + if (mode != DlBlendMode::kSrcOver) { + ret = &paint; + } + return ret; +} + +SkRect LayerStateStack::local_cull_rect() const { + SkM44 inverse; + if (cull_rect_.isEmpty() || !matrix_.invert(&inverse)) { + // Either rendering is clipped out or transformed into emptiness + return SkRect::MakeEmpty(); + } + if (has_perspective(inverse)) { + // We could do a 4-point long-form conversion, but since this is + // only used for culling, let's just return a non-constricting + // cull rect. + return kGiantRect; + } + return inverse.asM33().mapRect(cull_rect_); +} + +bool LayerStateStack::content_culled(const SkRect& content_bounds) const { + if (cull_rect_.isEmpty() || content_bounds.isEmpty()) { + return true; + } + if (has_perspective(matrix_)) { + return false; + } + return !matrix_.asM33().mapRect(content_bounds).intersects(cull_rect_); +} + +MutatorContext LayerStateStack::save() { + auto ret = MutatorContext(this); + state_stack_.emplace_back(std::make_unique()); + state_stack_.back()->apply(this); + return ret; +} + +void MutatorContext::saveLayer(const SkRect& bounds) { + layer_state_stack_->save_layer(bounds); +} + +void MutatorContext::applyOpacity(const SkRect& bounds, SkScalar opacity) { + if (opacity < SK_Scalar1) { + layer_state_stack_->push_attributes(); + layer_state_stack_->maybe_save_layer(opacity); + layer_state_stack_->push_opacity(bounds, opacity); + } +} + +void MutatorContext::applyImageFilter( + const SkRect& bounds, + const std::shared_ptr& filter) { + if (filter) { + layer_state_stack_->push_attributes(); + layer_state_stack_->maybe_save_layer(filter); + layer_state_stack_->push_image_filter(bounds, filter); + } +} + +void MutatorContext::applyColorFilter( + const SkRect& bounds, + const std::shared_ptr& filter) { + if (filter) { + layer_state_stack_->push_attributes(); + layer_state_stack_->maybe_save_layer(filter); + layer_state_stack_->push_color_filter(bounds, filter); + } +} + +void MutatorContext::applyBackdropFilter( + const SkRect& bounds, + const std::shared_ptr& filter, + DlBlendMode blend_mode) { + layer_state_stack_->push_backdrop(bounds, filter, blend_mode); +} + +void MutatorContext::translate(SkScalar tx, SkScalar ty) { + if (!(tx == 0 && ty == 0)) { + layer_state_stack_->maybe_save_layer_for_transform(); + layer_state_stack_->push_translate(tx, ty); + } +} + +void MutatorContext::transform(const SkMatrix& matrix) { + if (matrix.isTranslate()) { + translate(matrix.getTranslateX(), matrix.getTranslateY()); + } else if (!matrix.isIdentity()) { + layer_state_stack_->maybe_save_layer_for_transform(); + layer_state_stack_->push_transform(matrix); + } +} + +void MutatorContext::transform(const SkM44& m44) { + layer_state_stack_->maybe_save_layer_for_transform(); + layer_state_stack_->push_transform(m44); +} + +void MutatorContext::integralTransform() { + layer_state_stack_->maybe_save_layer_for_transform(); + layer_state_stack_->push_integral_transform(); +} + +void MutatorContext::clipRect(const SkRect& rect, bool is_aa) { + layer_state_stack_->maybe_save_layer_for_clip(); + layer_state_stack_->push_clip_rect(rect, is_aa); +} + +void MutatorContext::clipRRect(const SkRRect& rrect, bool is_aa) { + layer_state_stack_->maybe_save_layer_for_clip(); + layer_state_stack_->push_clip_rrect(rrect, is_aa); +} + +void MutatorContext::clipPath(const SkPath& path, bool is_aa) { + layer_state_stack_->maybe_save_layer_for_clip(); + layer_state_stack_->push_clip_path(path, is_aa); +} + +void LayerStateStack::restore_to_count(size_t restore_count) { + while (state_stack_.size() > restore_count) { + state_stack_.back()->restore(this); + state_stack_.pop_back(); + } +} + +void LayerStateStack::push_attributes() { + state_stack_.emplace_back(std::make_unique(outstanding_)); +} + +void LayerStateStack::push_opacity(const SkRect& bounds, SkScalar opacity) { + state_stack_.emplace_back(std::make_unique(bounds, opacity)); + apply_last_entry(); +} + +void LayerStateStack::push_color_filter( + const SkRect& bounds, + const std::shared_ptr& filter) { + state_stack_.emplace_back(std::make_unique(bounds, filter)); + apply_last_entry(); +} + +void LayerStateStack::push_image_filter( + const SkRect& bounds, + const std::shared_ptr& filter) { + state_stack_.emplace_back(std::make_unique(bounds, filter)); + apply_last_entry(); +} + +void LayerStateStack::push_backdrop( + const SkRect& bounds, + const std::shared_ptr& filter, + DlBlendMode blend_mode) { + state_stack_.emplace_back( + std::make_unique(bounds, filter, blend_mode)); + apply_last_entry(); +} + +void LayerStateStack::push_translate(SkScalar tx, SkScalar ty) { + state_stack_.emplace_back(std::make_unique(matrix_, tx, ty)); + apply_last_entry(); +} + +void LayerStateStack::push_transform(const SkM44& m44) { + state_stack_.emplace_back(std::make_unique(matrix_, m44)); + apply_last_entry(); +} + +void LayerStateStack::push_transform(const SkMatrix& matrix) { + state_stack_.emplace_back( + std::make_unique(matrix_, matrix)); + apply_last_entry(); +} + +void LayerStateStack::push_integral_transform() { + state_stack_.emplace_back(std::make_unique(matrix_)); + apply_last_entry(); +} + +void LayerStateStack::push_clip_rect(const SkRect& rect, bool is_aa) { + state_stack_.emplace_back( + std::make_unique(cull_rect_, rect, is_aa)); + apply_last_entry(); +} + +void LayerStateStack::push_clip_rrect(const SkRRect& rrect, bool is_aa) { + state_stack_.emplace_back( + std::make_unique(cull_rect_, rrect, is_aa)); + apply_last_entry(); +} + +void LayerStateStack::push_clip_path(const SkPath& path, bool is_aa) { + state_stack_.emplace_back( + std::make_unique(cull_rect_, path, is_aa)); + apply_last_entry(); +} + +bool LayerStateStack::needs_save_layer(int flags) const { + if (outstanding_.opacity < SK_Scalar1 && + (flags & LayerStateStack::kCallerCanApplyOpacity) == 0) { + return true; + } + if (outstanding_.image_filter && + (flags & LayerStateStack::kCallerCanApplyImageFilter) == 0) { + return true; + } + if (outstanding_.color_filter && + (flags & LayerStateStack::kCallerCanApplyColorFilter) == 0) { + return true; + } + return false; +} + +void LayerStateStack::save_layer(const SkRect& bounds) { + push_attributes(); + state_stack_.emplace_back( + std::make_unique(bounds, DlBlendMode::kSrcOver)); + apply_last_entry(); +} + +void LayerStateStack::maybe_save_layer_for_transform() { + // Alpha and ColorFilter don't care about transform + if (outstanding_.image_filter) { + save_layer(outstanding_.save_layer_bounds); + } +} + +void LayerStateStack::maybe_save_layer_for_clip() { + // Alpha and ColorFilter don't care about clipping + // - Alpha of clipped content == clip of alpha content + // - Color-filtering of clipped content == clip of color-filtered content + if (outstanding_.image_filter) { + save_layer(outstanding_.save_layer_bounds); + } +} + +void LayerStateStack::maybe_save_layer(int apply_flags) { + if (needs_save_layer(apply_flags)) { + save_layer(outstanding_.save_layer_bounds); + } +} + +void LayerStateStack::maybe_save_layer(SkScalar opacity) { + if (outstanding_.image_filter) { + save_layer(outstanding_.save_layer_bounds); + } +} + +void LayerStateStack::maybe_save_layer( + const std::shared_ptr& filter) { + if (outstanding_.color_filter || outstanding_.image_filter || + (outstanding_.opacity < SK_Scalar1 && + !filter->can_commute_with_opacity())) { + // TBD: compose the 2 color filters together. + save_layer(outstanding_.save_layer_bounds); + } +} + +void LayerStateStack::maybe_save_layer( + const std::shared_ptr& filter) { + if (outstanding_.image_filter) { + // TBD: compose the 2 image filters together. + save_layer(outstanding_.save_layer_bounds); + } +} + +void LayerStateStack::intersect_cull_rect(const SkRRect& clip, + SkClipOp op, + bool is_aa) { + switch (op) { + case SkClipOp::kIntersect: + break; + case SkClipOp::kDifference: + if (!clip.isRect()) { + return; + } + break; + } + intersect_cull_rect(clip.getBounds(), op, is_aa); +} + +void LayerStateStack::intersect_cull_rect(const SkPath& clip, + SkClipOp op, + bool is_aa) { + SkRect bounds; + switch (op) { + case SkClipOp::kIntersect: + bounds = clip.getBounds(); + break; + case SkClipOp::kDifference: + if (!clip.isRect(&bounds)) { + return; + } + break; + } + intersect_cull_rect(bounds, op, is_aa); +} + +void LayerStateStack::intersect_cull_rect(const SkRect& clip, + SkClipOp op, + bool is_aa) { + if (has_perspective(matrix_)) { + // We can conservatively ignore this clip. + return; + } + if (cull_rect_.isEmpty()) { + // No point in intersecting further. + return; + } + SkRect rect = clip; + switch (op) { + case SkClipOp::kIntersect: + if (rect.isEmpty()) { + cull_rect_.setEmpty(); + break; + } + rect = matrix_.asM33().mapRect(rect); + if (is_aa) { + rect.roundOut(&rect); + } + if (!cull_rect_.intersect(rect)) { + cull_rect_.setEmpty(); + } + break; + case SkClipOp::kDifference: + if (rect.isEmpty() || !rect.intersects(cull_rect_)) { + break; + } + if (matrix_.asM33().mapRect(&rect)) { + // This technique only works if it is rect -> rect + if (is_aa) { + SkIRect rounded; + rect.round(&rounded); + if (rounded.isEmpty()) { + break; + } + rect.set(rounded); + } + if (rect.fLeft <= cull_rect_.fLeft && + rect.fRight >= cull_rect_.fRight) { + // bounds spans entire width of cull_rect_ + // therefore we can slice off a top or bottom + // edge of the cull_rect_. + SkScalar top = std::max(rect.fBottom, cull_rect_.fTop); + SkScalar btm = std::min(rect.fTop, cull_rect_.fBottom); + if (top < btm) { + cull_rect_.fTop = top; + cull_rect_.fBottom = btm; + } else { + cull_rect_.setEmpty(); + } + } else if (rect.fTop <= cull_rect_.fTop && + rect.fBottom >= cull_rect_.fBottom) { + // bounds spans entire height of cull_rect_ + // therefore we can slice off a left or right + // edge of the cull_rect_. + SkScalar lft = std::max(rect.fRight, cull_rect_.fLeft); + SkScalar rgt = std::min(rect.fLeft, cull_rect_.fRight); + if (lft < rgt) { + cull_rect_.fLeft = lft; + cull_rect_.fRight = rgt; + } else { + cull_rect_.setEmpty(); + } + } + } + break; + } +} + +void LayerStateStack::AttributesEntry::restore(LayerStateStack* stack) const { + stack->outstanding_ = attributes_; +} + +void LayerStateStack::SaveEntry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + stack->canvas_->save(); + } + if (stack->builder_) { + stack->builder_->save(); + } +} + +void LayerStateStack::SaveEntry::restore(LayerStateStack* stack) const { + do_checkerboard(stack); + if (stack->canvas_) { + stack->canvas_->restore(); + } + if (stack->builder_) { + stack->builder_->restore(); + } +} + +void LayerStateStack::SaveLayerEntry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + TRACE_EVENT0("flutter", "Canvas::saveLayer"); + SkPaint paint; + stack->canvas_->saveLayer(bounds_, + stack->outstanding_.fill(paint, blend_mode_)); + } + if (stack->builder_) { + TRACE_EVENT0("flutter", "Canvas::saveLayer"); + DlPaint paint; + stack->builder_->saveLayer(&bounds_, + stack->outstanding_.fill(paint, blend_mode_)); + } + stack->outstanding_ = {}; +} + +void LayerStateStack::SaveLayerEntry::do_checkerboard( + LayerStateStack* stack) const { + if (stack->checkerboard_func_) { + (*stack->checkerboard_func_)(stack->canvas_, stack->builder_, bounds_); + } +} + +void LayerStateStack::OpacityEntry::apply(LayerStateStack* stack) const { + stack->outstanding_.save_layer_bounds = bounds_; + stack->outstanding_.opacity *= opacity_; + if (stack->mutators_) { + stack->mutators_->PushOpacity(DlColor::toAlpha(opacity_)); + } +} + +void LayerStateStack::OpacityEntry::restore(LayerStateStack* stack) const { + if (stack->mutators_) { + stack->mutators_->Pop(); + } +} + +void LayerStateStack::ImageFilterEntry::apply(LayerStateStack* stack) const { + stack->outstanding_.save_layer_bounds = bounds_; + stack->outstanding_.image_filter = filter_; + if (stack->mutators_) { + // MutatorsStack::PushImageFilter does not exist... + } +} + +void LayerStateStack::ColorFilterEntry::apply(LayerStateStack* stack) const { + stack->outstanding_.save_layer_bounds = bounds_; + stack->outstanding_.color_filter = filter_; + if (stack->mutators_) { + // MutatorsStack::PushColorFilter does not exist... + } +} + +void LayerStateStack::BackdropFilterEntry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + TRACE_EVENT0("flutter", "Canvas::saveLayer"); + sk_sp backdrop_filter = + filter_ ? filter_->skia_object() : nullptr; + SkPaint paint; + SkPaint* pPaint = stack->outstanding_.fill(paint, blend_mode_); + stack->canvas_->saveLayer( + SkCanvas::SaveLayerRec{&bounds_, pPaint, backdrop_filter.get(), 0}); + } + if (stack->builder_) { + TRACE_EVENT0("flutter", "Canvas::saveLayer"); + DlPaint paint; + DlPaint* pPaint = stack->outstanding_.fill(paint, blend_mode_); + stack->builder_->saveLayer(&bounds_, pPaint, filter_.get()); + } + if (stack->mutators_) { + stack->mutators_->PushBackdropFilter(filter_); + } + stack->outstanding_ = {}; +} + +void LayerStateStack::BackdropFilterEntry::restore( + LayerStateStack* stack) const { + if (stack->mutators_) { + stack->mutators_->Pop(); + } + LayerStateStack::SaveLayerEntry::restore(stack); +} + +void LayerStateStack::BackdropFilterEntry::reapply( + LayerStateStack* stack) const { + // On the reapply for subsequent overlay layers, we do not + // want to reapply the backdrop filter, but we do need to + // do a saveLayer to encapsulate the contents and match the + // restore that will be forthcoming. Note that this is not + // perfect if the BlendMode is not associative as we will be + // compositing multiple parts of the content in batches. + // Luckily the most common SrcOver is associative. + SaveLayerEntry::apply(stack); +} + +void LayerStateStack::TransformEntry::restore(LayerStateStack* stack) const { + stack->matrix_ = previous_matrix_; + if (stack->mutators_) { + stack->mutators_->Pop(); + } +} + +void LayerStateStack::TranslateEntry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + stack->canvas_->translate(tx_, ty_); + } + if (stack->builder_) { + stack->builder_->translate(tx_, ty_); + } + if (stack->mutators_) { + stack->mutators_->PushTransform(SkMatrix::Translate(tx_, ty_)); + } + stack->matrix_.preConcat(SkM44::Translate(tx_, ty_)); +} + +void LayerStateStack::TransformMatrixEntry::apply( + LayerStateStack* stack) const { + if (stack->canvas_) { + stack->canvas_->concat(matrix_); + } + if (stack->builder_) { + stack->builder_->transform(matrix_); + } + if (stack->mutators_) { + stack->mutators_->PushTransform(matrix_); + } + stack->matrix_.preConcat(matrix_); +} + +void LayerStateStack::TransformM44Entry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + stack->canvas_->concat(m44_); + } + if (stack->builder_) { + stack->builder_->transform(m44_); + } + if (stack->mutators_) { + stack->mutators_->PushTransform(m44_.asM33()); + } + stack->matrix_.preConcat(m44_); +} + +void LayerStateStack::IntegralTransformEntry::apply( + LayerStateStack* stack) const { + SkM44 matrix = RasterCacheUtil::GetIntegralTransCTM(stack->matrix_); + if (stack->canvas_) { + stack->canvas_->setMatrix(matrix); + } + if (stack->builder_) { + stack->builder_->transformReset(); + stack->builder_->transform(matrix); + } + if (stack->mutators_) { + // There is no "SetMatrix" on MutatorsStack, but we need to push + // something to match the corresponding pop on the transform + // restore. + stack->mutators_->PushTransform(SkMatrix::I()); + } + stack->matrix_ = matrix; +} + +void LayerStateStack::ClipEntry::restore(LayerStateStack* stack) const { + stack->cull_rect_ = previous_cull_rect_; + if (stack->mutators_) { + stack->mutators_->Pop(); + } +} + +void LayerStateStack::ClipRectEntry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + stack->canvas_->clipRect(clip_rect_, SkClipOp::kIntersect, is_aa_); + } + if (stack->builder_) { + stack->builder_->clipRect(clip_rect_, SkClipOp::kIntersect, is_aa_); + } + if (stack->mutators_) { + stack->mutators_->PushClipRect(clip_rect_); + } + stack->intersect_cull_rect(clip_rect_, SkClipOp::kIntersect, is_aa_); +} + +void LayerStateStack::ClipRRectEntry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + stack->canvas_->clipRRect(clip_rrect_, SkClipOp::kIntersect, is_aa_); + } + if (stack->builder_) { + stack->builder_->clipRRect(clip_rrect_, SkClipOp::kIntersect, is_aa_); + } + if (stack->mutators_) { + stack->mutators_->PushClipRRect(clip_rrect_); + } + stack->intersect_cull_rect(clip_rrect_, SkClipOp::kIntersect, is_aa_); +} + +void LayerStateStack::ClipPathEntry::apply(LayerStateStack* stack) const { + if (stack->canvas_) { + stack->canvas_->clipPath(clip_path_, SkClipOp::kIntersect, is_aa_); + } + if (stack->builder_) { + stack->builder_->clipPath(clip_path_, SkClipOp::kIntersect, is_aa_); + } + if (stack->mutators_) { + stack->mutators_->PushClipPath(clip_path_); + } + stack->intersect_cull_rect(clip_path_, SkClipOp::kIntersect, is_aa_); +} + +} // namespace flutter diff --git a/flow/layers/layer_state_stack.h b/flow/layers/layer_state_stack.h new file mode 100644 index 0000000000000..65c1076df1361 --- /dev/null +++ b/flow/layers/layer_state_stack.h @@ -0,0 +1,608 @@ +// 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 FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ +#define FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ + +#include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_canvas_recorder.h" +#include "flutter/flow/embedded_views.h" +#include "flutter/flow/paint_utils.h" + +namespace flutter { + +/// The LayerStateStack manages the inherited state passed down between +/// |Layer| objects in a |LayerTree| during |Preroll| and |Paint|. +/// +/// More specifically, it manages the clip and transform state during +/// recursive rendering and will hold and lazily apply opacity, ImageFilter +/// and ColorFilter attributes to recursive content. This is not a truly +/// general state management mechnanism as it makes assumptions that code +/// will be applying the attributes to rendered content that happens in +/// recursive calls. The automatic save/restore mechanisms only work in +/// a context where C++ auto-destruct calls will engage the restore at +/// the end of a code block and that any applied attributes will only +/// be applied to the content rendered inside that block. These restrictions +/// match the organization of the |LayerTree| precisely. +/// +/// The stack can manage a single state delegate. The stack will both +/// record the state internally regardless of any delegate and will also +/// apply it to a delegate as needed. The delegate can be swapped out +/// on the fly (as is typically done by PlatformViewLayer when recording +/// the state for multiple inter-embedded-view sub-trees) and the old +/// delegate will be restored to its original state (before it became a +/// delegate) and the new delegate will have all of the state recorded +/// by the stack replayed into it to bring it up to speed with the +/// current rendering context. +/// +/// The delegate can be any one of: +/// - MutatorsStack: used during Preroll to remember the outstanding +/// state for embedded platform layers +/// - SkCanvas: used during Paint for the default output to a Skia +/// surface +/// - DisplayListBuilder: used during Paint to construct a DisplayList +/// for Impeller output +/// The stack will know which state needs to be conveyed to any of these +/// delegates and when is the best time to convey that state (i.e. lazy +/// saveLayer calls for example). +/// +/// The rendering state attributes will be automatically applied to the +/// nested content using a |saveLayer| call at the point at which we +/// encounter rendered content (i.e. various nested layers that exist only +/// to apply new state will not trigger the |saveLayer| and the attributes +/// can accumulate until we reach actual content that is rendered.) Some +/// rendered content can avoid the |saveLayer| if it reports to the object +/// that it is able to apply all of the attributes that happen to be +/// outstanding (accumulated from parent state-modifiers). A |ContainerLayer| +/// can also monitor the attribute rendering capabilities of a list of +/// children and can ask the object to apply a protective |saveLayer| or +/// not based on the negotiated capabilities of the entire group. +/// +/// Any code that is planning to modify the clip, transform, or rendering +/// attributes for its child content must start by calling the |save| method +/// which returns a MutatorContext object. The methods that modify such +/// state only exist on the MutatorContext object so it is difficult to get +/// that wrong, but the caller must make sure that the call happens within +/// a C++ code block that will define the "rendering scope" of those +/// state changes as they will be automatically restored on exit from that +/// block. Note that the layer might make similar state calls directly on +/// the canvas or builder during the Paint cycle (via saveLayer, transform, +/// or clip calls), but should avoid doing so if there is any nested content +/// that needs to track or react to those state calls. +/// +/// Code that needs to render content can simply inform the parent of their +/// abilities by setting the |PrerollContext::renderable_state_flags| during +/// |Preroll| and then render with those attributes during |Paint| by +/// requesting the outstanding values of those attributes from the state_stack +/// object. Individual leaf layers can ignore this feature as the default +/// behavior during |Preroll| will have their parent |ContainerLayer| assume +/// that they cannot render any outstanding state attributes and will apply +/// the protective saveLayer on their behalf if needed. As such, this object +/// only provides "opt-in" features for leaf layers and no responsibilities +/// otherwise. +/// See |LayerStateStack::fill| +/// See |LayerStateStack::outstanding_opacity| +/// See |LayerStateStack::outstanding_color_filter| +/// See |LayerStateStack::outstanding_image_filter| +/// +/// State-modifying layers should contain code similar to this pattern in both +/// their |Preroll| and |Paint| methods. +/// +/// void [LayerType]::[Preroll/Paint](context) { +/// auto mutator = context.state_stack.save(); +/// mutator.translate(origin.x, origin.y); +/// mutator.applyOpacity(content_bounds, opacity_value); +/// mutator.applyColorFilter(content_bounds, color_filter); +/// +/// // Children will react to the state applied above during their +/// // Preroll/Paint methods or ContainerLayer will protect them +/// // conservatively by default. +/// [Preroll/Paint]Children(context); +/// +/// // here the mutator will be auto-destructed and the state accumulated +/// // by it will be restored out of the state_stack and its associated +/// // delegates. +/// } +class LayerStateStack { + public: + explicit LayerStateStack(const SkRect* cull_rect = nullptr); + + CheckerboardFunc checkerboard_func() const { return checkerboard_func_; } + void set_checkerboard_func(CheckerboardFunc checkerboard_func) { + checkerboard_func_ = checkerboard_func; + } + + // Clears out any old delegate to make room for a new one. + void clear_delegate(); + + // Return the SkCanvas delegate if the state stack has such a delegate. + // The state stack will only have one of an SkCanvas, Builder, or Mutators + // delegate at any given time. + // See also |builder_delegate| and |mutators_delegate|. + SkCanvas* canvas_delegate() { return canvas_; } + + // Return the DisplayListBuilder delegate if the state stack has such a + // delegate. + // The state stack will only have one of an SkCanvas, Builder, or Mutators + // delegate at any given time. + // See also |builder_delegate| and |mutators_delegate|. + DisplayListBuilder* builder_delegate() { return builder_; } + + // Return the MutatorsStack delegate if the state stack has such a + // delegate. + // The state stack will only have one of an SkCanvas, Builder, or Mutators + // delegate at any given time. + // See also |builder_delegate| and |mutators_delegate|. + MutatorsStack* mutators_delegate() { return mutators_; } + + // Clears the old delegate and sets the canvas delegate to the indicated + // canvas (if not nullptr). This ensures that only one delegate - either + // a canvas, a builder, or mutator stack - is present at any one time. + void set_delegate(SkCanvas* canvas); + + // Clears the old delegate and sets the builder delegate to the indicated + // buider (if not nullptr). This ensures that only one delegate - either + // a canvas, a builder, or mutator stack - is present at any one time. + void set_delegate(DisplayListBuilder* builder); + void set_delegate(sk_sp builder) { + set_delegate(builder.get()); + } + void set_delegate(DisplayListCanvasRecorder& recorder) { + set_delegate(recorder.builder().get()); + } + + // Clears the old delegate and sets the mutators delegate to the indicated + // MutatorsStack (if not null). This ensures that only one delegate - either + // a canvas, a builder, or mutator stack - is present at any one time. + void set_delegate(MutatorsStack* stack); + + // Overrides the initial cull rect and/or transform when it is not known at + // the time that the LayerStateStack is constructed. Must be called before + // any state has been pushed on the stack. + void set_initial_cull_rect(const SkRect& cull_rect); + void set_initial_transform(const SkMatrix& matrix); + void set_initial_transform(const SkM44& matrix); + void set_initial_state(const SkRect& cull_rect, const SkMatrix& matrix); + void set_initial_state(const SkRect& cull_rect, const SkM44& matrix); + + class AutoRestore { + public: + ~AutoRestore(); + + protected: + LayerStateStack* layer_state_stack_; + + private: + AutoRestore(LayerStateStack* stack); + friend class LayerStateStack; + + const size_t stack_restore_count_; + }; + + static constexpr int kCallerCanApplyOpacity = 0x1; + static constexpr int kCallerCanApplyColorFilter = 0x2; + static constexpr int kCallerCanApplyImageFilter = 0x4; + static constexpr int kCallerCanApplyAnything = + (kCallerCanApplyOpacity | kCallerCanApplyColorFilter | + kCallerCanApplyImageFilter); + + class MutatorContext : public AutoRestore { + public: + // Immediately executes a saveLayer with all accumulated state + // onto the canvas or builder to be applied at the next matching + // restore. A saveLayer is always executed by this method even if + // there are no outstanding attributes. + void saveLayer(const SkRect& bounds); + + // Records the opacity for application at the next call to + // saveLayer or applyState. A saveLayer may be executed at + // this time if the opacity cannot be batched with other + // outstanding attributes. + void applyOpacity(const SkRect& bounds, SkScalar opacity); + + // Records the image filter for application at the next call to + // saveLayer or applyState. A saveLayer may be executed at + // this time if the image filter cannot be batched with other + // outstanding attributes. + // (Currently only opacity is recorded for batching) + void applyImageFilter(const SkRect& bounds, + const std::shared_ptr& filter); + + // Records the color filter for application at the next call to + // saveLayer or applyState. A saveLayer may be executed at + // this time if the color filter cannot be batched with other + // outstanding attributes. + // (Currently only opacity is recorded for batching) + void applyColorFilter(const SkRect& bounds, + const std::shared_ptr& filter); + + // Saves the state stack and immediately executes a saveLayer + // with the indicated backdrop filter and any outstanding + // state attributes. Since the backdrop filter only applies + // to the pixels alrady on the screen when this call is made, + // the backdrop filter will only be applied to the canvas or + // builder installed at the time that this call is made, and + // subsequent canvas or builder objects that are made delegates + // will only see a saveLayer with the indicated blend_mode. + void applyBackdropFilter(const SkRect& bounds, + const std::shared_ptr& filter, + DlBlendMode blend_mode); + + void translate(SkScalar tx, SkScalar ty); + void translate(SkPoint tp) { translate(tp.fX, tp.fY); } + void transform(const SkM44& m44); + void transform(const SkMatrix& matrix); + void integralTransform(); + + void clipRect(const SkRect& rect, bool is_aa); + void clipRRect(const SkRRect& rrect, bool is_aa); + void clipPath(const SkPath& path, bool is_aa); + + private: + MutatorContext(LayerStateStack* stack) : AutoRestore(stack) {} + friend class LayerStateStack; + }; + + // Apply the outstanding state via saveLayer if necessary, + // respecting the flags representing which potentially + // outstanding attributes the calling layer can apply + // themselves. + // + // A saveLayer may or may not be sent to the delegates depending + // on how the outstanding state intersects with the flags supplied + // by the caller. + // + // An AutoRestore instance will always be returned even if there + // was no saveLayer applied. + [[nodiscard]] AutoRestore applyState(const SkRect& bounds, + int can_apply_flags = 0); + + SkScalar outstanding_opacity() const { return outstanding_.opacity; } + + std::shared_ptr outstanding_color_filter() const { + return outstanding_.color_filter; + } + + std::shared_ptr outstanding_image_filter() const { + return outstanding_.image_filter; + } + + SkRect outstanding_bounds() const { return outstanding_.save_layer_bounds; } + + // Fill the provided paint object with any oustanding attributes and + // return a pointer to it, or return a nullptr if there were no + // outstanding attributes to paint with. + SkPaint* fill(SkPaint& paint) const { return outstanding_.fill(paint); } + + // Fill the provided paint object with any oustanding attributes and + // return a pointer to it, or return a nullptr if there were no + // outstanding attributes to paint with. + DlPaint* fill(DlPaint& paint) const { return outstanding_.fill(paint); } + + SkRect device_cull_rect() const { return cull_rect_; } + SkRect local_cull_rect() const; + SkM44 transform_4x4() const { return matrix_; } + SkMatrix transform_3x3() const { return matrix_.asM33(); } + + // Tests if painting content with the current outstanding attributes + // will produce any content. + bool painting_is_nop() const { return outstanding_.opacity <= 0; } + + // Tests if painting content with the given bounds will produce any output. + bool content_culled(const SkRect& content_bounds) const; + + // Saves the current state of the state stack and returns a + // MutatorContext which can be used to manipulate the state. + // The state stack will be restored to its current state + // when the MutatorContext object goes out of scope. + [[nodiscard]] MutatorContext save(); + + bool is_empty() const { return state_stack_.empty(); } + + private: + size_t stack_count() const { return state_stack_.size(); } + void restore_to_count(size_t restore_count); + void reapply_all(); + + void apply_last_entry() { state_stack_.back()->apply(this); } + + // The push methods simply push an associated StateEntry on the stack + // and then apply it to the current canvas and builder. + // --------------------- + void push_attributes(); + void push_opacity(const SkRect& rect, SkScalar opacity); + void push_color_filter(const SkRect& bounds, + const std::shared_ptr& filter); + void push_image_filter(const SkRect& bounds, + const std::shared_ptr& filter); + void push_backdrop(const SkRect& bounds, + const std::shared_ptr& filter, + DlBlendMode blend_mode); + + void push_translate(SkScalar tx, SkScalar ty); + void push_transform(const SkM44& matrix); + void push_transform(const SkMatrix& matrix); + void push_integral_transform(); + + void push_clip_rect(const SkRect& rect, bool is_aa); + void push_clip_rrect(const SkRRect& rrect, bool is_aa); + void push_clip_path(const SkPath& path, bool is_aa); + // --------------------- + + // The maybe/needs_save_layer methods will determine if the indicated + // attribute can be incorporated into the outstanding attributes as is, + // or if the apply_flags are compatible with the outstanding attributes. + // If the oustanding attributes are incompatible with the new attribute + // or the apply flags, then a protective saveLayer will be executed. + // --------------------- + bool needs_save_layer(int flags) const; + void save_layer(const SkRect& bounds); + void maybe_save_layer_for_transform(); + void maybe_save_layer_for_clip(); + void maybe_save_layer(int apply_flags); + void maybe_save_layer(SkScalar opacity); + void maybe_save_layer(const std::shared_ptr& filter); + void maybe_save_layer(const std::shared_ptr& filter); + // --------------------- + + void intersect_cull_rect(const SkRect& clip, SkClipOp op, bool is_aa); + void intersect_cull_rect(const SkRRect& clip, SkClipOp op, bool is_aa); + void intersect_cull_rect(const SkPath& clip, SkClipOp op, bool is_aa); + + struct RenderingAttributes { + // We need to record the last bounds we received for the last + // attribute that we recorded so that we can perform a saveLayer + // on the proper area. When an attribute is applied that cannot + // be merged with the existing attributes, it will be submitted + // with a bounds for its own source content, not the bounds for + // the content that will be included in the saveLayer that applies + // the existing outstanding attributes - thus we need to record + // the bounds that were supplied with the most recent previous + // attribute to be applied. + SkRect save_layer_bounds{0, 0, 0, 0}; + + SkScalar opacity = SK_Scalar1; + std::shared_ptr color_filter; + std::shared_ptr image_filter; + + SkPaint* fill(SkPaint& paint, + DlBlendMode mode = DlBlendMode::kSrcOver) const; + DlPaint* fill(DlPaint& paint, + DlBlendMode mode = DlBlendMode::kSrcOver) const; + + bool operator==(const RenderingAttributes& other) const { + return save_layer_bounds == other.save_layer_bounds && + opacity == other.opacity && + Equals(color_filter, other.color_filter) && + Equals(image_filter, other.image_filter); + } + }; + + class StateEntry { + public: + virtual ~StateEntry() = default; + + virtual void apply(LayerStateStack* stack) const = 0; + + virtual void reapply(LayerStateStack* stack) const { apply(stack); } + + virtual void restore(LayerStateStack* stack) const {} + }; + + class AttributesEntry : public StateEntry { + public: + AttributesEntry(RenderingAttributes attributes) : attributes_(attributes) {} + + virtual void apply(LayerStateStack* stack) const override {} + + void restore(LayerStateStack* stack) const override; + + private: + const RenderingAttributes attributes_; + }; + + class SaveEntry : public StateEntry { + public: + SaveEntry() = default; + + void apply(LayerStateStack* stack) const override; + void restore(LayerStateStack* stack) const override; + + protected: + virtual void do_checkerboard(LayerStateStack* stack) const {} + }; + + class SaveLayerEntry : public SaveEntry { + public: + SaveLayerEntry(const SkRect& bounds, DlBlendMode blend_mode) + : bounds_(bounds), blend_mode_(blend_mode) {} + + void apply(LayerStateStack* stack) const override; + + protected: + const SkRect bounds_; + const DlBlendMode blend_mode_; + + void do_checkerboard(LayerStateStack* stack) const override; + }; + + class OpacityEntry : public StateEntry { + public: + OpacityEntry(const SkRect& bounds, SkScalar opacity) + : bounds_(bounds), opacity_(opacity) {} + + void apply(LayerStateStack* stack) const override; + void restore(LayerStateStack* stack) const override; + + private: + const SkRect bounds_; + const SkScalar opacity_; + }; + + class ImageFilterEntry : public StateEntry { + public: + ImageFilterEntry(const SkRect& bounds, + const std::shared_ptr& filter) + : bounds_(bounds), filter_(filter) {} + ~ImageFilterEntry() override = default; + + void apply(LayerStateStack* stack) const override; + + private: + const SkRect bounds_; + const std::shared_ptr filter_; + }; + + class ColorFilterEntry : public StateEntry { + public: + ColorFilterEntry(const SkRect& bounds, + const std::shared_ptr& filter) + : bounds_(bounds), filter_(filter) {} + ~ColorFilterEntry() override = default; + + void apply(LayerStateStack* stack) const override; + + private: + const SkRect bounds_; + const std::shared_ptr filter_; + }; + + class BackdropFilterEntry : public SaveLayerEntry { + public: + BackdropFilterEntry(const SkRect& bounds, + const std::shared_ptr& filter, + DlBlendMode blend_mode) + : SaveLayerEntry(bounds, blend_mode), filter_(filter) {} + ~BackdropFilterEntry() override = default; + + void apply(LayerStateStack* stack) const override; + void restore(LayerStateStack* stack) const override; + + void reapply(LayerStateStack* stack) const override; + + private: + const std::shared_ptr filter_; + friend class LayerStateStack; + }; + + class TransformEntry : public StateEntry { + public: + TransformEntry(const SkM44& matrix) : previous_matrix_(matrix) {} + + void restore(LayerStateStack* stack) const override; + + private: + const SkM44 previous_matrix_; + }; + + class TranslateEntry : public TransformEntry { + public: + TranslateEntry(const SkM44& previous_matrix, SkScalar tx, SkScalar ty) + : TransformEntry(previous_matrix), tx_(tx), ty_(ty) {} + + void apply(LayerStateStack* stack) const override; + + private: + const SkScalar tx_; + const SkScalar ty_; + }; + + class TransformMatrixEntry : public TransformEntry { + public: + TransformMatrixEntry(const SkM44 previous_matrix, const SkMatrix& matrix) + : TransformEntry(previous_matrix), matrix_(matrix) {} + + void apply(LayerStateStack* stack) const override; + + private: + const SkMatrix matrix_; + }; + + class TransformM44Entry : public TransformEntry { + public: + TransformM44Entry(const SkM44 previous_matrix, const SkM44& m44) + : TransformEntry(previous_matrix), m44_(m44) {} + + void apply(LayerStateStack* stack) const override; + + private: + const SkM44 m44_; + }; + + class IntegralTransformEntry : public TransformEntry { + public: + IntegralTransformEntry(const SkM44 previous_matrix) + : TransformEntry(previous_matrix) {} + + void apply(LayerStateStack* stack) const override; + }; + + class ClipEntry : public StateEntry { + protected: + ClipEntry(const SkRect& cull_rect, bool is_aa) + : previous_cull_rect_(cull_rect), is_aa_(is_aa) {} + + void restore(LayerStateStack* stack) const override; + + const SkRect previous_cull_rect_; + const bool is_aa_; + }; + + class ClipRectEntry : public ClipEntry { + public: + ClipRectEntry(const SkRect& cull_rect, const SkRect& clip_rect, bool is_aa) + : ClipEntry(cull_rect, is_aa), clip_rect_(clip_rect) {} + + void apply(LayerStateStack* stack) const override; + + private: + const SkRect clip_rect_; + }; + + class ClipRRectEntry : public ClipEntry { + public: + ClipRRectEntry(const SkRect& cull_rect, + const SkRRect& clip_rrect, + bool is_aa) + : ClipEntry(cull_rect, is_aa), clip_rrect_(clip_rrect) {} + + void apply(LayerStateStack* stack) const override; + + private: + const SkRRect clip_rrect_; + }; + + class ClipPathEntry : public ClipEntry { + public: + ClipPathEntry(const SkRect& cull_rect, const SkPath& clip_path, bool is_aa) + : ClipEntry(cull_rect, is_aa), clip_path_(clip_path) {} + ~ClipPathEntry() override = default; + + void apply(LayerStateStack* stack) const override; + + private: + const SkPath clip_path_; + }; + + std::vector> state_stack_; + friend class MutatorContext; + + SkM44 initial_matrix_; + SkM44 matrix_; + SkRect initial_cull_rect_; + SkRect cull_rect_; + + SkCanvas* canvas_ = nullptr; + DisplayListBuilder* builder_ = nullptr; + MutatorsStack* mutators_ = nullptr; + int restore_count_ = 0; + RenderingAttributes outstanding_; + CheckerboardFunc checkerboard_func_ = nullptr; + + friend class SaveLayerEntry; +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ diff --git a/flow/layers/layer_state_stack_unittests.cc b/flow/layers/layer_state_stack_unittests.cc new file mode 100644 index 0000000000000..65e9ddd78e034 --- /dev/null +++ b/flow/layers/layer_state_stack_unittests.cc @@ -0,0 +1,491 @@ +// 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 "gtest/gtest.h" + +#include "flutter/display_list/display_list_color_filter.h" +#include "flutter/display_list/display_list_image_filter.h" +#include "flutter/flow/layers/layer.h" +#include "flutter/flow/layers/layer_state_stack.h" +#include "flutter/testing/display_list_testing.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +TEST(LayerStateStack, Defaults) { + LayerStateStack state_stack; + + ASSERT_EQ(state_stack.canvas_delegate(), nullptr); + ASSERT_EQ(state_stack.builder_delegate(), nullptr); + ASSERT_EQ(state_stack.checkerboard_func(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_bounds(), SkRect()); + ASSERT_EQ(state_stack.device_cull_rect(), kGiantRect); + ASSERT_EQ(state_stack.local_cull_rect(), kGiantRect); + ASSERT_EQ(state_stack.transform_3x3(), SkMatrix::I()); + ASSERT_EQ(state_stack.transform_4x4(), SkM44()); + + SkPaint sk_paint; + state_stack.fill(sk_paint); + ASSERT_EQ(sk_paint, SkPaint()); + + DlPaint dl_paint; + state_stack.fill(dl_paint); + ASSERT_EQ(dl_paint, DlPaint()); +} + +TEST(LayerStateStack, SingularDelegate) { + LayerStateStack state_stack; + ASSERT_EQ(state_stack.canvas_delegate(), nullptr); + ASSERT_EQ(state_stack.builder_delegate(), nullptr); + + DisplayListBuilder builder; + MockCanvas canvas; + + // no delegate -> builder delegate + state_stack.set_delegate(&builder); + ASSERT_EQ(state_stack.canvas_delegate(), nullptr); + ASSERT_EQ(state_stack.builder_delegate(), &builder); + + // builder delegate -> canvas delegate + state_stack.set_delegate(&canvas); + ASSERT_EQ(state_stack.canvas_delegate(), &canvas); + ASSERT_EQ(state_stack.builder_delegate(), nullptr); + + // canvas delegate -> builder delegate + state_stack.set_delegate(&builder); + ASSERT_EQ(state_stack.canvas_delegate(), nullptr); + ASSERT_EQ(state_stack.builder_delegate(), &builder); + + // builder delegate -> no delegate + state_stack.clear_delegate(); + ASSERT_EQ(state_stack.canvas_delegate(), nullptr); + ASSERT_EQ(state_stack.builder_delegate(), nullptr); + + // canvas delegate -> no delegate + state_stack.set_delegate(&canvas); + state_stack.clear_delegate(); + ASSERT_EQ(state_stack.canvas_delegate(), nullptr); + ASSERT_EQ(state_stack.builder_delegate(), nullptr); +} + +TEST(LayerStateStack, Opacity) { + SkRect rect = {10, 10, 20, 20}; + + LayerStateStack state_stack; + { + auto mutator = state_stack.save(); + mutator.applyOpacity(rect, 0.5f); + + ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f); + ASSERT_EQ(state_stack.outstanding_bounds(), rect); + + // Check nested opacities multiply with each other + { + auto mutator2 = state_stack.save(); + mutator.applyOpacity(rect, 0.5f); + + ASSERT_EQ(state_stack.outstanding_opacity(), 0.25f); + ASSERT_EQ(state_stack.outstanding_bounds(), rect); + + // Verify output with applyState that does not accept opacity + { + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + { + auto restore = state_stack.applyState(rect, 0); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + ASSERT_EQ(state_stack.outstanding_bounds(), SkRect()); + + DlPaint paint; + state_stack.fill(paint); + builder.drawRect(rect, paint); + } + state_stack.clear_delegate(); + + DisplayListBuilder expected; + DlPaint save_paint = + DlPaint().setOpacity(state_stack.outstanding_opacity()); + expected.saveLayer(&rect, &save_paint); + expected.drawRect(rect, DlPaint()); + expected.restore(); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), expected.Build())); + } + + // Verify output with applyState that accepts opacity + { + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + { + auto restore = state_stack.applyState( + rect, LayerStateStack::kCallerCanApplyOpacity); + ASSERT_EQ(state_stack.outstanding_opacity(), 0.25f); + ASSERT_EQ(state_stack.outstanding_bounds(), rect); + + DlPaint paint; + state_stack.fill(paint); + builder.drawRect(rect, paint); + } + state_stack.clear_delegate(); + + DisplayListBuilder expected; + expected.drawRect(rect, DlPaint().setOpacity(0.25f)); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), expected.Build())); + } + } + + ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f); + ASSERT_EQ(state_stack.outstanding_bounds(), rect); + } + + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + ASSERT_EQ(state_stack.outstanding_bounds(), SkRect()); +} + +TEST(LayerStateStack, ColorFilter) { + SkRect rect = {10, 10, 20, 20}; + std::shared_ptr outer_filter = + std::make_shared(DlColor::kYellow(), + DlBlendMode::kColorBurn); + std::shared_ptr inner_filter = + std::make_shared(DlColor::kRed(), + DlBlendMode::kColorBurn); + + LayerStateStack state_stack; + { + auto mutator = state_stack.save(); + mutator.applyColorFilter(rect, outer_filter); + + ASSERT_EQ(state_stack.outstanding_color_filter(), outer_filter); + + // Check nested color filters result in nested saveLayers + { + auto mutator2 = state_stack.save(); + mutator.applyColorFilter(rect, inner_filter); + + ASSERT_EQ(state_stack.outstanding_color_filter(), inner_filter); + + // Verify output with applyState that does not accept color filters + { + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + { + auto restore = state_stack.applyState(rect, 0); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); + + DlPaint paint; + state_stack.fill(paint); + builder.drawRect(rect, paint); + } + state_stack.clear_delegate(); + + DisplayListBuilder expected; + DlPaint outer_save_paint = DlPaint().setColorFilter(outer_filter); + DlPaint inner_save_paint = DlPaint().setColorFilter(inner_filter); + expected.saveLayer(&rect, &outer_save_paint); + expected.saveLayer(&rect, &inner_save_paint); + expected.drawRect(rect, DlPaint()); + expected.restore(); + expected.restore(); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), expected.Build())); + } + + // Verify output with applyState that accepts color filters + { + SkRect rect = {10, 10, 20, 20}; + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + { + auto restore = state_stack.applyState( + rect, LayerStateStack::kCallerCanApplyColorFilter); + ASSERT_EQ(state_stack.outstanding_color_filter(), inner_filter); + + DlPaint paint; + state_stack.fill(paint); + builder.drawRect(rect, paint); + } + state_stack.clear_delegate(); + + DisplayListBuilder expected; + DlPaint save_paint = DlPaint().setColorFilter(outer_filter); + DlPaint draw_paint = DlPaint().setColorFilter(inner_filter); + expected.saveLayer(&rect, &save_paint); + expected.drawRect(rect, draw_paint); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), expected.Build())); + } + } + + ASSERT_EQ(state_stack.outstanding_color_filter(), outer_filter); + } + + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); +} + +TEST(LayerStateStack, ImageFilter) { + SkRect rect = {10, 10, 20, 20}; + std::shared_ptr outer_filter = + std::make_shared(2.0f, 2.0f, DlTileMode::kClamp); + std::shared_ptr inner_filter = + std::make_shared(3.0f, 3.0f, DlTileMode::kClamp); + SkRect inner_src_rect = rect; + SkRect outer_src_rect; + ASSERT_EQ(inner_filter->map_local_bounds(rect, outer_src_rect), + &outer_src_rect); + + LayerStateStack state_stack; + { + auto mutator = state_stack.save(); + mutator.applyImageFilter(outer_src_rect, outer_filter); + + ASSERT_EQ(state_stack.outstanding_image_filter(), outer_filter); + + // Check nested color filters result in nested saveLayers + { + auto mutator2 = state_stack.save(); + mutator.applyImageFilter(rect, inner_filter); + + ASSERT_EQ(state_stack.outstanding_image_filter(), inner_filter); + + // Verify output with applyState that does not accept color filters + { + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + { + auto restore = state_stack.applyState(rect, 0); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + + DlPaint paint; + state_stack.fill(paint); + builder.drawRect(rect, paint); + } + state_stack.clear_delegate(); + + DisplayListBuilder expected; + DlPaint outer_save_paint = DlPaint().setImageFilter(outer_filter); + DlPaint inner_save_paint = DlPaint().setImageFilter(inner_filter); + expected.saveLayer(&outer_src_rect, &outer_save_paint); + expected.saveLayer(&inner_src_rect, &inner_save_paint); + expected.drawRect(rect, DlPaint()); + expected.restore(); + expected.restore(); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), expected.Build())); + } + + // Verify output with applyState that accepts color filters + { + SkRect rect = {10, 10, 20, 20}; + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + { + auto restore = state_stack.applyState( + rect, LayerStateStack::kCallerCanApplyImageFilter); + ASSERT_EQ(state_stack.outstanding_image_filter(), inner_filter); + + DlPaint paint; + state_stack.fill(paint); + builder.drawRect(rect, paint); + } + state_stack.clear_delegate(); + + DisplayListBuilder expected; + DlPaint save_paint = DlPaint().setImageFilter(outer_filter); + DlPaint draw_paint = DlPaint().setImageFilter(inner_filter); + expected.saveLayer(&outer_src_rect, &save_paint); + expected.drawRect(rect, draw_paint); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), expected.Build())); + } + } + + ASSERT_EQ(state_stack.outstanding_image_filter(), outer_filter); + } + + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); +} + +TEST(LayerStateStack, OpacityAndColorFilterInteraction) { + SkRect rect = {10, 10, 20, 20}; + std::shared_ptr color_filter = + std::make_shared(DlColor::kYellow(), + DlBlendMode::kColorBurn); + + LayerStateStack state_stack; + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + ASSERT_EQ(builder.getSaveCount(), 1); + + { + auto mutator1 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 2); + mutator1.applyOpacity(rect, 0.5f); + ASSERT_EQ(builder.getSaveCount(), 2); + + { + auto mutator2 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 3); + mutator2.applyColorFilter(rect, color_filter); + + // The opacity will have been resolved by a saveLayer + ASSERT_EQ(builder.getSaveCount(), 4); + ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + } + ASSERT_EQ(builder.getSaveCount(), 2); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f); + } + ASSERT_EQ(builder.getSaveCount(), 1); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + + { + auto mutator1 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 2); + mutator1.applyColorFilter(rect, color_filter); + ASSERT_EQ(builder.getSaveCount(), 2); + + { + auto mutator2 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 3); + mutator2.applyOpacity(rect, 0.5f); + + // color filter applied to opacity can be applied together + ASSERT_EQ(builder.getSaveCount(), 3); + ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter); + ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f); + } + ASSERT_EQ(builder.getSaveCount(), 2); + ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + } + ASSERT_EQ(builder.getSaveCount(), 1); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); +} + +TEST(LayerStateStack, OpacityAndImageFilterInteraction) { + SkRect rect = {10, 10, 20, 20}; + std::shared_ptr image_filter = + std::make_shared(2.0f, 2.0f, DlTileMode::kClamp); + + LayerStateStack state_stack; + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + ASSERT_EQ(builder.getSaveCount(), 1); + + { + auto mutator1 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 2); + mutator1.applyOpacity(rect, 0.5f); + ASSERT_EQ(builder.getSaveCount(), 2); + + { + auto mutator2 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 3); + mutator2.applyImageFilter(rect, image_filter); + + // opacity applied to image filter can be applied together + ASSERT_EQ(builder.getSaveCount(), 3); + ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter); + ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f); + } + ASSERT_EQ(builder.getSaveCount(), 2); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f); + } + ASSERT_EQ(builder.getSaveCount(), 1); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + + { + auto mutator1 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 2); + mutator1.applyImageFilter(rect, image_filter); + ASSERT_EQ(builder.getSaveCount(), 2); + + { + auto mutator2 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 3); + mutator2.applyOpacity(rect, 0.5f); + + // The image filter will have been resolved by a saveLayer + ASSERT_EQ(builder.getSaveCount(), 4); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f); + } + ASSERT_EQ(builder.getSaveCount(), 2); + ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); + } + ASSERT_EQ(builder.getSaveCount(), 1); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_opacity(), SK_Scalar1); +} + +TEST(LayerStateStack, ColorFilterAndImageFilterInteraction) { + SkRect rect = {10, 10, 20, 20}; + std::shared_ptr color_filter = + std::make_shared(DlColor::kYellow(), + DlBlendMode::kColorBurn); + std::shared_ptr image_filter = + std::make_shared(2.0f, 2.0f, DlTileMode::kClamp); + + LayerStateStack state_stack; + DisplayListBuilder builder; + state_stack.set_delegate(&builder); + ASSERT_EQ(builder.getSaveCount(), 1); + + { + auto mutator1 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 2); + mutator1.applyColorFilter(rect, color_filter); + ASSERT_EQ(builder.getSaveCount(), 2); + + { + auto mutator2 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 3); + mutator2.applyImageFilter(rect, image_filter); + + // color filter applied to image filter can be applied together + ASSERT_EQ(builder.getSaveCount(), 3); + ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter); + ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter); + } + ASSERT_EQ(builder.getSaveCount(), 2); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter); + } + ASSERT_EQ(builder.getSaveCount(), 1); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); + + { + auto mutator1 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 2); + mutator1.applyImageFilter(rect, image_filter); + ASSERT_EQ(builder.getSaveCount(), 2); + + { + auto mutator2 = state_stack.save(); + ASSERT_EQ(builder.getSaveCount(), 3); + mutator2.applyColorFilter(rect, color_filter); + + // The image filter will have been resolved by a saveLayer + ASSERT_EQ(builder.getSaveCount(), 4); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter); + } + ASSERT_EQ(builder.getSaveCount(), 2); + ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); + } + ASSERT_EQ(builder.getSaveCount(), 1); + ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr); + ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index 0e5aacc98fe92..ddb80e24f225a 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -9,6 +9,7 @@ #include "flutter/flow/layer_snapshot_store.h" #include "flutter/flow/layers/cacheable_layer.h" #include "flutter/flow/layers/layer.h" +#include "flutter/flow/paint_utils.h" #include "flutter/flow/raster_cache.h" #include "flutter/fml/time/time_point.h" #include "flutter/fml/trace_event.h" @@ -44,7 +45,10 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, SkColorSpace* color_space = GetColorSpace(frame.canvas()); frame.context().raster_cache().SetCheckboardCacheImages( checkerboard_raster_cache_images_); + LayerStateStack state_stack; + state_stack.set_initial_state(cull_rect, frame.root_surface_transformation()); MutatorsStack stack; + state_stack.set_delegate(&stack); RasterCache* cache = ignore_raster_cache ? nullptr : &frame.context().raster_cache(); raster_cache_items_.clear(); @@ -54,21 +58,19 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, .raster_cache = cache, .gr_context = frame.gr_context(), .view_embedder = frame.view_embedder(), - .mutators_stack = stack, + .state_stack = state_stack, .dst_color_space = color_space, - .cull_rect = cull_rect, .surface_needs_readback = false, .raster_time = frame.context().raster_time(), .ui_time = frame.context().ui_time(), .texture_registry = frame.context().texture_registry(), - .checkerboard_offscreen_layers = checkerboard_offscreen_layers_, .frame_device_pixel_ratio = device_pixel_ratio_, .raster_cached_entries = &raster_cache_items_, .display_list_enabled = frame.display_list_builder() != nullptr, // clang-format on }; - root_layer_->Preroll(&context, frame.root_surface_transformation()); + root_layer_->Preroll(&context); return context.surface_needs_readback; } @@ -110,24 +112,18 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, return; } - SkISize canvas_size = frame.canvas()->getBaseLayerSize(); - SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height()); - internal_nodes_canvas.addCanvas(frame.canvas()); - if (frame.view_embedder() != nullptr) { - auto overlay_canvases = frame.view_embedder()->GetCurrentCanvases(); - for (size_t i = 0; i < overlay_canvases.size(); i++) { - internal_nodes_canvas.addCanvas(overlay_canvases[i]); - } + SkRect cull_rect = SkRect::Make(frame.canvas()->getDeviceClipBounds()); + LayerStateStack state_stack; + state_stack.set_initial_state(cull_rect, frame.root_surface_transformation()); + if (checkerboard_offscreen_layers_) { + state_stack.set_checkerboard_func(DrawCheckerboard); } + DisplayListBuilder* builder = frame.display_list_builder(); - DisplayListBuilderMultiplexer builder_multiplexer; if (builder) { - builder_multiplexer.addBuilder(builder); - if (frame.view_embedder()) { - for (auto* view_builder : frame.view_embedder()->GetCurrentBuilders()) { - builder_multiplexer.addBuilder(view_builder); - } - } + state_stack.set_delegate(builder); + } else { + state_stack.set_delegate(frame.canvas()); } // clear the previous snapshots. @@ -142,8 +138,9 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, ignore_raster_cache ? nullptr : &frame.context().raster_cache(); PaintContext context = { // clang-format off - .internal_nodes_canvas = &internal_nodes_canvas, - .leaf_nodes_canvas = frame.canvas(), + .state_stack = state_stack, + .canvas = frame.canvas(), + .builder = builder, .gr_context = frame.gr_context(), .dst_color_space = color_space, .view_embedder = frame.view_embedder(), @@ -151,13 +148,9 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, .ui_time = frame.context().ui_time(), .texture_registry = frame.context().texture_registry(), .raster_cache = cache, - .checkerboard_offscreen_layers = checkerboard_offscreen_layers_, .frame_device_pixel_ratio = device_pixel_ratio_, .layer_snapshot_store = snapshot_store, .enable_leaf_layer_tracing = enable_leaf_layer_tracing_, - .inherited_opacity = SK_Scalar1, - .leaf_nodes_builder = builder, - .builder_multiplexer = builder ? &builder_multiplexer : nullptr, // clang-format on }; @@ -177,42 +170,36 @@ sk_sp LayerTree::Flatten( GrDirectContext* gr_context) { TRACE_EVENT0("flutter", "LayerTree::Flatten"); - DisplayListCanvasRecorder builder(bounds); + DisplayListCanvasRecorder recorder(bounds); + + LayerStateStack state_stack; + state_stack.set_checkerboard_func(nullptr); + // No root surface transformation. So assume identity. + state_stack.set_initial_state(kGiantRect, SkMatrix::I()); MutatorsStack unused_stack; const FixedRefreshRateStopwatch unused_stopwatch; - SkMatrix root_surface_transformation; - - // No root surface transformation. So assume identity. - root_surface_transformation.reset(); PrerollContext preroll_context{ // clang-format off .raster_cache = nullptr, .gr_context = gr_context, .view_embedder = nullptr, - .mutators_stack = unused_stack, + .state_stack = state_stack, .dst_color_space = nullptr, - .cull_rect = kGiantRect, .surface_needs_readback = false, .raster_time = unused_stopwatch, .ui_time = unused_stopwatch, .texture_registry = texture_registry, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = device_pixel_ratio_ // clang-format on }; - SkISize canvas_size = builder.getBaseLayerSize(); - SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height()); - internal_nodes_canvas.addCanvas(&builder); - DisplayListBuilderMultiplexer multiplexer; - multiplexer.addBuilder(builder.builder().get()); - PaintContext paint_context = { // clang-format off - .internal_nodes_canvas = &internal_nodes_canvas, - .leaf_nodes_canvas = &builder, + .state_stack = state_stack, + .canvas = &recorder, + .builder = recorder.builder().get(), .gr_context = gr_context, .dst_color_space = nullptr, .view_embedder = nullptr, @@ -220,26 +207,29 @@ sk_sp LayerTree::Flatten( .ui_time = unused_stopwatch, .texture_registry = texture_registry, .raster_cache = nullptr, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = device_pixel_ratio_, .layer_snapshot_store = nullptr, .enable_leaf_layer_tracing = false, - .leaf_nodes_builder = builder.builder().get(), - .builder_multiplexer = &multiplexer, // clang-format on }; // Even if we don't have a root layer, we still need to create an empty // picture. if (root_layer_) { - root_layer_->Preroll(&preroll_context, root_surface_transformation); + state_stack.set_delegate(&unused_stack); + root_layer_->Preroll(&preroll_context); + FML_DCHECK(state_stack.is_empty()); + FML_DCHECK(state_stack.device_cull_rect() == kGiantRect); + FML_DCHECK(state_stack.transform_4x4() == SkM44()); + // The needs painting flag may be set after the preroll. So check it after. if (root_layer_->needs_painting(paint_context)) { + state_stack.set_delegate(recorder.builder()); root_layer_->Paint(paint_context); } } - return builder.Build(); + return recorder.Build(); } } // namespace flutter diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc index 950e542c3a685..09115f0c35ae7 100644 --- a/flow/layers/layer_tree_unittests.cc +++ b/flow/layers/layer_tree_unittests.cc @@ -194,37 +194,38 @@ TEST_F(LayerTreeTest, NeedsSystemComposite) { } TEST_F(LayerTreeTest, PrerollContextInitialization) { - MutatorsStack mock_mutators; + LayerStateStack state_stack; FixedRefreshRateStopwatch mock_raster_time; FixedRefreshRateStopwatch mock_ui_time; std::shared_ptr mock_registry; - auto expect_defaults = [&mock_mutators, &mock_raster_time, &mock_ui_time, + auto expect_defaults = [&state_stack, &mock_raster_time, &mock_ui_time, &mock_registry](const PrerollContext& context) { EXPECT_EQ(context.raster_cache, nullptr); EXPECT_EQ(context.gr_context, nullptr); EXPECT_EQ(context.view_embedder, nullptr); - EXPECT_EQ(&context.mutators_stack, &mock_mutators); + EXPECT_EQ(&context.state_stack, &state_stack); EXPECT_EQ(context.dst_color_space, nullptr); - EXPECT_EQ(context.cull_rect, SkRect::MakeEmpty()); + EXPECT_EQ(context.state_stack.device_cull_rect(), kGiantRect); + EXPECT_EQ(context.state_stack.transform_3x3(), SkMatrix::I()); + EXPECT_EQ(context.state_stack.transform_4x4(), SkM44()); EXPECT_EQ(context.surface_needs_readback, false); EXPECT_EQ(&context.raster_time, &mock_raster_time); EXPECT_EQ(&context.ui_time, &mock_ui_time); EXPECT_EQ(context.texture_registry.get(), mock_registry.get()); - EXPECT_EQ(context.checkerboard_offscreen_layers, false); EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f); EXPECT_EQ(context.has_platform_view, false); EXPECT_EQ(context.has_texture_layer, false); - EXPECT_EQ(context.subtree_can_inherit_opacity, false); + EXPECT_EQ(context.renderable_state_flags, 0); EXPECT_EQ(context.raster_cached_entries, nullptr); }; // These 4 initializers are required because they are handled by reference PrerollContext context{ - .mutators_stack = mock_mutators, + .state_stack = state_stack, .raster_time = mock_raster_time, .ui_time = mock_ui_time, .texture_registry = mock_registry, @@ -233,33 +234,32 @@ TEST_F(LayerTreeTest, PrerollContextInitialization) { } TEST_F(LayerTreeTest, PaintContextInitialization) { + LayerStateStack state_stack; FixedRefreshRateStopwatch mock_raster_time; FixedRefreshRateStopwatch mock_ui_time; std::shared_ptr mock_registry; - auto expect_defaults = [&mock_raster_time, &mock_ui_time, + auto expect_defaults = [&state_stack, &mock_raster_time, &mock_ui_time, &mock_registry](const PaintContext& context) { - EXPECT_EQ(context.internal_nodes_canvas, nullptr); - EXPECT_EQ(context.leaf_nodes_canvas, nullptr); + EXPECT_EQ(&context.state_stack, &state_stack); + EXPECT_EQ(context.canvas, nullptr); + EXPECT_EQ(context.builder, nullptr); EXPECT_EQ(context.gr_context, nullptr); EXPECT_EQ(context.view_embedder, nullptr); EXPECT_EQ(&context.raster_time, &mock_raster_time); EXPECT_EQ(&context.ui_time, &mock_ui_time); EXPECT_EQ(context.texture_registry.get(), mock_registry.get()); EXPECT_EQ(context.raster_cache, nullptr); - EXPECT_EQ(context.checkerboard_offscreen_layers, false); + EXPECT_EQ(context.state_stack.checkerboard_func(), nullptr); EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f); EXPECT_EQ(context.enable_leaf_layer_tracing, false); EXPECT_EQ(context.layer_snapshot_store, nullptr); - - EXPECT_EQ(context.inherited_opacity, SK_Scalar1); - EXPECT_EQ(context.leaf_nodes_builder, nullptr); - EXPECT_EQ(context.builder_multiplexer, nullptr); }; // These 4 initializers are required because they are handled by reference PaintContext context{ + .state_stack = state_stack, .raster_time = mock_raster_time, .ui_time = mock_ui_time, .texture_registry = mock_registry, diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index 111fbf798ea5b..eba507dcef0d6 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -36,39 +36,27 @@ void OpacityLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { +void OpacityLayer::Preroll(PrerollContext* context) { FML_DCHECK(!layers().empty()); // We can't be a leaf. - SkMatrix child_matrix = matrix; - child_matrix.preTranslate(offset_.fX, offset_.fY); + auto mutator = context->state_stack.save(); + mutator.translate(offset_); + mutator.applyOpacity(SkRect(), DlColor::toOpacity(alpha_)); - // Similar to what's done in TransformLayer::Preroll, we have to apply the - // reverse transformation to the cull rect to properly cull child layers. - context->cull_rect = context->cull_rect.makeOffset(-offset_.fX, -offset_.fY); - - context->mutators_stack.PushTransform( - SkMatrix::Translate(offset_.fX, offset_.fY)); - context->mutators_stack.PushOpacity(alpha_); - - AutoCache auto_cache = - AutoCache(layer_raster_cache_item_.get(), context, child_matrix); + AutoCache auto_cache = AutoCache(layer_raster_cache_item_.get(), context, + context->state_stack.transform_3x3()); Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); - // Collect inheritance information on our children in Preroll so that - // we can decide whether or not to use a saveLayer in Paint. - context->subtree_can_inherit_opacity = true; - // ContainerLayer will turn the flag off if any children are - // incompatible or if they overlap - ContainerLayer::Preroll(context, child_matrix); + ContainerLayer::Preroll(context); // We store the inheritance ability of our children for |Paint| - set_children_can_accept_opacity(context->subtree_can_inherit_opacity); + set_children_can_accept_opacity((context->renderable_state_flags & + LayerStateStack::kCallerCanApplyOpacity) != + 0); // Now we let our parent layers know that we, too, can inherit opacity // regardless of what our children are capable of - context->subtree_can_inherit_opacity = true; - context->mutators_stack.Pop(); - context->mutators_stack.Pop(); + context->renderable_state_flags |= LayerStateStack::kCallerCanApplyOpacity; set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY)); @@ -78,57 +66,22 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { // should tell the AutoCache object don't do raster_cache. auto_cache.ShouldNotBeCached(); } - - // Restore cull_rect - context->cull_rect = context->cull_rect.makeOffset(offset_.fX, offset_.fY); } void OpacityLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); - SkAutoCanvasRestore save(context.internal_nodes_canvas, true); - context.internal_nodes_canvas->translate(offset_.fX, offset_.fY); + auto mutator = context.state_stack.save(); + mutator.translate(offset_.fX, offset_.fY); if (context.raster_cache) { - context.internal_nodes_canvas->setMatrix( - RasterCacheUtil::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); + mutator.integralTransform(); } - SkScalar inherited_opacity = context.inherited_opacity; - SkScalar subtree_opacity = opacity() * inherited_opacity; + mutator.applyOpacity(child_paint_bounds(), opacity()); - if (children_can_accept_opacity()) { - context.inherited_opacity = subtree_opacity; + if (!context.state_stack.painting_is_nop()) { PaintChildren(context); - context.inherited_opacity = inherited_opacity; - return; } - - SkPaint paint; - paint.setAlphaf(subtree_opacity); - - if (layer_raster_cache_item_->Draw(context, &paint)) { - return; - } - - // Skia may clip the content with save_layer_bounds (although it's not a - // guaranteed clip). So we have to provide a big enough save_layer_bounds. To - // do so, we first remove the offset from paint bounds since it's already in - // the matrix. Then we round out the bounds. - // - // Note that the following lines are only accessible when the raster cache is - // not available (e.g., when we're using the software backend in golden - // tests). - SkRect save_layer_bounds; - paint_bounds() - .makeOffset(-offset_.fX, -offset_.fY) - .roundOut(&save_layer_bounds); - - Layer::AutoSaveLayer save_layer = - Layer::AutoSaveLayer::Create(context, save_layer_bounds, &paint); - context.inherited_opacity = SK_Scalar1; - PaintChildren(context); - context.inherited_opacity = inherited_opacity; } } // namespace flutter diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index aed2e7c7b0fd6..041efcef41938 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -31,7 +31,7 @@ class OpacityLayer : public CacheableContainerLayer { void Diff(DiffContext* context, const Layer* old_layer) override; - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc index 84a8ef29cd5a5..d760944641200 100644 --- a/flow/layers/opacity_layer_unittests.cc +++ b/flow/layers/opacity_layer_unittests.cc @@ -28,7 +28,7 @@ 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()), + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()), "\\!layers\\(\\)\\.empty\\(\\)"); } @@ -38,7 +38,7 @@ TEST_F(OpacityLayerTest, PaintingEmptyLayerDies) { std::make_shared(SK_AlphaOPAQUE, SkPoint::Make(0.0f, 0.0f)); layer->Add(mock_layer); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), SkPath().getBounds()); EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); EXPECT_EQ(layer->child_paint_bounds(), mock_layer->paint_bounds()); @@ -71,7 +71,8 @@ TEST_F(OpacityLayerTest, TranslateChildren) { layer->Add(mock_layer1); auto initial_transform = SkMatrix::Scale(2.0, 2.0); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); SkRect layer_bounds = mock_layer1->paint_bounds(); mock_layer1->parent_matrix().mapRect(&layer_bounds); @@ -107,7 +108,8 @@ TEST_F(OpacityLayerTest, CacheChild) { RasterCacheItem::CacheState::kNone); EXPECT_FALSE(cacheable_opacity_item->GetId().has_value()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); @@ -155,7 +157,8 @@ TEST_F(OpacityLayerTest, CacheChildren) { RasterCacheItem::CacheState::kNone); EXPECT_FALSE(cacheable_opacity_item->GetId().has_value()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); @@ -180,7 +183,6 @@ TEST_F(OpacityLayerTest, ShouldNotCacheChildren) { opacity_layer->Add(mock_layer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; use_mock_raster_cache(); @@ -193,9 +195,10 @@ TEST_F(OpacityLayerTest, ShouldNotCacheChildren) { RasterCacheItem::CacheState::kNone); EXPECT_FALSE(cacheable_opacity_item->GetId().has_value()); - opacity_layer->Preroll(preroll_context(), SkMatrix::I()); + opacity_layer->Preroll(preroll_context()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); @@ -217,7 +220,8 @@ TEST_F(OpacityLayerTest, FullyOpaque) { auto layer = std::make_shared(SK_AlphaOPAQUE, layer_offset); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_path.getBounds()); @@ -226,7 +230,7 @@ TEST_F(OpacityLayerTest, FullyOpaque) { 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)})); + std::vector({Mutator(layer_transform)})); const SkPaint opacity_paint = SkPaint(SkColors::kBlack); // A = 1.0f SkRect opacity_bounds; @@ -237,11 +241,7 @@ TEST_F(OpacityLayerTest, FullyOpaque) { MockCanvas::DrawCall{ 1, MockCanvas::ConcatMatrixData{SkM44(layer_transform)}}, 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}}, + 1, MockCanvas::DrawPathData{child_path, child_paint}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); layer->Paint(paint_context()); EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); @@ -261,7 +261,8 @@ TEST_F(OpacityLayerTest, FullyTransparent) { std::make_shared(SK_AlphaTRANSPARENT, layer_offset); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_path.getBounds()); @@ -273,16 +274,11 @@ TEST_F(OpacityLayerTest, FullyTransparent) { 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{SkM44(layer_transform)}}, - MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, - MockCanvas::DrawCall{ - 2, MockCanvas::ClipRectData{kEmptyRect, SkClipOp::kIntersect, - MockCanvas::kHard_ClipEdgeStyle}}, - MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, - MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + auto expected_draw_calls = + std::vector({MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{SkM44(layer_transform)}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); layer->Paint(paint_context()); EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); } @@ -301,7 +297,8 @@ TEST_F(OpacityLayerTest, HalfTransparent) { auto layer = std::make_shared(alpha_half, layer_offset); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_path.getBounds()); @@ -363,7 +360,8 @@ TEST_F(OpacityLayerTest, Nested) { layer1_child_bounds.join(child1_path.getBounds()); layer1_child_bounds.join(child3_path.getBounds()); SkRect expected_layer1_bounds = layer1_transform.mapRect(layer1_child_bounds); - layer1->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer1->Preroll(preroll_context()); 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()); @@ -396,11 +394,10 @@ TEST_F(OpacityLayerTest, Nested) { opacity1_paint.setAlphaf(alpha1 * (1.0 / SK_AlphaOPAQUE)); SkPaint opacity2_paint; opacity2_paint.setAlphaf(alpha2 * (1.0 / SK_AlphaOPAQUE)); - 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); + SkRect opacity1_bounds = + expected_layer1_bounds.makeOffset(-layer1_offset.fX, -layer1_offset.fY); + SkRect opacity2_bounds = + expected_layer2_bounds.makeOffset(-layer2_offset.fX, -layer2_offset.fY); auto expected_draw_calls = std::vector( {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, MockCanvas::DrawCall{ @@ -429,13 +426,12 @@ TEST_F(OpacityLayerTest, Nested) { } TEST_F(OpacityLayerTest, Readback) { - auto initial_transform = SkMatrix(); auto layer = std::make_shared(kOpaque_SkAlphaType, SkPoint()); layer->Add(std::make_shared(SkPath())); // OpacityLayer does not read from surface preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); // OpacityLayer blocks child with readback @@ -443,7 +439,7 @@ TEST_F(OpacityLayerTest, Readback) { mock_layer->set_fake_reads_surface(true); layer->Add(mock_layer); preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); } @@ -455,7 +451,7 @@ TEST_F(OpacityLayerTest, CullRectIsTransformed) { auto mock_layer = std::make_shared(SkPath()); clip_rect_layer->Add(opacity_layer); opacity_layer->Add(mock_layer); - clip_rect_layer->Preroll(preroll_context(), SkMatrix::I()); + clip_rect_layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->parent_cull_rect().fLeft, -20); EXPECT_EQ(mock_layer->parent_cull_rect().fTop, -20); } @@ -467,9 +463,9 @@ TEST_F(OpacityLayerTest, OpacityInheritanceCompatibleChild) { opacity_layer->Add(mock_layer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + opacity_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); } @@ -480,9 +476,9 @@ TEST_F(OpacityLayerTest, OpacityInheritanceIncompatibleChild) { opacity_layer->Add(mock_layer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + opacity_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); EXPECT_FALSE(opacity_layer->children_can_accept_opacity()); } @@ -495,14 +491,10 @@ TEST_F(OpacityLayerTest, OpacityInheritanceThroughContainer) { opacity_layer->Add(container_layer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); - // By default a container layer will not pass opacity through to - // its children - specific subclasses will have to enable this - // pass through by setting the flag to true themselves before - // calling their super method ContainerLayer::Preroll(). - EXPECT_FALSE(opacity_layer->children_can_accept_opacity()); + opacity_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); } TEST_F(OpacityLayerTest, OpacityInheritanceThroughTransform) { @@ -514,9 +506,9 @@ TEST_F(OpacityLayerTest, OpacityInheritanceThroughTransform) { opacity_layer->Add(transformLayer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + opacity_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); } @@ -530,9 +522,9 @@ TEST_F(OpacityLayerTest, OpacityInheritanceThroughImageFilter) { opacity_layer->Add(filter_layer); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + opacity_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); } @@ -547,9 +539,9 @@ TEST_F(OpacityLayerTest, OpacityInheritanceNestedWithCompatibleChild) { opacity_layer_1->Add(opacity_layer_2); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - opacity_layer_1->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + opacity_layer_1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); EXPECT_TRUE(opacity_layer_1->children_can_accept_opacity()); EXPECT_TRUE(opacity_layer_2->children_can_accept_opacity()); @@ -568,13 +560,8 @@ TEST_F(OpacityLayerTest, OpacityInheritanceNestedWithCompatibleChild) { { expected_builder.translate(offset2.fX, offset2.fY); /* mock_layer::Paint */ { - expected_builder.setColor(savelayer_paint.getAlpha() << 24); - expected_builder.saveLayer(&mock_path.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(mock_path); - } - expected_builder.restore(); + expected_builder.drawPath(mock_path, + DlPaint().setOpacity(inherited_opacity)); } } expected_builder.restore(); @@ -598,9 +585,9 @@ TEST_F(OpacityLayerTest, OpacityInheritanceNestedWithIncompatibleChild) { opacity_layer_1->Add(opacity_layer_2); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - opacity_layer_1->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + opacity_layer_1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); EXPECT_TRUE(opacity_layer_1->children_can_accept_opacity()); EXPECT_FALSE(opacity_layer_2->children_can_accept_opacity()); @@ -677,7 +664,9 @@ TEST_F(OpacityLayerTest, FullyOpaqueWithFractionalValues) { 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); + + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); const SkPaint opacity_paint = SkPaint(SkColors::kBlack); // A = 1.0f SkRect opacity_bounds; @@ -690,12 +679,8 @@ TEST_F(OpacityLayerTest, FullyOpaqueWithFractionalValues) { MockCanvas::DrawCall{ 1, MockCanvas::SetMatrixData{SkM44( RasterCacheUtil::GetIntegralTransCTM(layer_transform))}}, - MockCanvas::DrawCall{ - 1, MockCanvas::SaveLayerData{opacity_bounds, opacity_paint, nullptr, - 2}}, - MockCanvas::DrawCall{2, + MockCanvas::DrawCall{1, 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); diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index fe8b76288a90c..680873c5cab3f 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -97,16 +97,15 @@ void PerformanceOverlayLayer::Paint(PaintContext& context) const { SkScalar y = paint_bounds().y() + padding; SkScalar width = paint_bounds().width() - (padding * 2); SkScalar height = paint_bounds().height() / 2; - SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + auto mutator = context.state_stack.save(); VisualizeStopWatch( - context.leaf_nodes_canvas, context.raster_time, x, y, width, - height - padding, options_ & kVisualizeRasterizerStatistics, + context.canvas, context.raster_time, x, y, width, height - padding, + options_ & kVisualizeRasterizerStatistics, options_ & kDisplayRasterizerStatistics, "Raster", font_path_); - VisualizeStopWatch(context.leaf_nodes_canvas, context.ui_time, x, y + height, - width, height - padding, - options_ & kVisualizeEngineStatistics, + VisualizeStopWatch(context.canvas, context.ui_time, x, y + height, width, + height - padding, options_ & kVisualizeEngineStatistics, options_ & kDisplayEngineStatistics, "UI", font_path_); } diff --git a/flow/layers/performance_overlay_layer.h b/flow/layers/performance_overlay_layer.h index 2645a6a508452..62f26754574e0 100644 --- a/flow/layers/performance_overlay_layer.h +++ b/flow/layers/performance_overlay_layer.h @@ -39,6 +39,7 @@ class PerformanceOverlayLayer : public Layer { explicit PerformanceOverlayLayer(uint64_t options, const char* font_path = nullptr); + void Preroll(PrerollContext* context) override {} void Paint(PaintContext& context) const override; private: diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index 23e92937ea328..7c2f1edbe8265 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -58,17 +58,17 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { ASSERT_TRUE(surface != nullptr); + LayerStateStack state_stack; flutter::PaintContext paint_context = { // clang-format off - .internal_nodes_canvas = nullptr, - .leaf_nodes_canvas = surface->getCanvas(), + .state_stack = state_stack, + .canvas = surface->getCanvas(), .gr_context = nullptr, .view_embedder = nullptr, .raster_time = mock_stopwatch, .ui_time = mock_stopwatch, .texture_registry = nullptr, .raster_cache = nullptr, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, // clang-format on }; @@ -131,7 +131,7 @@ TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) { const uint64_t overlay_opts = kVisualizeRasterizerStatistics; auto layer = std::make_shared(overlay_opts); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -148,7 +148,7 @@ TEST_F(PerformanceOverlayLayerTest, InvalidOptions) { // this a constructor parameter and move the set_paint_bounds into Preroll layer->set_paint_bounds(layer_bounds); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), layer_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -166,7 +166,7 @@ TEST_F(PerformanceOverlayLayerTest, SimpleRasterizerStatistics) { // this a constructor parameter and move the set_paint_bounds into Preroll layer->set_paint_bounds(layer_bounds); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), layer_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -197,7 +197,7 @@ TEST_F(PerformanceOverlayLayerTest, MarkAsDirtyWhenResized) { const uint64_t overlay_opts = kVisualizeRasterizerStatistics; auto layer = std::make_shared(overlay_opts); layer->set_paint_bounds(SkRect::MakeLTRB(0.0f, 0.0f, 48.0f, 48.0f)); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); auto data = mock_canvas().draw_calls().front().data; auto image_data = std::get(data); @@ -206,7 +206,7 @@ TEST_F(PerformanceOverlayLayerTest, MarkAsDirtyWhenResized) { // Create a second PerformanceOverlayLayer with different bounds. layer = std::make_shared(overlay_opts); layer->set_paint_bounds(SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f)); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); layer->Paint(paint_context()); data = mock_canvas().draw_calls().back().data; image_data = std::get(data); diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index d84320228b5ea..77f347271e28e 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -50,13 +50,14 @@ void PhysicalShapeLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void PhysicalShapeLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void PhysicalShapeLayer::Preroll(PrerollContext* context) { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); SkRect child_paint_bounds = SkRect::MakeEmpty(); - PrerollChildren(context, matrix, &child_paint_bounds); + PrerollChildren(context, &child_paint_bounds); + context->renderable_state_flags = + UsesSaveLayer() ? Layer::kSaveLayerRenderFlags : 0; SkRect paint_bounds; if (elevation_ == 0) { @@ -65,7 +66,8 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context, // We will draw the shadow in Paint(), so add some margin to the paint // bounds to leave space for the shadow. paint_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds( - path_, elevation_, context->frame_device_pixel_ratio, matrix); + path_, elevation_, context->frame_device_pixel_ratio, + context->state_stack.transform_3x3()); } if (clip_behavior_ == Clip::none) { @@ -80,7 +82,7 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { if (elevation_ != 0) { DisplayListCanvasDispatcher::DrawShadow( - context.leaf_nodes_canvas, path_, shadow_color_, elevation_, + context.canvas, path_, shadow_color_, elevation_, SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio); } @@ -89,21 +91,21 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { paint.setColor(color_); paint.setAntiAlias(true); if (clip_behavior_ != Clip::antiAliasWithSaveLayer) { - context.leaf_nodes_canvas->drawPath(path_, paint); + context.canvas->drawPath(path_, paint); } - int save_count = context.internal_nodes_canvas->save(); + auto mutator = context.state_stack.save(); switch (clip_behavior_) { case Clip::hardEdge: - context.internal_nodes_canvas->clipPath(path_, false); + mutator.clipPath(path_, false); break; case Clip::antiAlias: - context.internal_nodes_canvas->clipPath(path_, true); + mutator.clipPath(path_, true); break; case Clip::antiAliasWithSaveLayer: { TRACE_EVENT0("flutter", "Canvas::saveLayer"); - context.internal_nodes_canvas->clipPath(path_, true); - context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr); + mutator.clipPath(path_, true); + mutator.saveLayer(paint_bounds()); } break; case Clip::none: break; @@ -114,18 +116,10 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { // (https://github.com/flutter/flutter/issues/18057#issue-328003931) // using saveLayer, we have to call drawPaint instead of drawPath as // anti-aliased drawPath will always have such artifacts. - context.leaf_nodes_canvas->drawPaint(paint); + context.canvas->drawPaint(paint); } PaintChildren(context); - - context.internal_nodes_canvas->restoreToCount(save_count); - - if (UsesSaveLayer()) { - if (context.checkerboard_offscreen_layers) { - DrawCheckerboard(context.internal_nodes_canvas, paint_bounds()); - } - } } } // namespace flutter diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index 0f2b24b87f754..b4a90785f6585 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -19,7 +19,7 @@ class PhysicalShapeLayer : public ContainerLayer { void Diff(DiffContext* context, const Layer* old_layer) override; - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc index 97937a9de8c97..b6c8e664254f1 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/flow/layers/physical_shape_layer_unittests.cc @@ -23,7 +23,7 @@ TEST_F(PhysicalShapeLayerTest, PaintingEmptyLayerDies) { 0.0f, // elevation SkPath(), Clip::none); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty()); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -54,7 +54,7 @@ TEST_F(PhysicalShapeLayerTest, NonEmptyLayer) { std::make_shared(SK_ColorGREEN, SK_ColorBLACK, 0.0f, // elevation layer_path, Clip::none); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds()); EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty()); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -90,7 +90,7 @@ TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathClip) { layer->Add(child2); SkRect child_paint_bounds = SkRect::MakeEmpty(); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); child_paint_bounds.join(child1->paint_bounds()); child_paint_bounds.join(child2->paint_bounds()); EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds()); @@ -118,8 +118,6 @@ TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathClip) { SkClipOp::kIntersect}}, MockCanvas::DrawCall{ 1, MockCanvas::DrawPathData{child1_path, child1_paint}}, - MockCanvas::DrawCall{ - 1, MockCanvas::DrawPathData{child2_path, child2_paint}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, })); } @@ -145,7 +143,7 @@ TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathNoClip) { layer->Add(child1); layer->Add(child2); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); SkRect child_bounds = child1->paint_bounds(); child_bounds.join(child2->paint_bounds()); SkRect total_bounds = child_bounds; @@ -182,7 +180,7 @@ TEST_F(PhysicalShapeLayerTest, ElevationSimple) { auto layer = std::make_shared( SK_ColorGREEN, SK_ColorBLACK, initial_elevation, layer_path, Clip::none); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and // their shadows , so we do not do any painting there. EXPECT_EQ(layer->paint_bounds(), @@ -229,7 +227,7 @@ TEST_F(PhysicalShapeLayerTest, ElevationComplex) { layers[0]->Add(layers[2]); layers[2]->Add(layers[3]); - layers[0]->Preroll(preroll_context(), SkMatrix()); + layers[0]->Preroll(preroll_context()); for (int i = 0; i < 4; i += 1) { // On Fuchsia, the system compositor handles all elevated // PhysicalShapeLayers and their shadows , so we do not do any painting @@ -357,7 +355,6 @@ static bool ReadbackResult(PrerollContext* context, Clip clip_behavior, const std::shared_ptr& child, bool before) { - const SkMatrix initial_matrix = SkMatrix(); const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); const SkPath layer_path = SkPath().addRect(layer_bounds); auto layer = @@ -368,7 +365,7 @@ static bool ReadbackResult(PrerollContext* context, layer->Add(child); } context->surface_needs_readback = before; - layer->Preroll(context, initial_matrix); + layer->Preroll(context); return context->surface_needs_readback; } @@ -426,9 +423,9 @@ TEST_F(PhysicalShapeLayerTest, OpacityInheritance) { layer_path, Clip::none); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - layer->Preroll(context, SkMatrix()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + context->renderable_state_flags = 0; + layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); } using PhysicalShapeLayerDiffTest = DiffContextTest; diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index a86886fb1b04f..d5b581ce05c84 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -11,8 +11,7 @@ PlatformViewLayer::PlatformViewLayer(const SkPoint& offset, int64_t view_id) : offset_(offset), size_(size), view_id_(view_id) {} -void PlatformViewLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void PlatformViewLayer::Preroll(PrerollContext* context) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), size_.height())); @@ -23,9 +22,10 @@ void PlatformViewLayer::Preroll(PrerollContext* context, } context->has_platform_view = true; set_subtree_has_platform_view(true); + auto mutators = context->state_stack.mutators_delegate(); std::unique_ptr params = - std::make_unique(matrix, size_, - context->mutators_stack, + std::make_unique(context->state_stack.transform_3x3(), + size_, *mutators, context->display_list_enabled); context->view_embedder->PrerollCompositeEmbeddedView(view_id_, std::move(params)); @@ -40,8 +40,13 @@ void PlatformViewLayer::Paint(PaintContext& context) const { } EmbedderPaintContext embedder_context = context.view_embedder->CompositeEmbeddedView(view_id_); - context.leaf_nodes_canvas = embedder_context.canvas; - context.leaf_nodes_builder = embedder_context.builder; + context.canvas = embedder_context.canvas; + context.builder = embedder_context.builder; + if (context.builder) { + context.state_stack.set_delegate(context.builder); + } else { + context.state_stack.set_delegate(context.canvas); + } } } // namespace flutter diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index 242b3734dd3b1..f812e52901ca0 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -15,7 +15,7 @@ class PlatformViewLayer : public Layer { public: PlatformViewLayer(const SkPoint& offset, const SkSize& size, int64_t view_id); - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; private: diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc index ecbbe3f39f036..c30661824c22a 100644 --- a/flow/layers/platform_view_layer_unittests.cc +++ b/flow/layers/platform_view_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/clip_rect_layer.h" #include "flutter/flow/layers/platform_view_layer.h" +#include "flutter/flow/layers/transform_layer.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_embedder.h" @@ -23,7 +24,7 @@ TEST_F(PlatformViewLayerTest, NullViewEmbedderDoesntPrerollCompositeOrPaint) { auto layer = std::make_shared(layer_offset, layer_size, view_id); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->has_platform_view); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeSize(layer_size) @@ -32,7 +33,7 @@ TEST_F(PlatformViewLayerTest, NullViewEmbedderDoesntPrerollCompositeOrPaint) { EXPECT_FALSE(layer->subtree_has_platform_view()); layer->Paint(paint_context()); - EXPECT_EQ(paint_context().leaf_nodes_canvas, &mock_canvas()); + EXPECT_EQ(paint_context().canvas, &mock_canvas()); EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); } @@ -54,7 +55,7 @@ TEST_F(PlatformViewLayerTest, ClippedPlatformViewPrerollsAndPaintsNothing) { auto embedder = MockViewEmbedder(); preroll_context()->view_embedder = &embedder; - parent_clip_layer->Preroll(preroll_context(), SkMatrix()); + parent_clip_layer->Preroll(preroll_context()); EXPECT_TRUE(preroll_context()->has_platform_view); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeSize(layer_size) @@ -67,7 +68,7 @@ TEST_F(PlatformViewLayerTest, ClippedPlatformViewPrerollsAndPaintsNothing) { EXPECT_TRUE(parent_clip_layer->subtree_has_platform_view()); parent_clip_layer->Paint(paint_context()); - EXPECT_EQ(paint_context().leaf_nodes_canvas, &mock_canvas()); + EXPECT_EQ(paint_context().canvas, &mock_canvas()); EXPECT_EQ( mock_canvas().draw_calls(), std::vector( @@ -75,11 +76,6 @@ TEST_F(PlatformViewLayerTest, ClippedPlatformViewPrerollsAndPaintsNothing) { MockCanvas::DrawCall{ 1, MockCanvas::ClipRectData{parent_clip, SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}}, - MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, - MockCanvas::DrawCall{ - 2, MockCanvas::ClipRectData{child_clip, SkClipOp::kIntersect, - MockCanvas::kHard_ClipEdgeStyle}}, - MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } @@ -91,9 +87,49 @@ TEST_F(PlatformViewLayerTest, OpacityInheritance) { std::make_shared(layer_offset, layer_size, view_id); PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - layer->Preroll(preroll_context(), SkMatrix()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + layer->Preroll(preroll_context()); + EXPECT_EQ(context->renderable_state_flags, 0); +} + +TEST_F(PlatformViewLayerTest, StateTransfer) { + const SkMatrix transform1 = SkMatrix::Translate(5, 5); + const SkMatrix transform2 = SkMatrix::Translate(15, 15); + const SkMatrix combined_transform = SkMatrix::Translate(20, 20); + 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; + const SkPath path1 = SkPath().addOval({10, 10, 20, 20}); + const SkPath path2 = SkPath().addOval({15, 15, 30, 30}); + + // transform_layer1 + // |- child1 + // |- platform_layer + // |- transform_layer2 + // |- child2 + auto transform_layer1 = std::make_shared(transform1); + auto transform_layer2 = std::make_shared(transform2); + auto platform_layer = + std::make_shared(layer_offset, layer_size, view_id); + auto child1 = std::make_shared(path1); + child1->set_expected_paint_matrix(transform1); + auto child2 = std::make_shared(path2); + child2->set_expected_paint_matrix(combined_transform); + transform_layer1->Add(child1); + transform_layer1->Add(platform_layer); + transform_layer1->Add(transform_layer2); + transform_layer2->Add(child2); + + auto embedder = MockViewEmbedder(); + DisplayListCanvasRecorder recorder({0, 0, 500, 500}); + embedder.AddRecorder(&recorder); + + PrerollContext* preroll_ctx = preroll_context(); + preroll_ctx->view_embedder = &embedder; + transform_layer1->Preroll(preroll_ctx); + + PaintContext& paint_ctx = paint_context(); + paint_ctx.view_embedder = &embedder; + transform_layer1->Paint(paint_ctx); } } // namespace testing diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index 1b88bc88dfd61..d22c41b1b27f6 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -35,40 +35,36 @@ void ShaderMaskLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void ShaderMaskLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { +void ShaderMaskLayer::Preroll(PrerollContext* context) { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); - SkMatrix child_matrix = matrix; - AutoCache cache = - AutoCache(layer_raster_cache_item_.get(), context, child_matrix); + AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context, + context->state_stack.transform_3x3()); - ContainerLayer::Preroll(context, child_matrix); + ContainerLayer::Preroll(context); // We always paint with a saveLayer (or a cached rendering), // so we can always apply opacity in any of those cases. - context->subtree_can_inherit_opacity = true; + context->renderable_state_flags = kSaveLayerRenderFlags; } void ShaderMaskLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); - AutoCachePaint cache_paint(context); + auto mutator = context.state_stack.save(); if (context.raster_cache) { - context.internal_nodes_canvas->setMatrix( - RasterCacheUtil::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); - } + mutator.integralTransform(); - if (context.raster_cache) { - if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { + SkPaint sk_paint; + if (layer_raster_cache_item_->Draw(context, + context.state_stack.fill(sk_paint))) { return; } } auto shader_rect = SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()); - if (context.leaf_nodes_builder) { - context.builder_multiplexer->saveLayer(&paint_bounds(), - cache_paint.dl_paint()); + mutator.saveLayer(paint_bounds()); + if (context.builder) { PaintChildren(context); DlPaint dl_paint; @@ -76,20 +72,17 @@ void ShaderMaskLayer::Paint(PaintContext& context) const { if (color_source_) { dl_paint.setColorSource(color_source_.get()); } - context.leaf_nodes_builder->translate(mask_rect_.left(), mask_rect_.top()); - context.leaf_nodes_builder->drawRect(shader_rect, dl_paint); - context.builder_multiplexer->restore(); + context.builder->translate(mask_rect_.left(), mask_rect_.top()); + context.builder->drawRect(shader_rect, dl_paint); } else { - Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( - context, paint_bounds(), cache_paint.sk_paint()); PaintChildren(context); SkPaint paint; paint.setBlendMode(ToSk(blend_mode_)); if (color_source_) { paint.setShader(color_source_->skia_object()); } - context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top()); - context.leaf_nodes_canvas->drawRect(shader_rect, paint); + context.canvas->translate(mask_rect_.left(), mask_rect_.top()); + context.canvas->drawRect(shader_rect, paint); } } diff --git a/flow/layers/shader_mask_layer.h b/flow/layers/shader_mask_layer.h index 491348ae66347..ae3b1d6accea5 100644 --- a/flow/layers/shader_mask_layer.h +++ b/flow/layers/shader_mask_layer.h @@ -18,7 +18,7 @@ class ShaderMaskLayer : public CacheableContainerLayer { void Diff(DiffContext* context, const Layer* old_layer) override; - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) 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 index f19d72e8bd742..73e23f7880abc 100644 --- a/flow/layers/shader_mask_layer_unittests.cc +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -26,7 +26,7 @@ TEST_F(ShaderMaskLayerTest, PaintingEmptyLayerDies) { auto layer = std::make_shared(nullptr, kEmptyRect, DlBlendMode::kSrc); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -61,7 +61,8 @@ TEST_F(ShaderMaskLayerTest, EmptyFilter) { DlBlendMode::kSrc); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); @@ -105,7 +106,8 @@ TEST_F(ShaderMaskLayerTest, SimpleFilter) { DlBlendMode::kSrc); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), child_bounds); EXPECT_EQ(layer->child_paint_bounds(), child_bounds); EXPECT_TRUE(layer->needs_painting(paint_context())); @@ -154,7 +156,8 @@ TEST_F(ShaderMaskLayerTest, MultipleChildren) { SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); 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); @@ -216,7 +219,8 @@ TEST_F(ShaderMaskLayerTest, Nested) { SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); - layer1->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer1->Preroll(preroll_context()); 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); @@ -270,7 +274,6 @@ TEST_F(ShaderMaskLayerTest, Nested) { } TEST_F(ShaderMaskLayerTest, Readback) { - auto initial_transform = SkMatrix(); const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); @@ -280,7 +283,7 @@ TEST_F(ShaderMaskLayerTest, Readback) { // ShaderMaskLayer does not read from surface preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); // ShaderMaskLayer blocks child with readback @@ -288,7 +291,7 @@ TEST_F(ShaderMaskLayerTest, Readback) { mock_layer->set_fake_reads_surface(true); layer->Add(mock_layer); preroll_context()->surface_needs_readback = false; - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); EXPECT_FALSE(preroll_context()->surface_needs_readback); } @@ -310,6 +313,7 @@ TEST_F(ShaderMaskLayerTest, LayerCached) { cache_canvas.setMatrix(cache_ctm); use_mock_raster_cache(); + preroll_context()->state_stack.set_initial_transform(initial_transform); const auto* cacheable_shader_masker_item = layer->raster_cache_item(); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); @@ -318,7 +322,7 @@ TEST_F(ShaderMaskLayerTest, LayerCached) { EXPECT_FALSE(cacheable_shader_masker_item->GetId().has_value()); // frame 1. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); @@ -327,7 +331,7 @@ TEST_F(ShaderMaskLayerTest, LayerCached) { EXPECT_FALSE(cacheable_shader_masker_item->GetId().has_value()); // frame 2. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0); EXPECT_EQ(cacheable_shader_masker_item->cache_state(), @@ -335,7 +339,7 @@ TEST_F(ShaderMaskLayerTest, LayerCached) { EXPECT_FALSE(cacheable_shader_masker_item->GetId().has_value()); // frame 3. - layer->Preroll(preroll_context(), initial_transform); + layer->Preroll(preroll_context()); LayerTree::TryToRasterCache(cacheable_items(), &paint_context()); EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1); EXPECT_EQ(cacheable_shader_masker_item->cache_state(), @@ -356,16 +360,14 @@ TEST_F(ShaderMaskLayerTest, OpacityInheritance) { // ShaderMaskLayers can always support opacity despite incompatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - shader_mask_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + shader_mask_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(shader_mask_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -410,7 +412,9 @@ TEST_F(ShaderMaskLayerTest, SimpleFilterWithRasterCache) { auto layer = std::make_shared(dl_filter, layer_bounds, DlBlendMode::kSrc); layer->Add(mock_layer); - layer->Preroll(preroll_context(), initial_transform); + + preroll_context()->state_stack.set_initial_transform(initial_transform); + layer->Preroll(preroll_context()); SkPaint filter_paint; filter_paint.setBlendMode(SkBlendMode::kSrc); @@ -418,21 +422,23 @@ TEST_F(ShaderMaskLayerTest, SimpleFilterWithRasterCache) { layer->Paint(paint_context()); EXPECT_EQ( mock_canvas().draw_calls(), - std::vector({MockCanvas::DrawCall{0, MockCanvas::SetMatrixData{SkM44( + std::vector({MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkM44( SkMatrix::Translate(0.0, 0.0))}}, MockCanvas::DrawCall{ - 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), - nullptr, 1}}, + 1, MockCanvas::SaveLayerData{child_bounds, SkPaint(), + nullptr, 2}}, MockCanvas::DrawCall{ - 1, MockCanvas::DrawPathData{child_path, child_paint}}, + 2, MockCanvas::DrawPathData{child_path, child_paint}}, MockCanvas::DrawCall{ - 1, MockCanvas::ConcatMatrixData{SkM44::Translate( + 2, MockCanvas::ConcatMatrixData{SkM44::Translate( layer_bounds.fLeft, layer_bounds.fTop)}}, MockCanvas::DrawCall{ - 1, MockCanvas::DrawRectData{SkRect::MakeWH( + 2, MockCanvas::DrawRectData{SkRect::MakeWH( layer_bounds.width(), layer_bounds.height()), filter_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } diff --git a/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index 00361d1606132..5e0b3a7b8dd92 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -40,11 +40,11 @@ void TextureLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void TextureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { +void TextureLayer::Preroll(PrerollContext* context) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), size_.height())); context->has_texture_layer = true; - context->subtree_can_inherit_opacity = true; + context->renderable_state_flags = LayerStateStack::kCallerCanApplyOpacity; } void TextureLayer::Paint(PaintContext& context) const { @@ -58,13 +58,14 @@ void TextureLayer::Paint(PaintContext& context) const { TRACE_EVENT_INSTANT0("flutter", "null texture"); return; } - AutoCachePaint cache_paint(context); + SkPaint sk_paint; + DlPaint dl_paint; Texture::PaintContext ctx{ - .canvas = context.leaf_nodes_canvas, - .builder = context.leaf_nodes_builder, + .canvas = context.canvas, + .builder = context.builder, .gr_context = context.gr_context, - .sk_paint = cache_paint.sk_paint(), - .dl_paint = cache_paint.dl_paint(), + .sk_paint = context.state_stack.fill(sk_paint), + .dl_paint = context.state_stack.fill(dl_paint), }; texture->Paint(ctx, paint_bounds(), freeze_, ToSk(sampling_)); } diff --git a/flow/layers/texture_layer.h b/flow/layers/texture_layer.h index 434d74a81de73..460e6a7fec12f 100644 --- a/flow/layers/texture_layer.h +++ b/flow/layers/texture_layer.h @@ -27,7 +27,7 @@ class TextureLayer : public Layer { const TextureLayer* as_texture_layer() const override { return this; } - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; private: diff --git a/flow/layers/texture_layer_unittests.cc b/flow/layers/texture_layer_unittests.cc index 7d45d1d54e825..0862a81da9ab3 100644 --- a/flow/layers/texture_layer_unittests.cc +++ b/flow/layers/texture_layer_unittests.cc @@ -22,7 +22,7 @@ TEST_F(TextureLayerTest, InvalidTexture) { auto layer = std::make_shared( layer_offset, layer_size, 0, false, DlImageSampling::kNearestNeighbor); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), (SkRect::MakeSize(layer_size) .makeOffset(layer_offset.fX, layer_offset.fY))); @@ -45,7 +45,7 @@ TEST_F(TextureLayerTest, PaintingEmptyLayerDies) { // Ensure the texture is located by the Layer. preroll_context()->texture_registry->RegisterTexture(mock_texture); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -80,7 +80,7 @@ TEST_F(TextureLayerTest, PaintingWithLinearSampling) { // Ensure the texture is located by the Layer. preroll_context()->texture_registry->RegisterTexture(mock_texture); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), (SkRect::MakeSize(layer_size) .makeOffset(layer_offset.fX, layer_offset.fY))); @@ -128,10 +128,10 @@ TEST_F(TextureLayerTest, OpacityInheritance) { // The texture layer always reports opacity compatibility. PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; context->texture_registry->RegisterTexture(mock_texture); - layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); // MockTexture has no actual textur to render into the // PaintContext canvas so we have no way to verify its diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 335c80d070739..0c72924fd65cf 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -40,39 +40,22 @@ void TransformLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { - SkMatrix child_matrix; - child_matrix.setConcat(matrix, transform_); - context->mutators_stack.PushTransform(transform_); - SkRect previous_cull_rect = context->cull_rect; - SkMatrix inverse_transform; - // Perspective projections don't produce rectangles that are useful for - // culling for some reason. - if (!transform_.hasPerspective() && transform_.invert(&inverse_transform)) { - inverse_transform.mapRect(&context->cull_rect); - } else { - context->cull_rect = kGiantRect; - } - - // Collect inheritance information on our children in Preroll so that - // we can pass it along by default. - context->subtree_can_inherit_opacity = true; +void TransformLayer::Preroll(PrerollContext* context) { + auto mutator = context->state_stack.save(); + mutator.transform(transform_); SkRect child_paint_bounds = SkRect::MakeEmpty(); - PrerollChildren(context, child_matrix, &child_paint_bounds); + PrerollChildren(context, &child_paint_bounds); transform_.mapRect(&child_paint_bounds); set_paint_bounds(child_paint_bounds); - - context->cull_rect = previous_cull_rect; - context->mutators_stack.Pop(); } void TransformLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); - SkAutoCanvasRestore save(context.internal_nodes_canvas, true); - context.internal_nodes_canvas->concat(transform_); + auto mutator = context.state_stack.save(); + mutator.transform(transform_); PaintChildren(context); } diff --git a/flow/layers/transform_layer.h b/flow/layers/transform_layer.h index 72ad561acd46e..cb4c712d7bfd7 100644 --- a/flow/layers/transform_layer.h +++ b/flow/layers/transform_layer.h @@ -17,7 +17,7 @@ class TransformLayer : public ContainerLayer { void Diff(DiffContext* context, const Layer* old_layer) override; - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/transform_layer_unittests.cc b/flow/layers/transform_layer_unittests.cc index e05334c2337eb..30744f39fffc1 100644 --- a/flow/layers/transform_layer_unittests.cc +++ b/flow/layers/transform_layer_unittests.cc @@ -19,7 +19,7 @@ using TransformLayerTest = LayerTest; TEST_F(TransformLayerTest, PaintingEmptyLayerDies) { auto layer = std::make_shared(SkMatrix()); // identity - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty()); EXPECT_FALSE(layer->needs_painting(paint_context())); @@ -48,8 +48,8 @@ TEST_F(TransformLayerTest, Identity) { auto layer = std::make_shared(SkMatrix()); // identity layer->Add(mock_layer); - preroll_context()->cull_rect = cull_rect; - layer->Preroll(preroll_context(), SkMatrix()); + preroll_context()->state_stack.set_initial_cull_rect(cull_rect); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); EXPECT_EQ(layer->child_paint_bounds(), mock_layer->paint_bounds()); @@ -57,7 +57,7 @@ TEST_F(TransformLayerTest, Identity) { EXPECT_TRUE(layer->needs_painting(paint_context())); 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())})); + EXPECT_EQ(mock_layer->parent_mutators(), MutatorsStack()); layer->Paint(paint_context()); EXPECT_EQ(mock_canvas().draw_calls(), @@ -68,8 +68,9 @@ TEST_F(TransformLayerTest, Identity) { 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::Translate(-0.5f, -0.5f); + SkRect local_cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkRect device_cull_rect = initial_transform.mapRect(local_cull_rect); SkMatrix layer_transform = SkMatrix::Translate(2.5f, 2.5f); SkMatrix inverse_layer_transform; EXPECT_TRUE(layer_transform.invert(&inverse_layer_transform)); @@ -78,8 +79,9 @@ TEST_F(TransformLayerTest, Simple) { auto layer = std::make_shared(layer_transform); layer->Add(mock_layer); - preroll_context()->cull_rect = cull_rect; - layer->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_state(device_cull_rect, + initial_transform); + layer->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); EXPECT_EQ(layer->paint_bounds(), layer_transform.mapRect(mock_layer->paint_bounds())); @@ -89,7 +91,7 @@ TEST_F(TransformLayerTest, Simple) { 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)); + inverse_layer_transform.mapRect(local_cull_rect)); EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_transform)})); @@ -107,8 +109,9 @@ TEST_F(TransformLayerTest, Simple) { 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::Translate(-0.5f, -0.5f); + SkRect local_cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkRect device_cull_rect = initial_transform.mapRect(local_cull_rect); SkMatrix layer1_transform = SkMatrix::Translate(2.5f, 2.5f); SkMatrix layer2_transform = SkMatrix::Translate(2.5f, 2.5f); SkMatrix inverse_layer1_transform, inverse_layer2_transform; @@ -121,8 +124,9 @@ TEST_F(TransformLayerTest, Nested) { layer1->Add(layer2); layer2->Add(mock_layer); - preroll_context()->cull_rect = cull_rect; - layer1->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_state(device_cull_rect, + initial_transform); + layer1->Preroll(preroll_context()); EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); EXPECT_EQ(layer2->paint_bounds(), layer2_transform.mapRect(mock_layer->paint_bounds())); @@ -139,7 +143,7 @@ TEST_F(TransformLayerTest, Nested) { layer2_transform)); EXPECT_EQ(mock_layer->parent_cull_rect(), inverse_layer2_transform.mapRect( - inverse_layer1_transform.mapRect(cull_rect))); + inverse_layer1_transform.mapRect(local_cull_rect))); EXPECT_EQ( mock_layer->parent_mutators(), std::vector({Mutator(layer2_transform), Mutator(layer1_transform)})); @@ -162,8 +166,9 @@ TEST_F(TransformLayerTest, Nested) { 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::Translate(-0.5f, -0.5f); + SkRect local_cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkRect device_cull_rect = initial_transform.mapRect(local_cull_rect); SkMatrix layer1_transform = SkMatrix::Translate(2.5f, 2.5f); SkMatrix layer2_transform = SkMatrix::Translate(2.5f, 2.5f); SkMatrix inverse_layer1_transform, inverse_layer2_transform; @@ -180,8 +185,9 @@ TEST_F(TransformLayerTest, NestedSeparated) { layer1->Add(layer2); layer2->Add(mock_layer2); - preroll_context()->cull_rect = cull_rect; - layer1->Preroll(preroll_context(), initial_transform); + preroll_context()->state_stack.set_initial_state(device_cull_rect, + initial_transform); + layer1->Preroll(preroll_context()); SkRect layer1_child_bounds = layer2->paint_bounds(); layer1_child_bounds.join(mock_layer1->paint_bounds()); SkRect expected_layer1_bounds = layer1_child_bounds; @@ -205,10 +211,10 @@ TEST_F(TransformLayerTest, NestedSeparated) { SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), layer2_transform)); EXPECT_EQ(mock_layer1->parent_cull_rect(), - inverse_layer1_transform.mapRect(cull_rect)); + inverse_layer1_transform.mapRect(local_cull_rect)); EXPECT_EQ(mock_layer2->parent_cull_rect(), inverse_layer2_transform.mapRect( - inverse_layer1_transform.mapRect(cull_rect))); + inverse_layer1_transform.mapRect(local_cull_rect))); EXPECT_EQ(mock_layer1->parent_mutators(), std::vector({Mutator(layer1_transform)})); EXPECT_EQ( @@ -242,9 +248,9 @@ TEST_F(TransformLayerTest, OpacityInheritance) { // TransformLayer will pass through compatibility from a compatible child PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - transform1->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + transform1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path2 = SkPath().addRect({40, 40, 50, 50}); auto mock2 = MockLayer::MakeOpacityCompatible(path2); @@ -252,9 +258,9 @@ TEST_F(TransformLayerTest, OpacityInheritance) { // TransformLayer will pass through compatibility from multiple // non-overlapping compatible children - context->subtree_can_inherit_opacity = false; - transform1->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + transform1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path3 = SkPath().addRect({20, 20, 40, 40}); auto mock3 = MockLayer::MakeOpacityCompatible(path3); @@ -262,18 +268,17 @@ TEST_F(TransformLayerTest, OpacityInheritance) { // TransformLayer will not pass through compatibility from multiple // overlapping children even if they are individually compatible - context->subtree_can_inherit_opacity = false; - transform1->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + transform1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); auto transform2 = std::make_shared(SkMatrix::Scale(2, 2)); transform2->Add(mock1); transform2->Add(mock2); // Double check first two children are compatible and non-overlapping - context->subtree_can_inherit_opacity = false; - transform2->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + transform2->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); auto path4 = SkPath().addRect({60, 60, 70, 70}); auto mock4 = MockLayer::Make(path4); @@ -281,9 +286,8 @@ TEST_F(TransformLayerTest, OpacityInheritance) { // The third child is non-overlapping, but not compatible so the // TransformLayer should end up incompatible - context->subtree_can_inherit_opacity = false; - transform2->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + transform2->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); } TEST_F(TransformLayerTest, OpacityInheritancePainting) { @@ -299,16 +303,15 @@ TEST_F(TransformLayerTest, OpacityInheritancePainting) { // TransformLayer will pass through compatibility from multiple // non-overlapping compatible children PrerollContext* context = preroll_context(); - context->subtree_can_inherit_opacity = false; - transform_layer->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + transform_layer->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); int opacity_alpha = 0x7F; SkPoint offset = SkPoint::Make(10, 10); auto opacity_layer = std::make_shared(opacity_alpha, offset); opacity_layer->Add(transform_layer); - context->subtree_can_inherit_opacity = false; - opacity_layer->Preroll(context, SkMatrix::I()); + opacity_layer->Preroll(context); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); DisplayListBuilder expected_builder; @@ -320,22 +323,10 @@ TEST_F(TransformLayerTest, OpacityInheritancePainting) { expected_builder.save(); expected_builder.transform(transform); /* child layer1 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path1.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path1); - } - expected_builder.restore(); + expected_builder.drawPath(path1, DlPaint().setAlpha(opacity_alpha)); } /* child layer2 paint */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.saveLayer(&path2.getBounds(), true); - { - expected_builder.setColor(0xFF000000); - expected_builder.drawPath(path2); - } - expected_builder.restore(); + expected_builder.drawPath(path2, DlPaint().setAlpha(opacity_alpha)); } expected_builder.restore(); } @@ -344,7 +335,7 @@ TEST_F(TransformLayerTest, OpacityInheritancePainting) { } opacity_layer->Paint(display_list_paint_context()); - EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build())); } using TransformLayerLayerDiffTest = DiffContextTest; diff --git a/flow/paint_utils.cc b/flow/paint_utils.cc index 0e61e16200b76..2d1d6f65029ef 100644 --- a/flow/paint_utils.cc +++ b/flow/paint_utils.cc @@ -24,14 +24,19 @@ sk_sp CreateCheckerboardShader(SkColor c1, SkColor c2, int size) { SkSamplingOptions()); } -void DrawCheckerboard(SkCanvas* canvas, SkColor c1, SkColor c2, int size) { - SkPaint paint; - paint.setShader(CreateCheckerboardShader(c1, c2, size)); - canvas->drawPaint(paint); -} - } // anonymous namespace +void DrawCheckerboard(SkCanvas* canvas, + DisplayListBuilder* builder, + const SkRect& rect) { + if (canvas) { + DrawCheckerboard(canvas, rect); + } + if (builder) { + DrawCheckerboard(builder, rect); + } +} + void DrawCheckerboard(SkCanvas* canvas, const SkRect& rect) { // Draw a checkerboard canvas->save(); @@ -43,7 +48,9 @@ void DrawCheckerboard(SkCanvas* canvas, const SkRect& rect) { SkColorSetARGB(64, rand() % 256, rand() % 256, rand() % 256); // NOLINTEND(clang-analyzer-security.insecureAPI.rand) - DrawCheckerboard(canvas, checkerboard_color, 0x00000000, 12); + SkPaint paint; + paint.setShader(CreateCheckerboardShader(checkerboard_color, 0x00000000, 12)); + canvas->drawPaint(paint); canvas->restore(); // Stroke the drawn area @@ -54,4 +61,29 @@ void DrawCheckerboard(SkCanvas* canvas, const SkRect& rect) { canvas->drawRect(rect, debug_paint); } +void DrawCheckerboard(DisplayListBuilder* builder, const SkRect& rect) { + // Draw a checkerboard + builder->save(); + builder->clipRect(rect, SkClipOp::kIntersect, false); + + // Secure random number generation isn't needed here. + // NOLINTBEGIN(clang-analyzer-security.insecureAPI.rand) + auto checkerboard_color = + SkColorSetARGB(64, rand() % 256, rand() % 256, rand() % 256); + // NOLINTEND(clang-analyzer-security.insecureAPI.rand) + + DlPaint paint; + paint.setColorSource(DlColorSource::From( + CreateCheckerboardShader(checkerboard_color, 0x00000000, 12))); + builder->drawPaint(paint); + builder->restore(); + + // Stroke the drawn area + DlPaint debug_paint; + debug_paint.setStrokeWidth(8); + debug_paint.setColor(SkColorSetA(checkerboard_color, 255)); + debug_paint.setDrawStyle(DlDrawStyle::kStroke); + builder->drawRect(rect, debug_paint); +} + } // namespace flutter diff --git a/flow/paint_utils.h b/flow/paint_utils.h index 432dd12811372..c01d7d1a8c5f4 100644 --- a/flow/paint_utils.h +++ b/flow/paint_utils.h @@ -5,13 +5,20 @@ #ifndef FLUTTER_FLOW_PAINT_UTILS_H_ #define FLUTTER_FLOW_PAINT_UTILS_H_ +#include "flutter/display_list/display_list_builder.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkRect.h" namespace flutter { +typedef void (*CheckerboardFunc)(SkCanvas*, DisplayListBuilder*, const SkRect&); + +void DrawCheckerboard(SkCanvas* canvas, + DisplayListBuilder* builder, + const SkRect& rect); void DrawCheckerboard(SkCanvas* canvas, const SkRect& rect); +void DrawCheckerboard(DisplayListBuilder* builder, const SkRect& rect); } // namespace flutter diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index c25f656e13c22..31f73f5d330a6 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -92,8 +92,8 @@ bool RasterCache::UpdateCacheEntry( RasterCacheKey key = RasterCacheKey(id, raster_cache_context.matrix); Entry& entry = cache_[key]; if (!entry.image) { - entry.image = - Rasterize(raster_cache_context, render_function, DrawCheckerboard); + void (*func)(SkCanvas*, const SkRect& rect) = DrawCheckerboard; + entry.image = Rasterize(raster_cache_context, render_function, func); if (entry.image != nullptr) { switch (id.type()) { case RasterCacheKeyType::kDisplayList: { diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index d72b91abf3b1b..0a209da0ebda1 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -38,13 +38,13 @@ TEST(RasterCache, MetricsOmitUnpopulatedEntries) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -94,13 +94,13 @@ TEST(RasterCache, ThresholdIsRespectedForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -138,10 +138,11 @@ TEST(RasterCache, SetCheckboardCacheImages) { SkMatrix matrix = SkMatrix::I(); auto display_list = GetSampleDisplayList(); + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& paint_context = paint_context_holder.paint_context; auto dummy_draw_function = [](SkCanvas* canvas) {}; bool did_draw_checkerboard = false; @@ -178,13 +179,13 @@ TEST(RasterCache, AccessThresholdOfZeroDisablesCachingForSkPicture) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -207,13 +208,13 @@ TEST(RasterCache, AccessThresholdOfZeroDisablesCachingForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -238,13 +239,13 @@ TEST(RasterCache, PictureCacheLimitPerFrameIsRespectedWhenZeroForSkPicture) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -274,13 +275,13 @@ TEST(RasterCache, PictureCacheLimitPerFrameIsRespectedWhenZeroForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -314,13 +315,13 @@ TEST(RasterCache, EvitUnusedCacheEntries) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -424,13 +425,13 @@ TEST(RasterCache, DeviceRectRoundOutForDisplayList) { SkCanvas canvas(100, 100, nullptr); canvas.setMatrix(ctm); + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -467,13 +468,13 @@ TEST(RasterCache, NestedOpCountMetricUsedForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -514,13 +515,13 @@ TEST(RasterCache, NaiveComplexityScoringDisplayList) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -580,13 +581,13 @@ TEST(RasterCache, DisplayListWithSingularMatrixIsNotCached) { SkCanvas dummy_canvas; SkPaint paint; + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( - &cache, &raster_time, &ui_time, &mutators_stack); + state_stack, &cache, &raster_time, &ui_time); PaintContextHolder paint_context_holder = - GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, &cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; diff --git a/flow/raster_cache_util.h b/flow/raster_cache_util.h index 71836fdaf502d..18019fc83192f 100644 --- a/flow/raster_cache_util.h +++ b/flow/raster_cache_util.h @@ -6,6 +6,7 @@ #define FLUTTER_FLOW_RASTER_CACHE_UTIL_H_ #include "flutter/fml/logging.h" +#include "include/core/SkM44.h" #include "include/core/SkMatrix.h" #include "include/core/SkRect.h" @@ -87,6 +88,46 @@ struct RasterCacheUtil { result[SkMatrix::kMTransY] = SkScalarRoundToScalar(ctm.getTranslateY()); return result; } + + /** + * @brief Snap the translation components of the matrix to integers. + * + * The snapping will only happen if the matrix only has scale and translation + * transformations. This is used, along with GetRoundedOutDeviceBounds, to + * ensure that the textures drawn by the raster cache are exactly aligned to + * physical pixels. Any layers that participate in raster caching must align + * themselves to physical pixels even when not cached to prevent a change in + * apparent location if caching is later applied. + * + * @param ctm the current transformation matrix. + * @return SkM44 the snapped transformation matrix. + */ + static SkM44 GetIntegralTransCTM(const SkM44& ctm) { + // Avoid integral snapping if the matrix has complex transformation to avoid + // the artifact observed in https://github.com/flutter/flutter/issues/41654. + if (ctm.rc(0, 1) != 0 || ctm.rc(0, 2) != 0) { + // X multiplied by either Y or Z + return ctm; + } + if (ctm.rc(1, 0) != 0 || ctm.rc(1, 2) != 0) { + // Y multiplied by either X or Z + return ctm; + } + // We do not need to worry about the Z row unless the W row + // has perspective entries... + if (ctm.rc(3, 0) != 0 || ctm.rc(3, 1) != 0 || ctm.rc(3, 2) != 0 || + ctm.rc(3, 3) != 1) { + // W not identity row, therefore perspective is applied + return ctm; + } + + SkM44 result = ctm; + result.setRC(0, 3, SkScalarRoundToScalar(ctm.rc(0, 3))); + result.setRC(1, 3, SkScalarRoundToScalar(ctm.rc(1, 3))); + // No need to worry about Z translation because it has no effect + // without perspective entries... + return result; + } }; } // namespace flutter diff --git a/flow/testing/auto_save_layer_unittests.cc b/flow/testing/auto_save_layer_unittests.cc deleted file mode 100644 index a44ebdbead301..0000000000000 --- a/flow/testing/auto_save_layer_unittests.cc +++ /dev/null @@ -1,117 +0,0 @@ -// 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/layer_test.h" - -#include "gtest/gtest.h" - -namespace flutter { -namespace testing { - -using AutoSaveLayerTests = LayerTest; - -TEST_F(AutoSaveLayerTests, SaveLayerOnInternalNodesCanvasByDefault) { - // For: - // static AutoSaveLayer Create(const PaintContext& paint_context, - // const SkRect& bounds, - // const SkPaint* paint, - // SaveMode save_mode); - { - int saved_count_before = - paint_context().internal_nodes_canvas->getSaveCount(); - { - const SkPaint paint; - const SkRect rect = SkRect::MakeEmpty(); - Layer::AutoSaveLayer save = - Layer::AutoSaveLayer::Create(paint_context(), rect, &paint); - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before + 1); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before + 1); - } - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before); - } - // For: - // static AutoSaveLayer Create(const PaintContext& paint_context, - // const SkCanvas::SaveLayerRec& layer_rec, - // SaveMode save_mode); - { - int saved_count_before = - paint_context().internal_nodes_canvas->getSaveCount(); - { - const SkPaint paint; - const SkRect rect = SkRect::MakeEmpty(); - const SkCanvas::SaveLayerRec save_layer_rect = - SkCanvas::SaveLayerRec{&rect, &paint, nullptr, 0}; - Layer::AutoSaveLayer save = - Layer::AutoSaveLayer::Create(paint_context(), save_layer_rect); - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before + 1); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before + 1); - } - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before); - } -} - -TEST_F(AutoSaveLayerTests, SaveLayerOnlyOnLeafNodesCanvas) { - // For: - // static AutoSaveLayer Create(const PaintContext& paint_context, - // const SkRect& bounds, - // const SkPaint* paint, - // SaveMode save_mode); - { - int saved_count_before = - paint_context().internal_nodes_canvas->getSaveCount(); - { - const SkPaint paint; - const SkRect rect = SkRect::MakeEmpty(); - Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( - paint_context(), rect, &paint, - Layer::AutoSaveLayer::SaveMode::kLeafNodesCanvas); - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before + 1); - } - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before); - } - // For: - // static AutoSaveLayer Create(const PaintContext& paint_context, - // const SkCanvas::SaveLayerRec& layer_rec, - // SaveMode save_mode); - { - int saved_count_before = - paint_context().internal_nodes_canvas->getSaveCount(); - { - const SkPaint paint; - const SkRect rect = SkRect::MakeEmpty(); - const SkCanvas::SaveLayerRec save_layer_rect = - SkCanvas::SaveLayerRec{&rect, &paint, nullptr, 0}; - Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( - paint_context(), save_layer_rect, - Layer::AutoSaveLayer::SaveMode::kLeafNodesCanvas); - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before + 1); - } - EXPECT_EQ(paint_context().internal_nodes_canvas->getSaveCount(), - saved_count_before); - EXPECT_EQ(paint_context().leaf_nodes_canvas->getSaveCount(), - saved_count_before); - } -} - -} // namespace testing -} // namespace flutter diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h index 60b4536056ab6..8d0f1519f17c4 100644 --- a/flow/testing/layer_test.h +++ b/flow/testing/layer_test.h @@ -51,14 +51,12 @@ class LayerTestBase : public CanvasTestBase { .raster_cache = nullptr, .gr_context = nullptr, .view_embedder = nullptr, - .mutators_stack = mutators_stack_, + .state_stack = preroll_state_stack_, .dst_color_space = TestT::mock_color_space(), - .cull_rect = kGiantRect, .surface_needs_readback = false, .raster_time = raster_time_, .ui_time = ui_time_, .texture_registry = texture_registry_, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, .has_platform_view = false, .raster_cached_entries = &cacheable_items_, @@ -66,54 +64,52 @@ class LayerTestBase : public CanvasTestBase { }, paint_context_{ // clang-format off - .internal_nodes_canvas = TestT::mock_internal_canvas(), - .leaf_nodes_canvas = &TestT::mock_canvas(), + .state_stack = paint_state_stack_, + .canvas = &TestT::mock_canvas(), .gr_context = nullptr, .view_embedder = nullptr, .raster_time = raster_time_, .ui_time = ui_time_, .texture_registry = texture_registry_, .raster_cache = nullptr, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, // clang-format on }, display_list_recorder_(kDlBounds), - internal_display_list_canvas_(kDlBounds.width(), kDlBounds.height()), display_list_paint_context_{ // clang-format off - .internal_nodes_canvas = &internal_display_list_canvas_, - .leaf_nodes_canvas = &display_list_recorder_, + .state_stack = display_list_state_stack_, + .canvas = &display_list_recorder_, + .builder = display_list_recorder_.builder().get(), .gr_context = nullptr, .view_embedder = nullptr, .raster_time = raster_time_, .ui_time = ui_time_, .texture_registry = texture_registry_, .raster_cache = nullptr, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, - .leaf_nodes_builder = display_list_recorder_.builder().get(), - .builder_multiplexer = &display_list_multiplexer_, // clang-format on }, - check_board_context_{ + checkerboard_context_{ // clang-format off - .internal_nodes_canvas = TestT::mock_internal_canvas(), - .leaf_nodes_canvas = &TestT::mock_canvas(), + .state_stack = checkerboard_state_stack_, + .canvas = &TestT::mock_canvas(), .gr_context = nullptr, .view_embedder = nullptr, .raster_time = raster_time_, .ui_time = ui_time_, .texture_registry = texture_registry_, .raster_cache = nullptr, - .checkerboard_offscreen_layers = true, .frame_device_pixel_ratio = 1.0f, // clang-format on } { - internal_display_list_canvas_.addCanvas(&display_list_recorder_); - display_list_multiplexer_.addBuilder( - display_list_recorder_.builder().get()); use_null_raster_cache(); + preroll_state_stack_.set_delegate(&mutators_stack_); + paint_state_stack_.set_delegate(&TestT::mock_canvas()); + display_list_state_stack_.set_delegate(display_list_recorder_); + checkerboard_state_stack_.set_delegate(&TestT::mock_canvas()); + checkerboard_state_stack_.set_checkerboard_func(draw_checkerboard); + checkerboard_paint_.setColor(checkerboard_color_); } /** @@ -176,7 +172,8 @@ class LayerTestBase : public CanvasTestBase { PaintContext& display_list_paint_context() { return display_list_paint_context_; } - PaintContext& check_board_context() { return check_board_context_; } + const SkPaint& checkerboard_paint() { return checkerboard_paint_; } + PaintContext& checkerboard_context() { return checkerboard_context_; } LayerSnapshotStore& layer_snapshot_store() { return snapshot_store_; } sk_sp display_list() { @@ -184,10 +181,8 @@ class LayerTestBase : public CanvasTestBase { display_list_ = display_list_recorder_.Build(); // null out the canvas and recorder fields of the PaintContext // to prevent future use. - display_list_paint_context_.leaf_nodes_canvas = nullptr; - display_list_paint_context_.internal_nodes_canvas = nullptr; - display_list_paint_context_.leaf_nodes_builder = nullptr; - display_list_paint_context_.builder_multiplexer = nullptr; + display_list_paint_context_.canvas = nullptr; + display_list_paint_context_.builder = nullptr; } return display_list_; } @@ -210,6 +205,26 @@ class LayerTestBase : public CanvasTestBase { display_list_paint_context_.raster_cache = raster_cache_.get(); } + static constexpr SkColor checkerboard_color_ = 0x42424242; + + static void draw_checkerboard(SkCanvas* canvas, + DisplayListBuilder* builder, + const SkRect& rect) { + if (canvas) { + SkPaint paint; + paint.setColor(checkerboard_color_); + canvas->drawRect(rect, paint); + } + if (builder) { + DlPaint paint; + paint.setColor(checkerboard_color_); + builder->drawRect(rect, paint); + } + } + + LayerStateStack preroll_state_stack_; + LayerStateStack paint_state_stack_; + LayerStateStack checkerboard_state_stack_; FixedRefreshRateStopwatch raster_time_; FixedRefreshRateStopwatch ui_time_; MutatorsStack mutators_stack_; @@ -219,11 +234,11 @@ class LayerTestBase : public CanvasTestBase { PrerollContext preroll_context_; PaintContext paint_context_; DisplayListCanvasRecorder display_list_recorder_; - DisplayListBuilderMultiplexer display_list_multiplexer_; + LayerStateStack display_list_state_stack_; sk_sp display_list_; - SkNWayCanvas internal_display_list_canvas_; PaintContext display_list_paint_context_; - PaintContext check_board_context_; + SkPaint checkerboard_paint_; + PaintContext checkerboard_context_; LayerSnapshotStore snapshot_store_; std::vector cacheable_items_; diff --git a/flow/testing/mock_embedder.cc b/flow/testing/mock_embedder.cc index 8004308d796c2..4767481ea6f5a 100644 --- a/flow/testing/mock_embedder.cc +++ b/flow/testing/mock_embedder.cc @@ -11,6 +11,15 @@ MockViewEmbedder::MockViewEmbedder() = default; MockViewEmbedder::~MockViewEmbedder() = default; +void MockViewEmbedder::AddCanvas(SkCanvas* canvas) { + contexts_.emplace_back(EmbedderPaintContext{canvas, nullptr}); +} + +void MockViewEmbedder::AddRecorder(DisplayListCanvasRecorder* recorder) { + contexts_.emplace_back( + EmbedderPaintContext{recorder, recorder->builder().get()}); +} + // |ExternalViewEmbedder| SkCanvas* MockViewEmbedder::GetRootCanvas() { return nullptr; @@ -43,7 +52,9 @@ std::vector MockViewEmbedder::GetCurrentBuilders() { // |ExternalViewEmbedder| EmbedderPaintContext MockViewEmbedder::CompositeEmbeddedView(int view_id) { - return {nullptr, nullptr}; + EmbedderPaintContext context = contexts_.front(); + contexts_.pop_front(); + return context; } } // namespace testing diff --git a/flow/testing/mock_embedder.h b/flow/testing/mock_embedder.h index abc57d3d28530..70e8d8ce1cc29 100644 --- a/flow/testing/mock_embedder.h +++ b/flow/testing/mock_embedder.h @@ -16,6 +16,9 @@ class MockViewEmbedder : public ExternalViewEmbedder { ~MockViewEmbedder(); + void AddCanvas(SkCanvas* canvas); + void AddRecorder(DisplayListCanvasRecorder* recorder); + // |ExternalViewEmbedder| SkCanvas* GetRootCanvas() override; @@ -42,6 +45,9 @@ class MockViewEmbedder : public ExternalViewEmbedder { // |ExternalViewEmbedder| EmbedderPaintContext CompositeEmbeddedView(int view_id) override; + + private: + std::deque contexts_; }; } // namespace testing diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc index 838095ad49389..77dd3f216c328 100644 --- a/flow/testing/mock_layer.cc +++ b/flow/testing/mock_layer.cc @@ -30,10 +30,10 @@ void MockLayer::Diff(DiffContext* context, const Layer* old_layer) { context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } -void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { - parent_mutators_ = context->mutators_stack; - parent_matrix_ = matrix; - parent_cull_rect_ = context->cull_rect; +void MockLayer::Preroll(PrerollContext* context) { + parent_mutators_ = *context->state_stack.mutators_delegate(); + parent_matrix_ = context->state_stack.transform_3x3(); + parent_cull_rect_ = context->state_stack.local_cull_rect(); set_parent_has_platform_view(context->has_platform_view); set_parent_has_texture_layer(context->has_texture_layer); @@ -45,42 +45,41 @@ void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { context->surface_needs_readback = true; } if (fake_opacity_compatible()) { - context->subtree_can_inherit_opacity = true; + context->renderable_state_flags = LayerStateStack::kCallerCanApplyOpacity; } } void MockLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); - if (context.inherited_opacity < SK_Scalar1) { - SkPaint p; - p.setAlphaf(context.inherited_opacity); - context.leaf_nodes_canvas->saveLayer(fake_paint_path_.getBounds(), &p); - } - context.leaf_nodes_canvas->drawPath(fake_paint_path_, fake_paint_); - if (context.inherited_opacity < SK_Scalar1) { - context.leaf_nodes_canvas->restore(); + if (expected_paint_matrix_.has_value()) { + SkMatrix matrix = context.builder ? context.builder->getTransform() + : context.canvas->getTotalMatrix(); + + ASSERT_EQ(matrix, expected_paint_matrix_.value()); } + + SkPaint sk_paint = fake_paint_; + context.state_stack.fill(sk_paint); + context.canvas->drawPath(fake_paint_path_, sk_paint); } -void MockCacheableContainerLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void MockCacheableContainerLayer::Preroll(PrerollContext* context) { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); - SkMatrix child_matrix = matrix; - auto cache = AutoCache(layer_raster_cache_item_.get(), context, child_matrix); + auto cache = AutoCache(layer_raster_cache_item_.get(), context, + context->state_stack.transform_3x3()); - ContainerLayer::Preroll(context, child_matrix); + ContainerLayer::Preroll(context); } -void MockCacheableLayer::Preroll(PrerollContext* context, - const SkMatrix& matrix) { +void MockCacheableLayer::Preroll(PrerollContext* context) { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); - SkMatrix child_matrix = matrix; - auto cache = AutoCache(raster_cache_item_.get(), context, child_matrix); + auto cache = AutoCache(raster_cache_item_.get(), context, + context->state_stack.transform_3x3()); - MockLayer::Preroll(context, child_matrix); + MockLayer::Preroll(context); } } // namespace testing diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h index bc351f3fc3270..804e837942218 100644 --- a/flow/testing/mock_layer.h +++ b/flow/testing/mock_layer.h @@ -37,7 +37,7 @@ class MockLayer : public Layer { return mock_layer; } - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; void Paint(PaintContext& context) const override; const MutatorsStack& parent_mutators() { return parent_mutators_; } @@ -102,12 +102,17 @@ class MockLayer : public Layer { return *this; } + void set_expected_paint_matrix(const SkMatrix& matrix) { + expected_paint_matrix_ = matrix; + } + private: MutatorsStack parent_mutators_; SkMatrix parent_matrix_; SkRect parent_cull_rect_ = SkRect::MakeEmpty(); SkPath fake_paint_path_; SkPaint fake_paint_; + std::optional expected_paint_matrix_; static constexpr int kParentHasPlatformView = 1 << 0; static constexpr int kParentHasTextureLayer = 1 << 1; @@ -135,7 +140,7 @@ class MockCacheableContainerLayer : public CacheableContainerLayer { return std::make_shared(); } - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; explicit MockCacheableContainerLayer(bool cache_children = false) : CacheableContainerLayer(3, cache_children) {} @@ -159,7 +164,7 @@ class MockCacheableLayer : public MockLayer { return raster_cache_item_.get(); } - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Preroll(PrerollContext* context) override; private: std::unique_ptr raster_cache_item_; diff --git a/flow/testing/mock_layer_unittests.cc b/flow/testing/mock_layer_unittests.cc index d7f007eed25db..4e62dd6aeb1ee 100644 --- a/flow/testing/mock_layer_unittests.cc +++ b/flow/testing/mock_layer_unittests.cc @@ -25,7 +25,7 @@ TEST_F(MockLayerTest, PaintBeforePrerollDies) { TEST_F(MockLayerTest, PaintingEmptyLayerDies) { auto layer = std::make_shared(SkPath(), SkPaint()); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(layer->paint_bounds(), SkPath().getBounds()); EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), @@ -38,20 +38,24 @@ TEST_F(MockLayerTest, SimpleParams) { const SkPaint paint = SkPaint(SkColors::kBlue); const SkMatrix start_matrix = SkMatrix::Translate(1.0f, 2.0f); const SkMatrix scale_matrix = SkMatrix::Scale(0.5f, 0.5f); - const SkRect cull_rect = SkRect::MakeWH(5.0f, 5.0f); + const SkMatrix combined_matrix = SkMatrix::Concat(start_matrix, scale_matrix); + const SkRect local_cull_rect = SkRect::MakeWH(5.0f, 5.0f); + const SkRect device_cull_rect = combined_matrix.mapRect(local_cull_rect); 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()->state_stack.set_initial_state(device_cull_rect, + start_matrix); + auto mutator = preroll_context()->state_stack.save(); + mutator.transform(scale_matrix); preroll_context()->has_platform_view = parent_has_platform_view; - layer->Preroll(preroll_context(), start_matrix); + layer->Preroll(preroll_context()); EXPECT_EQ(preroll_context()->has_platform_view, false); EXPECT_EQ(layer->paint_bounds(), path.getBounds()); EXPECT_TRUE(layer->needs_painting(paint_context())); 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_matrix(), combined_matrix); + EXPECT_EQ(layer->parent_cull_rect(), local_cull_rect); EXPECT_EQ(layer->parent_has_platform_view(), parent_has_platform_view); layer->Paint(paint_context()); @@ -65,7 +69,7 @@ TEST_F(MockLayerTest, FakePlatformView) { layer->set_fake_has_platform_view(true); EXPECT_EQ(preroll_context()->has_platform_view, false); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(preroll_context()->has_platform_view, true); } @@ -74,7 +78,7 @@ TEST_F(MockLayerTest, SaveLayerOnLeafNodesCanvas) { layer->set_fake_has_platform_view(true); EXPECT_EQ(preroll_context()->has_platform_view, false); - layer->Preroll(preroll_context(), SkMatrix()); + layer->Preroll(preroll_context()); EXPECT_EQ(preroll_context()->has_platform_view, true); } @@ -83,14 +87,13 @@ TEST_F(MockLayerTest, OpacityInheritance) { PrerollContext* context = preroll_context(); auto mock1 = std::make_shared(path1); - context->subtree_can_inherit_opacity = false; - mock1->Preroll(context, SkMatrix::I()); - EXPECT_FALSE(context->subtree_can_inherit_opacity); + mock1->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, 0); auto mock2 = MockLayer::MakeOpacityCompatible(path1); - context->subtree_can_inherit_opacity = false; - mock2->Preroll(context, SkMatrix::I()); - EXPECT_TRUE(context->subtree_can_inherit_opacity); + mock2->Preroll(context); + EXPECT_EQ(context->renderable_state_flags, + LayerStateStack::kCallerCanApplyOpacity); } TEST_F(MockLayerTest, FlagGetSet) { diff --git a/flow/testing/mock_raster_cache.cc b/flow/testing/mock_raster_cache.cc index 33b81c15030b3..b08b0f6d829f4 100644 --- a/flow/testing/mock_raster_cache.cc +++ b/flow/testing/mock_raster_cache.cc @@ -27,7 +27,7 @@ void MockRasterCache::AddMockLayer(int width, int height) { int layer_cached_threshold = 1; MockCacheableLayer layer = MockCacheableLayer(path, SkPaint(), layer_cached_threshold); - layer.Preroll(&preroll_context_, ctm); + layer.Preroll(&preroll_context_); layer.raster_cache_item()->TryToPrepareRasterCache(paint_context_); RasterCache::Context r_context = { // clang-format off @@ -58,8 +58,9 @@ void MockRasterCache::AddMockPicture(int width, int height) { FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; + LayerStateStack state_stack; PaintContextHolder holder = - GetSamplePaintContextHolder(this, &raster_time, &ui_time); + GetSamplePaintContextHolder(state_stack, this, &raster_time, &ui_time); holder.paint_context.dst_color_space = color_space_; DisplayListRasterCacheItem display_list_item(display_list.get(), SkPoint(), @@ -85,10 +86,10 @@ void MockRasterCache::AddMockPicture(int width, int height) { } PrerollContextHolder GetSamplePrerollContextHolder( + LayerStateStack& state_stack, RasterCache* raster_cache, FixedRefreshRateStopwatch* raster_time, - FixedRefreshRateStopwatch* ui_time, - MutatorsStack* mutators_stack) { + FixedRefreshRateStopwatch* ui_time) { sk_sp srgb = SkColorSpace::MakeSRGB(); PrerollContextHolder holder = { @@ -97,14 +98,12 @@ PrerollContextHolder GetSamplePrerollContextHolder( .raster_cache = raster_cache, .gr_context = nullptr, .view_embedder = nullptr, - .mutators_stack = *mutators_stack, + .state_stack = state_stack, .dst_color_space = srgb.get(), - .cull_rect = kGiantRect, .surface_needs_readback = false, .raster_time = *raster_time, .ui_time = *ui_time, .texture_registry = nullptr, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, .has_platform_view = false, .has_texture_layer = false, @@ -117,24 +116,23 @@ PrerollContextHolder GetSamplePrerollContextHolder( } PaintContextHolder GetSamplePaintContextHolder( + LayerStateStack& state_stack, RasterCache* raster_cache, FixedRefreshRateStopwatch* raster_time, FixedRefreshRateStopwatch* ui_time) { sk_sp srgb = SkColorSpace::MakeSRGB(); PaintContextHolder holder = {// clang-format off { - .internal_nodes_canvas = nullptr, - .leaf_nodes_canvas = nullptr, - .gr_context = nullptr, - .dst_color_space = srgb.get(), - .view_embedder = nullptr, - .raster_time = *raster_time, - .ui_time = *ui_time, - .texture_registry = nullptr, - .raster_cache = raster_cache, - .checkerboard_offscreen_layers = false, - .frame_device_pixel_ratio = 1.0f, - .inherited_opacity = SK_Scalar1, + .state_stack = state_stack, + .canvas = nullptr, + .gr_context = nullptr, + .dst_color_space = srgb.get(), + .view_embedder = nullptr, + .raster_time = *raster_time, + .ui_time = *ui_time, + .texture_registry = nullptr, + .raster_cache = raster_cache, + .frame_device_pixel_ratio = 1.0f, }, // clang-format on srgb}; diff --git a/flow/testing/mock_raster_cache.h b/flow/testing/mock_raster_cache.h index 37742486409bb..ea223a34b4b46 100644 --- a/flow/testing/mock_raster_cache.h +++ b/flow/testing/mock_raster_cache.h @@ -61,12 +61,15 @@ class MockRasterCache : public RasterCache { size_t picture_and_display_list_cache_limit_per_frame = RasterCacheUtil::kDefaultPictureAndDispLayListCacheLimitPerFrame) : RasterCache(access_threshold, - picture_and_display_list_cache_limit_per_frame) {} + picture_and_display_list_cache_limit_per_frame) { + state_stack_.set_delegate(&mutators_stack_); + } void AddMockLayer(int width, int height); void AddMockPicture(int width, int height); private: + LayerStateStack state_stack_; MockCanvas mock_canvas_; SkColorSpace* color_space_ = mock_canvas_.imageInfo().colorSpace(); MutatorsStack mutators_stack_; @@ -78,14 +81,12 @@ class MockRasterCache : public RasterCache { .raster_cache = this, .gr_context = nullptr, .view_embedder = nullptr, - .mutators_stack = mutators_stack_, + .state_stack = state_stack_, .dst_color_space = color_space_, - .cull_rect = kGiantRect, .surface_needs_readback = false, .raster_time = raster_time_, .ui_time = ui_time_, .texture_registry = texture_registry_, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, .has_platform_view = false, .has_texture_layer = false, @@ -95,18 +96,16 @@ class MockRasterCache : public RasterCache { PaintContext paint_context_ = { // clang-format off - .internal_nodes_canvas = nullptr, - .leaf_nodes_canvas = nullptr, - .gr_context = nullptr, - .dst_color_space = color_space_, - .view_embedder = nullptr, - .raster_time = raster_time_, - .ui_time = ui_time_, - .texture_registry = texture_registry_, - .raster_cache = nullptr, - .checkerboard_offscreen_layers = false, - .frame_device_pixel_ratio = 1.0f, - .inherited_opacity = SK_Scalar1, + .state_stack = state_stack_, + .canvas = nullptr, + .gr_context = nullptr, + .dst_color_space = color_space_, + .view_embedder = nullptr, + .raster_time = raster_time_, + .ui_time = ui_time_, + .texture_registry = texture_registry_, + .raster_cache = nullptr, + .frame_device_pixel_ratio = 1.0f, // clang-format on }; }; @@ -122,12 +121,13 @@ struct PaintContextHolder { }; PrerollContextHolder GetSamplePrerollContextHolder( + LayerStateStack& state_stack, RasterCache* raster_cache, FixedRefreshRateStopwatch* raster_time, - FixedRefreshRateStopwatch* ui_time, - MutatorsStack* mutators_stack); + FixedRefreshRateStopwatch* ui_time); PaintContextHolder GetSamplePaintContextHolder( + LayerStateStack& state_stack, RasterCache* raster_cache, FixedRefreshRateStopwatch* raster_time, FixedRefreshRateStopwatch* ui_time); diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 9589f0874aefa..4959450c931b4 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -2433,13 +2433,14 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { auto* compositor_context = shell->GetRasterizer()->compositor_context(); auto& raster_cache = compositor_context->raster_cache(); + LayerStateStack state_stack; FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; MutatorsStack mutators_stack; PaintContext paint_context = { // clang-format off - .internal_nodes_canvas = nullptr, - .leaf_nodes_canvas = nullptr, + .state_stack = state_stack, + .canvas = nullptr, .gr_context = nullptr, .dst_color_space = nullptr, .view_embedder = nullptr, @@ -2447,9 +2448,7 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { .ui_time = ui_time, .texture_registry = nullptr, .raster_cache = &raster_cache, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, - .inherited_opacity = SK_Scalar1, // clang-format on }; @@ -2458,14 +2457,12 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { .raster_cache = &raster_cache, .gr_context = nullptr, .view_embedder = nullptr, - .mutators_stack = mutators_stack, + .state_stack = state_stack, .dst_color_space = nullptr, - .cull_rect = kGiantRect, .surface_needs_readback = false, .raster_time = raster_time, .ui_time = ui_time, .texture_registry = nullptr, - .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, .has_platform_view = false, .has_texture_layer = false, @@ -2482,11 +2479,13 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { display_list.get(), SkPoint(), true, false); for (int i = 0; i < 4; i += 1) { SkMatrix matrix = SkMatrix::I(); + state_stack.set_delegate(&mutators_stack); display_list_raster_cache_item.PrerollSetup(&preroll_context, matrix); display_list_raster_cache_item.PrerollFinalize(&preroll_context, matrix); picture_cache_generated = display_list_raster_cache_item.need_caching(); + state_stack.set_delegate(&dummy_canvas); display_list_raster_cache_item.TryToPrepareRasterCache(paint_context); display_list_raster_cache_item.Draw(paint_context, &dummy_canvas, &paint); diff --git a/testing/canvas_test.h b/testing/canvas_test.h index e01b0cd018542..0d9eda9080c13 100644 --- a/testing/canvas_test.h +++ b/testing/canvas_test.h @@ -20,7 +20,6 @@ class CanvasTestBase : public BaseT { MockCanvas& mock_canvas() { return canvas_; } SkColorSpace* mock_color_space() { return canvas_.imageInfo().colorSpace(); } - SkNWayCanvas* mock_internal_canvas() { return canvas_.internal_canvas(); } private: MockCanvas canvas_; diff --git a/testing/mock_canvas.h b/testing/mock_canvas.h index c09acf5d9e0a3..345adbc0cde18 100644 --- a/testing/mock_canvas.h +++ b/testing/mock_canvas.h @@ -155,6 +155,7 @@ class MockCanvas : public SkCanvasVirtualEnforcer { SkNWayCanvas* internal_canvas() { return &internal_canvas_; } const std::vector& draw_calls() const { return draw_calls_; } + void reset_draw_calls() { draw_calls_.clear(); } protected: // Save/restore/set operations that we track.