diff --git a/display_list/display_list.cc b/display_list/display_list.cc index fa5446a3962b3..029e940419ed3 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -24,7 +24,9 @@ DisplayList::DisplayList() bounds_({0, 0, 0, 0}), can_apply_group_opacity_(true), is_ui_thread_safe_(true), - modifies_transparent_black_(false) {} + modifies_transparent_black_(false), + root_has_backdrop_filter_(false), + max_root_blend_mode_(DlBlendMode::kClear) {} DisplayList::DisplayList(DisplayListStorage&& storage, size_t byte_count, @@ -36,6 +38,8 @@ DisplayList::DisplayList(DisplayListStorage&& storage, bool can_apply_group_opacity, bool is_ui_thread_safe, bool modifies_transparent_black, + DlBlendMode max_root_blend_mode, + bool root_has_backdrop_filter, sk_sp rtree) : storage_(std::move(storage)), byte_count_(byte_count), @@ -48,6 +52,8 @@ DisplayList::DisplayList(DisplayListStorage&& storage, can_apply_group_opacity_(can_apply_group_opacity), is_ui_thread_safe_(is_ui_thread_safe), modifies_transparent_black_(modifies_transparent_black), + root_has_backdrop_filter_(root_has_backdrop_filter), + max_root_blend_mode_(max_root_blend_mode), rtree_(std::move(rtree)) {} DisplayList::~DisplayList() { diff --git a/display_list/display_list.h b/display_list/display_list.h index 4c0ec0a669968..e4cc58a32a37b 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -8,6 +8,7 @@ #include #include +#include "flutter/display_list/dl_blend_mode.h" #include "flutter/display_list/dl_sampling_options.h" #include "flutter/display_list/geometry/dl_rtree.h" #include "flutter/fml/logging.h" @@ -208,6 +209,13 @@ class SaveLayerOptions { return options; } + bool contains_backdrop_filter() const { return fHasBackdropFilter; } + SaveLayerOptions with_contains_backdrop_filter() const { + SaveLayerOptions options(this); + options.fHasBackdropFilter = true; + return options; + } + SaveLayerOptions& operator=(const SaveLayerOptions& other) { flags_ = other.flags_; return *this; @@ -226,6 +234,7 @@ class SaveLayerOptions { unsigned fCanDistributeOpacity : 1; unsigned fBoundsFromCaller : 1; unsigned fContentIsClipped : 1; + unsigned fHasBackdropFilter : 1; }; uint32_t flags_; }; @@ -313,6 +322,24 @@ class DisplayList : public SkRefCnt { const DisplayListStorage& GetStorage() const { return storage_; } + /// @brief Indicates if there are any saveLayer operations at the root + /// surface level of the DisplayList that use a backdrop filter. + /// + /// This condition can be used to determine what kind of surface to create + /// for the root layer into which to render the DisplayList as some GPUs + /// can support surfaces that do or do not support the readback that would + /// be required for the backdrop filter to do its work. + bool root_has_backdrop_filter() const { return root_has_backdrop_filter_; } + + /// @brief Indicates the maximum DlBlendMode used on any rendering op + /// in the root surface of the DisplayList. + /// + /// This condition can be used to determine what kind of surface to create + /// for the root layer into which to render the DisplayList as some GPUs + /// can support surfaces that do or do not support the readback that would + /// be required for the indicated blend mode to do its work. + DlBlendMode max_root_blend_mode() const { return max_root_blend_mode_; } + private: DisplayList(DisplayListStorage&& ptr, size_t byte_count, @@ -324,6 +351,8 @@ class DisplayList : public SkRefCnt { bool can_apply_group_opacity, bool is_ui_thread_safe, bool modifies_transparent_black, + DlBlendMode max_root_blend_mode, + bool root_has_backdrop_filter, sk_sp rtree); static uint32_t next_unique_id(); @@ -345,6 +374,8 @@ class DisplayList : public SkRefCnt { const bool can_apply_group_opacity_; const bool is_ui_thread_safe_; const bool modifies_transparent_black_; + const bool root_has_backdrop_filter_; + const DlBlendMode max_root_blend_mode_; const sk_sp rtree_; diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 941336badda7e..ef423aa7f29f8 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -643,8 +643,8 @@ TEST_F(DisplayListTest, SingleOpSizes) { auto& invocation = group.variants[i]; sk_sp dl = Build(invocation); auto desc = group.op_name + "(variant " + std::to_string(i + 1) + ")"; - ASSERT_EQ(dl->op_count(false), invocation.op_count()) << desc; - ASSERT_EQ(dl->bytes(false), invocation.byte_count()) << desc; + EXPECT_EQ(dl->op_count(false), invocation.op_count()) << desc; + EXPECT_EQ(dl->bytes(false), invocation.byte_count()) << desc; EXPECT_EQ(dl->total_depth(), invocation.depth_accumulated()) << desc; } } @@ -1314,38 +1314,64 @@ TEST_F(DisplayListTest, SaveLayerBoundsSnapshotsImageFilter) { EXPECT_EQ(bounds, SkRect::MakeLTRB(50, 50, 100, 100)); } -class SaveLayerOptionsExpector : public virtual DlOpReceiver, - public IgnoreAttributeDispatchHelper, - public IgnoreClipDispatchHelper, - public IgnoreTransformDispatchHelper, - public IgnoreDrawDispatchHelper { +class SaveLayerExpector : public virtual DlOpReceiver, + public IgnoreAttributeDispatchHelper, + public IgnoreClipDispatchHelper, + public IgnoreTransformDispatchHelper, + public IgnoreDrawDispatchHelper { public: - explicit SaveLayerOptionsExpector(const SaveLayerOptions& expected) { + struct Expectations { + // NOLINTNEXTLINE(google-explicit-constructor) + Expectations(SaveLayerOptions o) : options(o) {} + // NOLINTNEXTLINE(google-explicit-constructor) + Expectations(DlBlendMode mode) : max_blend_mode(mode) {} + + std::optional options; + std::optional max_blend_mode; + }; + + explicit SaveLayerExpector(const Expectations& expected) { expected_.push_back(expected); } - explicit SaveLayerOptionsExpector(std::vector expected) + explicit SaveLayerExpector(std::vector expected) : expected_(std::move(expected)) {} void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override { - EXPECT_EQ(options, expected_[save_layer_count_]) - << "index " << save_layer_count_; - save_layer_count_++; + FML_UNREACHABLE(); + } + + virtual void saveLayer(const SkRect& bounds, + const SaveLayerOptions& options, + uint32_t total_content_depth, + DlBlendMode max_content_blend_mode, + const DlImageFilter* backdrop = nullptr) { + auto label = "index " + std::to_string(save_layer_count_); + ASSERT_LT(save_layer_count_, expected_.size()); + auto expect = expected_[save_layer_count_++]; + if (expect.options.has_value()) { + EXPECT_EQ(options, expect.options.value()) << label; + } + if (expect.max_blend_mode.has_value()) { + EXPECT_EQ(max_content_blend_mode, expect.max_blend_mode.value()) << label; + } } - int save_layer_count() { return save_layer_count_; } + bool all_expectations_checked() const { + return save_layer_count_ == expected_.size(); + } private: - std::vector expected_; - int save_layer_count_ = 0; + std::vector expected_; + size_t save_layer_count_ = 0; }; TEST_F(DisplayListTest, SaveLayerOneSimpleOpInheritsOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes.with_can_distribute_opacity(); - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1355,13 +1381,13 @@ TEST_F(DisplayListTest, SaveLayerOneSimpleOpInheritsOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerNoAttributesInheritsOpacity) { SaveLayerOptions expected = SaveLayerOptions::kNoAttributes.with_can_distribute_opacity(); - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1370,12 +1396,12 @@ TEST_F(DisplayListTest, SaveLayerNoAttributesInheritsOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerTwoOverlappingOpsDoesNotInheritOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes; - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1386,7 +1412,7 @@ TEST_F(DisplayListTest, SaveLayerTwoOverlappingOpsDoesNotInheritOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, NestedSaveLayersMightInheritOpacity) { @@ -1395,7 +1421,7 @@ TEST_F(DisplayListTest, NestedSaveLayersMightInheritOpacity) { SaveLayerOptions expected2 = SaveLayerOptions::kWithAttributes; SaveLayerOptions expected3 = SaveLayerOptions::kWithAttributes.with_can_distribute_opacity(); - SaveLayerOptionsExpector expector({expected1, expected2, expected3}); + SaveLayerExpector expector({expected1, expected2, expected3}); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1410,7 +1436,7 @@ TEST_F(DisplayListTest, NestedSaveLayersMightInheritOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 3); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, NestedSaveLayersCanBothSupportOpacityOptimization) { @@ -1418,7 +1444,7 @@ TEST_F(DisplayListTest, NestedSaveLayersCanBothSupportOpacityOptimization) { SaveLayerOptions::kWithAttributes.with_can_distribute_opacity(); SaveLayerOptions expected2 = SaveLayerOptions::kNoAttributes.with_can_distribute_opacity(); - SaveLayerOptionsExpector expector({expected1, expected2}); + SaveLayerExpector expector({expected1, expected2}); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1430,12 +1456,12 @@ TEST_F(DisplayListTest, NestedSaveLayersCanBothSupportOpacityOptimization) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 2); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerImageFilterDoesNotInheritOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes; - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1447,12 +1473,12 @@ TEST_F(DisplayListTest, SaveLayerImageFilterDoesNotInheritOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerColorFilterDoesNotInheritOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes; - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1464,12 +1490,12 @@ TEST_F(DisplayListTest, SaveLayerColorFilterDoesNotInheritOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerSrcBlendDoesNotInheritOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes; - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1481,13 +1507,13 @@ TEST_F(DisplayListTest, SaveLayerSrcBlendDoesNotInheritOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerImageFilterOnChildInheritsOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes.with_can_distribute_opacity(); - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1498,12 +1524,12 @@ TEST_F(DisplayListTest, SaveLayerImageFilterOnChildInheritsOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerColorFilterOnChildDoesNotInheritOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes; - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1514,12 +1540,12 @@ TEST_F(DisplayListTest, SaveLayerColorFilterOnChildDoesNotInheritOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, SaveLayerSrcBlendOnChildDoesNotInheritOpacity) { SaveLayerOptions expected = SaveLayerOptions::kWithAttributes; - SaveLayerOptionsExpector expector(expected); + SaveLayerExpector expector(expected); DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1530,7 +1556,7 @@ TEST_F(DisplayListTest, SaveLayerSrcBlendOnChildDoesNotInheritOpacity) { receiver.restore(); builder.Build()->Dispatch(expector); - EXPECT_EQ(expector.save_layer_count(), 1); + EXPECT_TRUE(expector.all_expectations_checked()); } TEST_F(DisplayListTest, FlutterSvgIssue661BoundsWereEmpty) { @@ -1656,7 +1682,7 @@ TEST_F(DisplayListTest, FlutterSvgIssue661BoundsWereEmpty) { // This is the more practical result. The bounds are "almost" 0,0,100x100 EXPECT_EQ(display_list->bounds().roundOut(), SkIRect::MakeWH(100, 100)); EXPECT_EQ(display_list->op_count(), 19u); - EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 400u); + EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 408u); EXPECT_EQ(display_list->total_depth(), 3u); } @@ -3928,6 +3954,7 @@ class DepthExpector : public virtual DlOpReceiver, void saveLayer(const SkRect& bounds, const SaveLayerOptions& options, uint32_t total_content_depth, + DlBlendMode max_content_mode, const DlImageFilter* backdrop) override { ASSERT_LT(index_, depth_expectations_.size()); EXPECT_EQ(depth_expectations_[index_], total_content_depth) @@ -4072,5 +4099,237 @@ TEST_F(DisplayListTest, OpacityIncompatibleRenderOpInsideDeferredSave) { } } +TEST_F(DisplayListTest, MaxBlendModeEmptyDisplayList) { + DisplayListBuilder builder; + EXPECT_EQ(builder.Build()->max_root_blend_mode(), DlBlendMode::kClear); +} + +TEST_F(DisplayListTest, MaxBlendModeSimpleRect) { + auto test = [](DlBlendMode mode) { + DisplayListBuilder builder; + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setAlpha(0x7f).setBlendMode(mode)); + DlBlendMode expect = + (mode == DlBlendMode::kDst) ? DlBlendMode::kClear : mode; + EXPECT_EQ(builder.Build()->max_root_blend_mode(), expect) // + << "testing " << mode; + }; + + for (int i = 0; i < static_cast(DlBlendMode::kLastMode); i++) { + test(static_cast(i)); + } +} + +TEST_F(DisplayListTest, MaxBlendModeInsideNonDeferredSave) { + DisplayListBuilder builder; + builder.Save(); + { + // Trigger the deferred save + builder.Scale(2.0f, 2.0f); + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kModulate)); + } + // Save was triggered, did it forward the max blend mode? + builder.Restore(); + EXPECT_EQ(builder.Build()->max_root_blend_mode(), DlBlendMode::kModulate); +} + +TEST_F(DisplayListTest, MaxBlendModeInsideDeferredSave) { + DisplayListBuilder builder; + builder.Save(); + { + // Nothing to trigger the deferred save... + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kModulate)); + } + // Deferred save was not triggered, did it forward the max blend mode? + builder.Restore(); + EXPECT_EQ(builder.Build()->max_root_blend_mode(), DlBlendMode::kModulate); +} + +TEST_F(DisplayListTest, MaxBlendModeInsideSaveLayer) { + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kModulate)); + } + builder.Restore(); + auto dl = builder.Build(); + EXPECT_EQ(dl->max_root_blend_mode(), DlBlendMode::kSrcOver); + SaveLayerExpector expector(DlBlendMode::kModulate); + dl->Dispatch(expector); + EXPECT_TRUE(expector.all_expectations_checked()); +} + +TEST_F(DisplayListTest, MaxBlendModeInsideNonDefaultBlendedSaveLayer) { + DisplayListBuilder builder; + DlPaint save_paint; + save_paint.setBlendMode(DlBlendMode::kScreen); + builder.SaveLayer(nullptr, &save_paint); + { + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kModulate)); + } + builder.Restore(); + auto dl = builder.Build(); + EXPECT_EQ(dl->max_root_blend_mode(), DlBlendMode::kScreen); + SaveLayerExpector expector(DlBlendMode::kModulate); + dl->Dispatch(expector); + EXPECT_TRUE(expector.all_expectations_checked()); +} + +TEST_F(DisplayListTest, MaxBlendModeInsideComplexDeferredSaves) { + DisplayListBuilder builder; + builder.Save(); + { + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kModulate)); + builder.Save(); + { + // We want to use a blend mode that is greater than modulate here + ASSERT_GT(DlBlendMode::kScreen, DlBlendMode::kModulate); + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kScreen)); + } + builder.Restore(); + + // We want to use a blend mode that is smaller than modulate here + ASSERT_LT(DlBlendMode::kSrc, DlBlendMode::kModulate); + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kSrc)); + } + builder.Restore(); + + // Double check that kScreen is the max blend mode + auto expect = std::max(DlBlendMode::kModulate, DlBlendMode::kScreen); + expect = std::max(expect, DlBlendMode::kSrc); + ASSERT_EQ(expect, DlBlendMode::kScreen); + + EXPECT_EQ(builder.Build()->max_root_blend_mode(), DlBlendMode::kScreen); +} + +TEST_F(DisplayListTest, MaxBlendModeInsideComplexSaveLayers) { + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { + // outer save layer has Modulate now and Src later - Modulate is larger + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kModulate)); + builder.SaveLayer(nullptr, nullptr); + { + // inner save layer only has a Screen blend + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kScreen)); + } + builder.Restore(); + + // We want to use a blend mode that is smaller than modulate here + ASSERT_LT(DlBlendMode::kSrc, DlBlendMode::kModulate); + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kSrc)); + } + builder.Restore(); + + // Double check that kModulate is the max blend mode for the first + // saveLayer operations + auto expect = std::max(DlBlendMode::kModulate, DlBlendMode::kSrc); + ASSERT_EQ(expect, DlBlendMode::kModulate); + + auto dl = builder.Build(); + EXPECT_EQ(dl->max_root_blend_mode(), DlBlendMode::kSrcOver); + SaveLayerExpector expector({DlBlendMode::kModulate, DlBlendMode::kScreen}); + dl->Dispatch(expector); + EXPECT_TRUE(expector.all_expectations_checked()); +} + +TEST_F(DisplayListTest, BackdropDetectionEmptyDisplayList) { + DisplayListBuilder builder; + EXPECT_FALSE(builder.Build()->root_has_backdrop_filter()); +} + +TEST_F(DisplayListTest, BackdropDetectionSimpleRect) { + DisplayListBuilder builder; + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), DlPaint()); + EXPECT_FALSE(builder.Build()->root_has_backdrop_filter()); +} + +TEST_F(DisplayListTest, BackdropDetectionSimpleSaveLayer) { + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr, &kTestBlurImageFilter1); + { + // inner content has no backdrop filter + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), DlPaint()); + } + builder.Restore(); + auto dl = builder.Build(); + + EXPECT_TRUE(dl->root_has_backdrop_filter()); + // The saveLayer itself, though, does not have the contains backdrop + // flag set because its content does not contain a saveLayer with backdrop + SaveLayerExpector expector( + SaveLayerOptions::kNoAttributes.with_can_distribute_opacity()); + dl->Dispatch(expector); + EXPECT_TRUE(expector.all_expectations_checked()); +} + +TEST_F(DisplayListTest, BackdropDetectionNestedSaveLayer) { + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { + // first inner content does have backdrop filter + builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), DlPaint()); + builder.SaveLayer(nullptr, nullptr, &kTestBlurImageFilter1); + { + // second inner content has no backdrop filter + builder.DrawRect(SkRect::MakeLTRB(10, 10, 20, 20), DlPaint()); + } + builder.Restore(); + } + builder.Restore(); + auto dl = builder.Build(); + + EXPECT_FALSE(dl->root_has_backdrop_filter()); + SaveLayerExpector expector({ + SaveLayerOptions::kNoAttributes.with_contains_backdrop_filter(), + SaveLayerOptions::kNoAttributes.with_can_distribute_opacity(), + }); + dl->Dispatch(expector); + EXPECT_TRUE(expector.all_expectations_checked()); +} + +TEST_F(DisplayListTest, DrawDisplayListForwardsMaxBlend) { + DisplayListBuilder child_builder; + child_builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kMultiply)); + auto child_dl = child_builder.Build(); + EXPECT_EQ(child_dl->max_root_blend_mode(), DlBlendMode::kMultiply); + EXPECT_FALSE(child_dl->root_has_backdrop_filter()); + + DisplayListBuilder parent_builder; + parent_builder.DrawDisplayList(child_dl); + auto parent_dl = parent_builder.Build(); + EXPECT_EQ(parent_dl->max_root_blend_mode(), DlBlendMode::kMultiply); + EXPECT_FALSE(parent_dl->root_has_backdrop_filter()); +} + +TEST_F(DisplayListTest, DrawDisplayListForwardsBackdropFlag) { + DisplayListBuilder child_builder; + DlBlurImageFilter backdrop(2.0f, 2.0f, DlTileMode::kDecal); + child_builder.SaveLayer(nullptr, nullptr, &backdrop); + child_builder.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), + DlPaint().setBlendMode(DlBlendMode::kMultiply)); + child_builder.Restore(); + auto child_dl = child_builder.Build(); + EXPECT_EQ(child_dl->max_root_blend_mode(), DlBlendMode::kSrcOver); + EXPECT_TRUE(child_dl->root_has_backdrop_filter()); + + DisplayListBuilder parent_builder; + parent_builder.DrawDisplayList(child_dl); + auto parent_dl = parent_builder.Build(); + EXPECT_EQ(parent_dl->max_root_blend_mode(), DlBlendMode::kSrcOver); + EXPECT_TRUE(parent_dl->root_has_backdrop_filter()); +} + } // namespace testing } // namespace flutter diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index 7b4aa9777302a..944219c974cf1 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -77,6 +77,8 @@ sk_sp DisplayListBuilder::Build() { bool compatible = current_info().is_group_opacity_compatible(); bool is_safe = is_ui_thread_safe_; bool affects_transparency = current_info().affects_transparent_layer; + bool root_has_backdrop_filter = current_info().contains_backdrop_filter; + DlBlendMode max_root_blend_mode = current_info().max_blend_mode; sk_sp rtree; SkRect bounds; @@ -112,10 +114,10 @@ sk_sp DisplayListBuilder::Build() { } storage_.realloc(bytes); - return sk_sp( - new DisplayList(std::move(storage_), bytes, count, nested_bytes, - nested_count, total_depth, bounds, compatible, is_safe, - affects_transparency, std::move(rtree))); + return sk_sp(new DisplayList( + std::move(storage_), bytes, count, nested_bytes, nested_count, + total_depth, bounds, compatible, is_safe, affects_transparency, + max_root_blend_mode, root_has_backdrop_filter, std::move(rtree))); } static constexpr DlRect kEmpty = DlRect(); @@ -451,6 +453,10 @@ void DisplayListBuilder::saveLayer(const SkRect& bounds, return; } + if (backdrop != nullptr) { + current_info().contains_backdrop_filter = true; + } + // Snapshot these values before we do any work as we need the values // from before the method was called, but some of the operations below // might update them. @@ -468,8 +474,10 @@ void DisplayListBuilder::saveLayer(const SkRect& bounds, } filter = current_.getImageFilter(); CheckLayerOpacityCompatibility(true); + UpdateLayerResult(result, true); } else { CheckLayerOpacityCompatibility(false); + UpdateLayerResult(result, false); } // The actual flood of the outer layer clip will occur after the @@ -495,7 +503,7 @@ void DisplayListBuilder::saveLayer(const SkRect& bounds, rtree_data_.has_value() ? rtree_data_->rects.size() : 0u; save_stack_.emplace_back(¤t_info(), filter, rtree_index); - current_info().is_nop = false; + FML_DCHECK(!current_info().is_nop); FML_DCHECK(!current_info().has_deferred_save_op); current_info().save_offset = save_offset; current_info().save_depth = save_depth; @@ -569,8 +577,6 @@ void DisplayListBuilder::saveLayer(const SkRect& bounds, UpdateLayerOpacityCompatibility(false); } } - // REMIND: NEEDED? - UpdateLayerResult(result); } void DisplayListBuilder::SaveLayer(const SkRect* bounds, const DlPaint* paint, @@ -597,39 +603,45 @@ void DisplayListBuilder::Restore() { } { - // The current_info will have a lifetime that does not extend past the - // pop_back() method below. - auto& current_info = this->current_info(); + // The current_info and parent_info will have a lifetime that does not + // extend past the pop_back() method below. + const auto& current_info = this->current_info(); + auto& parent_info = this->parent_info(); if (!current_info.has_deferred_save_op) { SaveOpBase* op = reinterpret_cast(storage_.get() + current_info.save_offset); - FML_DCHECK(op->type == DisplayListOpType::kSave || - op->type == DisplayListOpType::kSaveLayer || - op->type == DisplayListOpType::kSaveLayerBackdrop); + FML_CHECK(op->type == DisplayListOpType::kSave || + op->type == DisplayListOpType::kSaveLayer || + op->type == DisplayListOpType::kSaveLayerBackdrop); op->restore_index = op_index_; op->total_content_depth = depth_ - current_info.save_depth; } if (current_info.is_save_layer) { - RestoreLayer(current_info, parent_info()); + RestoreLayer(current_info, parent_info); } else { // No need to propagate bounds as we do with layers... // global accumulator is either the same object or both nullptr FML_DCHECK(current_info.global_space_accumulator.get() == - parent_info().global_space_accumulator.get()); + parent_info.global_space_accumulator.get()); // layer accumulators are both the same object FML_DCHECK(current_info.layer_local_accumulator.get() == - parent_info().layer_local_accumulator.get()); + parent_info.layer_local_accumulator.get()); FML_DCHECK(current_info.layer_local_accumulator.get() != nullptr); // We only propagate these values through a regular save() if (current_info.opacity_incompatible_op_detected) { - parent_info().opacity_incompatible_op_detected = true; + parent_info.opacity_incompatible_op_detected = true; + } + + if (current_info.contains_backdrop_filter) { + parent_info.contains_backdrop_filter = true; } + parent_info.update_blend_mode(current_info.max_blend_mode); } // Wait until all outgoing bounds information for the saveLayer is @@ -659,30 +671,23 @@ void DisplayListBuilder::RestoreLayer(const SaveInfo& current_info, SaveLayerOpBase* layer_op = reinterpret_cast( storage_.get() + current_info.save_offset); - FML_DCHECK(layer_op->type == DisplayListOpType::kSaveLayer || - layer_op->type == DisplayListOpType::kSaveLayerBackdrop); - - switch (layer_op->type) { - case DisplayListOpType::kSaveLayer: - case DisplayListOpType::kSaveLayerBackdrop: { - if (layer_op->options.bounds_from_caller()) { - if (!content_bounds.isEmpty() && - !layer_op->rect.contains(content_bounds)) { - layer_op->options = layer_op->options.with_content_is_clipped(); - content_bounds.intersect(layer_op->rect); - } - } - layer_op->rect = content_bounds; - break; + FML_CHECK(layer_op->type == DisplayListOpType::kSaveLayer || + layer_op->type == DisplayListOpType::kSaveLayerBackdrop); + + if (layer_op->options.bounds_from_caller()) { + if (!content_bounds.isEmpty() && !layer_op->rect.contains(content_bounds)) { + layer_op->options = layer_op->options.with_content_is_clipped(); + content_bounds.intersect(layer_op->rect); } - default: - FML_UNREACHABLE(); + } + layer_op->rect = content_bounds; + layer_op->max_blend_mode = current_info.max_blend_mode; + + if (current_info.contains_backdrop_filter) { + layer_op->options = layer_op->options.with_contains_backdrop_filter(); } if (current_info.is_group_opacity_compatible()) { - // We are now going to go back and modify the matching saveLayer - // call to add the option indicating it can distribute an opacity - // value to its children. layer_op->options = layer_op->options.with_can_distribute_opacity(); } @@ -1113,7 +1118,7 @@ void DisplayListBuilder::DrawColor(DlColor color, DlBlendMode mode) { if (result != OpResult::kNoEffect && AccumulateUnbounded()) { Push(0, color, mode); CheckLayerOpacityCompatibility(mode); - UpdateLayerResult(result); + UpdateLayerResult(result, mode); } } void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) { @@ -1390,7 +1395,7 @@ void DisplayListBuilder::drawImage(const sk_sp image, ? Push(0, image, point, sampling) : Push(0, image, point, sampling); CheckLayerOpacityCompatibility(render_with_attributes); - UpdateLayerResult(result); + UpdateLayerResult(result, render_with_attributes); is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); } } @@ -1420,7 +1425,7 @@ void DisplayListBuilder::drawImageRect(const sk_sp image, Push(0, image, src, dst, sampling, render_with_attributes, constraint); CheckLayerOpacityCompatibility(render_with_attributes); - UpdateLayerResult(result); + UpdateLayerResult(result, render_with_attributes); is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); } } @@ -1452,7 +1457,7 @@ void DisplayListBuilder::drawImageNine(const sk_sp image, ? Push(0, image, center, dst, filter) : Push(0, image, center, dst, filter); CheckLayerOpacityCompatibility(render_with_attributes); - UpdateLayerResult(result); + UpdateLayerResult(result, render_with_attributes); is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); } } @@ -1540,7 +1545,7 @@ void DisplayListBuilder::drawAtlas(const sk_sp atlas, // on it to distribute the opacity without overlap without checking all // of the transforms and texture rectangles. UpdateLayerOpacityCompatibility(false); - UpdateLayerResult(result); + UpdateLayerResult(result, render_with_attributes); is_ui_thread_safe_ = is_ui_thread_safe_ && atlas->isUIThreadSafe(); } void DisplayListBuilder::DrawAtlas(const sk_sp& atlas, @@ -1625,7 +1630,11 @@ void DisplayListBuilder::DrawDisplayList(const sk_sp display_list, // pixels or we do not. We should not have [kNoEffect]. UpdateLayerResult(display_list->modifies_transparent_black() ? OpResult::kAffectsAll - : OpResult::kPreservesTransparency); + : OpResult::kPreservesTransparency, + display_list->max_root_blend_mode()); + if (display_list->root_has_backdrop_filter()) { + current_info().contains_backdrop_filter = true; + } } void DisplayListBuilder::drawTextBlob(const sk_sp blob, SkScalar x, @@ -1718,7 +1727,7 @@ void DisplayListBuilder::DrawShadow(const SkPath& path, dpr) : Push(0, path, color, elevation, dpr); UpdateLayerOpacityCompatibility(false); - UpdateLayerResult(result); + UpdateLayerResult(result, DlBlendMode::kSrcOver); } } } diff --git a/display_list/dl_builder.h b/display_list/dl_builder.h index eaf4ea146f328..9c6b54920664f 100644 --- a/display_list/dl_builder.h +++ b/display_list/dl_builder.h @@ -564,6 +564,12 @@ class DisplayListBuilder final : public virtual DlCanvas, // Simply transfers the local bounds to the parent void TransferBoundsToParent(const SaveInfo& parent); + void update_blend_mode(DlBlendMode mode) { + if (max_blend_mode < mode) { + max_blend_mode = mode; + } + } + const bool is_root_layer; const bool is_save_layer; @@ -572,6 +578,9 @@ class DisplayListBuilder final : public virtual DlCanvas, bool opacity_incompatible_op_detected = false; bool is_unbounded = false; bool affects_transparent_layer = false; + bool contains_backdrop_filter = false; + + DlBlendMode max_blend_mode = DlBlendMode::kClear; // The offset into the buffer where the associated save op is recorded // (which is not necessarily the same as when the Save() method is called) @@ -755,7 +764,7 @@ class DisplayListBuilder final : public virtual DlCanvas, OpResult PaintResult(const DlPaint& paint, DisplayListAttributeFlags flags = kDrawPaintFlags); - void UpdateLayerResult(OpResult result) { + void UpdateLayerResult(OpResult result, DlBlendMode mode) { switch (result) { case OpResult::kNoEffect: case OpResult::kPreservesTransparency: @@ -764,6 +773,11 @@ class DisplayListBuilder final : public virtual DlCanvas, current_info().affects_transparent_layer = true; break; } + current_info().update_blend_mode(mode); + } + void UpdateLayerResult(OpResult result, bool uses_attributes = true) { + UpdateLayerResult(result, uses_attributes ? current_.getBlendMode() + : DlBlendMode::kSrcOver); } // kAnyColor is a non-opaque and non-transparent color that will not diff --git a/display_list/dl_op_receiver.h b/display_list/dl_op_receiver.h index 68781fe26a2d2..5814c4cc6a52d 100644 --- a/display_list/dl_op_receiver.h +++ b/display_list/dl_op_receiver.h @@ -191,6 +191,7 @@ class DlOpReceiver { virtual void saveLayer(const SkRect& bounds, const SaveLayerOptions& options, uint32_t total_content_depth, + DlBlendMode max_content_blend_mode, const DlImageFilter* backdrop = nullptr) { saveLayer(bounds, options, backdrop); } diff --git a/display_list/dl_op_records.h b/display_list/dl_op_records.h index 3f8d70af1ce5d..2ce4d54a70f71 100644 --- a/display_list/dl_op_records.h +++ b/display_list/dl_op_records.h @@ -316,7 +316,7 @@ struct SetSharedImageFilterOp : DLOp { }; // The base struct for all save() and saveLayer() ops -// 4 byte header + 8 byte payload packs into 16 bytes (4 bytes unused) +// 4 byte header + 12 byte payload packs exactly into 16 bytes struct SaveOpBase : DLOp { static constexpr uint32_t kDepthInc = 0; static constexpr uint32_t kRenderOpInc = 1; @@ -327,8 +327,9 @@ struct SaveOpBase : DLOp { : options(options), restore_index(0), total_content_depth(0) {} // options parameter is only used by saveLayer operations, but since - // it packs neatly into the empty space created by laying out the 64-bit - // offsets, it can be stored for free and defaulted to 0 for save operations. + // it packs neatly into the empty space created by laying out the rest + // of the data here, it can be stored for free and defaulted to 0 for + // save operations. SaveLayerOptions options; int restore_index; uint32_t total_content_depth; @@ -353,14 +354,16 @@ struct SaveOp final : SaveOpBase { } }; // The base struct for all saveLayer() ops -// 16 byte SaveOpBase + 16 byte payload packs into 32 bytes (4 bytes unused) +// 16 byte SaveOpBase + 20 byte payload packs into 36 bytes struct SaveLayerOpBase : SaveOpBase { SaveLayerOpBase(const SaveLayerOptions& options, const SkRect& rect) : SaveOpBase(options), rect(rect) {} SkRect rect; + DlBlendMode max_blend_mode = DlBlendMode::kClear; }; -// 32 byte SaveLayerOpBase with no additional data +// 36 byte SaveLayerOpBase with no additional data packs into 40 bytes +// of buffer storage with 4 bytes unused. struct SaveLayerOp final : SaveLayerOpBase { static constexpr auto kType = DisplayListOpType::kSaveLayer; @@ -369,11 +372,13 @@ struct SaveLayerOp final : SaveLayerOpBase { void dispatch(DispatchContext& ctx) const { if (save_needed(ctx)) { - ctx.receiver.saveLayer(rect, options, total_content_depth); + ctx.receiver.saveLayer(rect, options, total_content_depth, + max_blend_mode); } } }; -// 32 byte SaveLayerOpBase + 16 byte payload packs into minimum 48 bytes +// 36 byte SaveLayerOpBase + 4 bytes for alignment + 16 byte payload packs +// into minimum 56 bytes struct SaveLayerBackdropOp final : SaveLayerOpBase { static constexpr auto kType = DisplayListOpType::kSaveLayerBackdrop; @@ -386,7 +391,7 @@ struct SaveLayerBackdropOp final : SaveLayerOpBase { void dispatch(DispatchContext& ctx) const { if (save_needed(ctx)) { - ctx.receiver.saveLayer(rect, options, total_content_depth, + ctx.receiver.saveLayer(rect, options, total_content_depth, max_blend_mode, backdrop.get()); } } diff --git a/display_list/testing/dl_test_snippets.cc b/display_list/testing/dl_test_snippets.cc index ffdbfee2043f2..56b36804f6a0b 100644 --- a/display_list/testing/dl_test_snippets.cc +++ b/display_list/testing/dl_test_snippets.cc @@ -271,7 +271,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 112, 3, + {5, 120, 3, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kNoAttributes); r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true); @@ -279,7 +279,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 112, 3, + {5, 120, 3, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kWithAttributes); r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true); @@ -287,7 +287,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 112, 3, + {5, 120, 3, [](DlOpReceiver& r) { r.saveLayer(&kTestBounds, SaveLayerOptions::kNoAttributes); r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true); @@ -295,7 +295,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 112, 3, + {5, 120, 3, [](DlOpReceiver& r) { r.saveLayer(&kTestBounds, SaveLayerOptions::kWithAttributes); r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true); @@ -303,7 +303,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 128, 3, + {5, 136, 3, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kNoAttributes, &kTestCFImageFilter1); @@ -312,7 +312,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 128, 3, + {5, 136, 3, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kWithAttributes, &kTestCFImageFilter1); @@ -321,7 +321,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 128, 3, + {5, 136, 3, [](DlOpReceiver& r) { r.saveLayer(&kTestBounds, SaveLayerOptions::kNoAttributes, &kTestCFImageFilter1); @@ -330,7 +330,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 128, 3, + {5, 136, 3, [](DlOpReceiver& r) { r.saveLayer(&kTestBounds, SaveLayerOptions::kWithAttributes, &kTestCFImageFilter1); diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 12abfe87c010c..fa8782426c4ff 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -619,6 +619,7 @@ void DlDispatcherBase::save(uint32_t total_content_depth) { void DlDispatcherBase::saveLayer(const SkRect& bounds, const flutter::SaveLayerOptions& options, uint32_t total_content_depth, + flutter::DlBlendMode max_content_mode, const flutter::DlImageFilter* backdrop) { auto paint = options.renders_with_attributes() ? paint_ : Paint{}; auto promise = options.content_is_clipped() diff --git a/impeller/display_list/dl_dispatcher.h b/impeller/display_list/dl_dispatcher.h index 2873dba6595b8..ef694b2d2167e 100644 --- a/impeller/display_list/dl_dispatcher.h +++ b/impeller/display_list/dl_dispatcher.h @@ -71,6 +71,7 @@ class DlDispatcherBase : public flutter::DlOpReceiver { void saveLayer(const SkRect& bounds, const flutter::SaveLayerOptions& options, uint32_t total_content_depth, + flutter::DlBlendMode max_content_mode, const flutter::DlImageFilter* backdrop) override; // |flutter::DlOpReceiver| @@ -275,7 +276,8 @@ class DlDispatcher : public DlDispatcherBase { // This dispatcher is used from test cases that might not supply // a content_depth parameter. Since this dispatcher doesn't use // the value, we just pass through a 0. - DlDispatcherBase::saveLayer(bounds, options, 0u, backdrop); + DlDispatcherBase::saveLayer(bounds, options, 0u, + flutter::DlBlendMode::kLastMode, backdrop); } using DlDispatcherBase::saveLayer; diff --git a/testing/display_list_testing.cc b/testing/display_list_testing.cc index 03a37553ac84f..170504373425f 100644 --- a/testing/display_list_testing.cc +++ b/testing/display_list_testing.cc @@ -35,6 +35,29 @@ bool DisplayListsNE_Verbose(const DisplayList* a, const DisplayList* b) { return true; } +} // namespace testing +} // namespace flutter + +namespace std { + +using DisplayList = flutter::DisplayList; +using DlColor = flutter::DlColor; +using DlPaint = flutter::DlPaint; +using DlCanvas = flutter::DlCanvas; +using DlImage = flutter::DlImage; +using DlDrawStyle = flutter::DlDrawStyle; +using DlBlendMode = flutter::DlBlendMode; +using DlStrokeCap = flutter::DlStrokeCap; +using DlStrokeJoin = flutter::DlStrokeJoin; +using DlBlurStyle = flutter::DlBlurStyle; +using DlFilterMode = flutter::DlFilterMode; +using DlVertexMode = flutter::DlVertexMode; +using DlTileMode = flutter::DlTileMode; +using DlImageSampling = flutter::DlImageSampling; +using SaveLayerOptions = flutter::SaveLayerOptions; + +using DisplayListStreamDispatcher = flutter::testing::DisplayListStreamDispatcher; + std::ostream& operator<<(std::ostream& os, const DisplayList& display_list) { DisplayListStreamDispatcher dispatcher(os); @@ -116,9 +139,11 @@ std::ostream& operator<<(std::ostream& os, const DlBlendMode& mode) { std::ostream& operator<<(std::ostream& os, const SaveLayerOptions& options) { return os << "SaveLayerOptions(" + << "renders_with_attributes: " << options.renders_with_attributes() + << ", " << "can_distribute_opacity: " << options.can_distribute_opacity() << ", " - << "renders_with_attributes: " << options.renders_with_attributes() + << "contains_backdrop: " << options.contains_backdrop_filter() << ")"; } @@ -330,6 +355,11 @@ std::ostream& operator<<(std::ostream& os, const DlImage* image) { return os << "isTextureBacked: " << image->isTextureBacked() << ")"; } +} // namespace std + +namespace flutter { +namespace testing { + std::ostream& DisplayListStreamDispatcher::startl() { for (int i = 0; i < cur_indent_; i++) { os_ << " "; diff --git a/testing/display_list_testing.h b/testing/display_list_testing.h index 74bc2826228a8..369864176ca90 100644 --- a/testing/display_list_testing.h +++ b/testing/display_list_testing.h @@ -30,25 +30,50 @@ bool inline DisplayListsNE_Verbose(const sk_sp& a, return DisplayListsNE_Verbose(a.get(), b.get()); } +} // namespace testing +} // namespace flutter + +namespace std { + +extern std::ostream& operator<<(std::ostream& os, + const flutter::DisplayList& display_list); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlPaint& paint); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlBlendMode& mode); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlCanvas::ClipOp& op); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlCanvas::PointMode& op); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlCanvas::SrcRectConstraint& op); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlStrokeCap& cap); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlStrokeJoin& join); extern std::ostream& operator<<(std::ostream& os, - const DisplayList& display_list); -extern std::ostream& operator<<(std::ostream& os, const DlPaint& paint); -extern std::ostream& operator<<(std::ostream& os, const DlBlendMode& mode); -extern std::ostream& operator<<(std::ostream& os, const DlCanvas::ClipOp& op); -extern std::ostream& operator<<(std::ostream& os, - const DlCanvas::PointMode& op); -extern std::ostream& operator<<(std::ostream& os, - const DlCanvas::SrcRectConstraint& op); -extern std::ostream& operator<<(std::ostream& os, const DlStrokeCap& cap); -extern std::ostream& operator<<(std::ostream& os, const DlStrokeJoin& join); -extern std::ostream& operator<<(std::ostream& os, const DlDrawStyle& style); -extern std::ostream& operator<<(std::ostream& os, const DlBlurStyle& style); -extern std::ostream& operator<<(std::ostream& os, const DlFilterMode& mode); -extern std::ostream& operator<<(std::ostream& os, const DlColor& color); -extern std::ostream& operator<<(std::ostream& os, DlImageSampling sampling); -extern std::ostream& operator<<(std::ostream& os, const DlVertexMode& mode); -extern std::ostream& operator<<(std::ostream& os, const DlTileMode& mode); -extern std::ostream& operator<<(std::ostream& os, const DlImage* image); + const flutter::DlDrawStyle& style); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlBlurStyle& style); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlFilterMode& mode); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlColor& color); +extern std::ostream& operator<<(std::ostream& os, + flutter::DlImageSampling sampling); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlVertexMode& mode); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlTileMode& mode); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DlImage* image); +extern std::ostream& operator<<(std::ostream& os, + const flutter::SaveLayerOptions& image); + +} // namespace std + +namespace flutter { +namespace testing { class DisplayListStreamDispatcher final : public DlOpReceiver { public: