DisplayList SaveLayer (and root layer) read-back flags (flutter/engine#53104)

The DisplayListBuilder now tracks the blend mode(s) used for its contents and whether it contains a child SaveLayer that uses a backdrop filter - both conditions that could require the graphics engine to use a different type of layer to satisfy the requests.

blend modes are tracked as the "highest" blend mode enum used by any content (defaults to kClear) as the enum values tend to be ordered so that larger values will tend to require more complicated render-target accesses.

The root layer of the DisplayList can be queried for both conditions on the root layer using methods on the DisplayList class.
This commit is contained in:
Jim Graham
2024-05-30 13:39:10 -07:00
committed by GitHub
parent ef7f67b710
commit 2d3cfa1e4c
12 changed files with 500 additions and 117 deletions

View File

@@ -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<const DlRTree> 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() {

View File

@@ -8,6 +8,7 @@
#include <memory>
#include <optional>
#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<const DlRTree> 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<const DlRTree> rtree_;

View File

@@ -643,8 +643,8 @@ TEST_F(DisplayListTest, SingleOpSizes) {
auto& invocation = group.variants[i];
sk_sp<DisplayList> 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<SaveLayerOptions> options;
std::optional<DlBlendMode> max_blend_mode;
};
explicit SaveLayerExpector(const Expectations& expected) {
expected_.push_back(expected);
}
explicit SaveLayerOptionsExpector(std::vector<SaveLayerOptions> expected)
explicit SaveLayerExpector(std::vector<Expectations> 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();
}
int save_layer_count() { return save_layer_count_; }
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;
}
}
bool all_expectations_checked() const {
return save_layer_count_ == expected_.size();
}
private:
std::vector<SaveLayerOptions> expected_;
int save_layer_count_ = 0;
std::vector<Expectations> 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<int>(DlBlendMode::kLastMode); i++) {
test(static_cast<DlBlendMode>(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

View File

@@ -77,6 +77,8 @@ sk_sp<DisplayList> 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<DlRTree> rtree;
SkRect bounds;
@@ -112,10 +114,10 @@ sk_sp<DisplayList> DisplayListBuilder::Build() {
}
storage_.realloc(bytes);
return sk_sp<DisplayList>(
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<DisplayList>(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(&current_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<SaveOpBase*>(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<SaveLayerOpBase*>(
storage_.get() + current_info.save_offset);
FML_DCHECK(layer_op->type == DisplayListOpType::kSaveLayer ||
layer_op->type == DisplayListOpType::kSaveLayerBackdrop);
FML_CHECK(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;
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<DrawColorOp>(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<DlImage> image,
? Push<DrawImageWithAttrOp>(0, image, point, sampling)
: Push<DrawImageOp>(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<DlImage> image,
Push<DrawImageRectOp>(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<DlImage> image,
? Push<DrawImageNineWithAttrOp>(0, image, center, dst, filter)
: Push<DrawImageNineOp>(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<DlImage> 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<DlImage>& atlas,
@@ -1625,7 +1630,11 @@ void DisplayListBuilder::DrawDisplayList(const sk_sp<DisplayList> 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<SkTextBlob> blob,
SkScalar x,
@@ -1718,7 +1727,7 @@ void DisplayListBuilder::DrawShadow(const SkPath& path,
dpr)
: Push<DrawShadowOp>(0, path, color, elevation, dpr);
UpdateLayerOpacityCompatibility(false);
UpdateLayerResult(result);
UpdateLayerResult(result, DlBlendMode::kSrcOver);
}
}
}

View File

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

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -271,7 +271,7 @@ std::vector<DisplayListInvocationGroup> 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<DisplayListInvocationGroup> 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<DisplayListInvocationGroup> 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<DisplayListInvocationGroup> 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<DisplayListInvocationGroup> 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<DisplayListInvocationGroup> 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<DisplayListInvocationGroup> 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<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
r.drawRect({10, 10, 20, 20});
r.restore();
}},
{5, 128, 3,
{5, 136, 3,
[](DlOpReceiver& r) {
r.saveLayer(&kTestBounds, SaveLayerOptions::kWithAttributes,
&kTestCFImageFilter1);

View File

@@ -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()

View File

@@ -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;

View File

@@ -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_ << " ";

View File

@@ -30,25 +30,50 @@ bool inline DisplayListsNE_Verbose(const sk_sp<const DisplayList>& a,
return DisplayListsNE_Verbose(a.get(), b.get());
}
} // namespace testing
} // namespace flutter
namespace std {
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);
const flutter::DisplayList& display_list);
extern std::ostream& operator<<(std::ostream& os,
const DlCanvas::PointMode& op);
const flutter::DlPaint& paint);
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::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 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: