[DisplayList] track unbounded state on save layers and DisplayLists (flutter/engine#54032)

New flags on SaveLayerOptions will report if a saveLayer result is unbounded because a rendering operation within its contents did not have a definable bounds and there was no clip installed at the time (consider DrawPaint for example). A similar flag is found on DisplayList objects which reports if their top level had an unbounded operation.
This commit is contained in:
Jim Graham
2024-07-23 11:15:18 -07:00
committed by GitHub
parent 94a7e50936
commit 3aafd537ae
7 changed files with 602 additions and 61 deletions

View File

@@ -130,6 +130,7 @@ if (enable_unittests) {
":display_list",
":display_list_fixtures",
"//flutter/display_list/testing:display_list_testing",
"//flutter/impeller/typographer/backends/skia:typographer_skia_backend",
"//flutter/testing",
"//flutter/testing:skia",
]

View File

@@ -26,6 +26,7 @@ DisplayList::DisplayList()
is_ui_thread_safe_(true),
modifies_transparent_black_(false),
root_has_backdrop_filter_(false),
root_is_unbounded_(false),
max_root_blend_mode_(DlBlendMode::kClear) {}
DisplayList::DisplayList(DisplayListStorage&& storage,
@@ -40,6 +41,7 @@ DisplayList::DisplayList(DisplayListStorage&& storage,
bool modifies_transparent_black,
DlBlendMode max_root_blend_mode,
bool root_has_backdrop_filter,
bool root_is_unbounded,
sk_sp<const DlRTree> rtree)
: storage_(std::move(storage)),
byte_count_(byte_count),
@@ -53,6 +55,7 @@ DisplayList::DisplayList(DisplayListStorage&& storage,
is_ui_thread_safe_(is_ui_thread_safe),
modifies_transparent_black_(modifies_transparent_black),
root_has_backdrop_filter_(root_has_backdrop_filter),
root_is_unbounded_(root_is_unbounded),
max_root_blend_mode_(max_root_blend_mode),
rtree_(std::move(rtree)) {}

View File

@@ -216,6 +216,13 @@ class SaveLayerOptions {
return options;
}
bool content_is_unbounded() const { return fContentIsUnbounded; }
SaveLayerOptions with_content_is_unbounded() const {
SaveLayerOptions options(this);
options.fContentIsUnbounded = true;
return options;
}
SaveLayerOptions& operator=(const SaveLayerOptions& other) {
flags_ = other.flags_;
return *this;
@@ -235,6 +242,7 @@ class SaveLayerOptions {
unsigned fBoundsFromCaller : 1;
unsigned fContentIsClipped : 1;
unsigned fHasBackdropFilter : 1;
unsigned fContentIsUnbounded : 1;
};
uint32_t flags_;
};
@@ -331,6 +339,18 @@ class DisplayList : public SkRefCnt {
/// be required for the backdrop filter to do its work.
bool root_has_backdrop_filter() const { return root_has_backdrop_filter_; }
/// @brief Indicates if a rendering operation at the root level of the
/// DisplayList had an unbounded result, not otherwise limited by
/// a clip operation.
///
/// This condition can occur in a number of situations. The most common
/// situation is when there is a drawPaint or drawColor rendering
/// operation which fills out the entire drawable surface unless it is
/// bounded by a clip. Other situations include an operation rendered
/// through an ImageFilter that cannot compute the resulting bounds or
/// when an unclipped backdrop filter is applied by a save layer.
bool root_is_unbounded() const { return root_is_unbounded_; }
/// @brief Indicates the maximum DlBlendMode used on any rendering op
/// in the root surface of the DisplayList.
///
@@ -353,6 +373,7 @@ class DisplayList : public SkRefCnt {
bool modifies_transparent_black,
DlBlendMode max_root_blend_mode,
bool root_has_backdrop_filter,
bool root_is_unbounded,
sk_sp<const DlRTree> rtree);
static uint32_t next_unique_id();
@@ -375,6 +396,7 @@ class DisplayList : public SkRefCnt {
const bool is_ui_thread_safe_;
const bool modifies_transparent_black_;
const bool root_has_backdrop_filter_;
const bool root_is_unbounded_;
const DlBlendMode max_root_blend_mode_;
const sk_sp<const DlRTree> rtree_;

View File

@@ -18,6 +18,7 @@
#include "flutter/display_list/utils/dl_receiver_utils.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/math.h"
#include "flutter/impeller/typographer/backends/skia/text_frame_skia.h"
#include "flutter/testing/assertions_skia.h"
#include "flutter/testing/display_list_testing.h"
#include "flutter/testing/testing.h"
@@ -1309,28 +1310,80 @@ TEST_F(DisplayListTest, SaveLayerBoundsSnapshotsImageFilter) {
EXPECT_EQ(bounds, SkRect::MakeLTRB(50, 50, 100, 100));
}
#define SAVE_LAYER_EXPECTOR(name) SaveLayerExpector name(__FILE__, __LINE__)
using SaveLayerOptionsTester =
std::function<bool(const SaveLayerOptions& options)>;
struct SaveLayerExpectations {
SaveLayerExpectations() {}
// NOLINTNEXTLINE(google-explicit-constructor)
SaveLayerExpectations(const SaveLayerOptions& o) : options(o) {}
// NOLINTNEXTLINE(google-explicit-constructor)
SaveLayerExpectations(const SaveLayerOptionsTester& t) : tester(t) {}
// NOLINTNEXTLINE(google-explicit-constructor)
SaveLayerExpectations(DlBlendMode mode) : max_blend_mode(mode) {}
std::optional<SaveLayerOptions> options;
std::optional<SaveLayerOptionsTester> tester;
std::optional<DlBlendMode> max_blend_mode;
};
::std::ostream& operator<<(::std::ostream& os,
const SaveLayerExpectations& expect) {
os << "SaveLayerExpectation(";
if (expect.options.has_value()) {
os << "options: " << expect.options.value();
}
if (expect.tester.has_value()) {
os << "option tester: " << &expect.tester.value();
}
if (expect.max_blend_mode.has_value()) {
os << "max_blend: " << expect.max_blend_mode.value();
}
os << ")";
return os;
}
class SaveLayerExpector : public virtual DlOpReceiver,
public IgnoreAttributeDispatchHelper,
public IgnoreClipDispatchHelper,
public IgnoreTransformDispatchHelper,
public IgnoreDrawDispatchHelper {
public:
struct Expectations {
// NOLINTNEXTLINE(google-explicit-constructor)
Expectations(SaveLayerOptions o) : options(o) {}
// NOLINTNEXTLINE(google-explicit-constructor)
Expectations(DlBlendMode mode) : max_blend_mode(mode) {}
SaveLayerExpector(const std::string& file, int line)
: file_(file), line_(line), detail_("") {}
std::optional<SaveLayerOptions> options;
std::optional<DlBlendMode> max_blend_mode;
};
explicit SaveLayerExpector(const Expectations& expected) {
expected_.push_back(expected);
~SaveLayerExpector() { //
EXPECT_EQ(save_layer_count_, expected_.size()) << label();
while (save_layer_count_ < expected_.size()) {
auto expect = expected_[save_layer_count_];
FML_LOG(ERROR) << "leftover expectation[" << save_layer_count_
<< "] = " << expect;
save_layer_count_++;
}
}
explicit SaveLayerExpector(std::vector<Expectations> expected)
: expected_(std::move(expected)) {}
SaveLayerExpector& addDetail(const std::string& detail) {
detail_ = detail;
return *this;
}
SaveLayerExpector& addExpectation(const SaveLayerExpectations& expected) {
expected_.push_back(expected);
return *this;
}
SaveLayerExpector& addExpectation(const SaveLayerOptionsTester& tester) {
expected_.push_back(SaveLayerExpectations(tester));
return *this;
}
SaveLayerExpector& addOpenExpectation() {
expected_.push_back(SaveLayerExpectations());
return *this;
}
void saveLayer(const SkRect& bounds,
const SaveLayerOptions options,
@@ -1343,15 +1396,19 @@ class SaveLayerExpector : public virtual DlOpReceiver,
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_++];
ASSERT_LT(save_layer_count_, expected_.size()) << label();
auto expect = expected_[save_layer_count_];
if (expect.options.has_value()) {
EXPECT_EQ(options, expect.options.value()) << label;
EXPECT_EQ(options, expect.options.value()) << label();
}
if (expect.tester.has_value()) {
EXPECT_TRUE(expect.tester.value()(options)) << label();
}
if (expect.max_blend_mode.has_value()) {
EXPECT_EQ(max_content_blend_mode, expect.max_blend_mode.value()) << label;
EXPECT_EQ(max_content_blend_mode, expect.max_blend_mode.value())
<< label();
}
save_layer_count_++;
}
bool all_expectations_checked() const {
@@ -1359,14 +1416,29 @@ class SaveLayerExpector : public virtual DlOpReceiver,
}
private:
std::vector<Expectations> expected_;
// mutable allows the copy constructor to leave no expectations behind
mutable std::vector<SaveLayerExpectations> expected_;
size_t save_layer_count_ = 0;
const std::string file_;
const int line_;
std::string detail_;
std::string label() {
std::string label = "at index " + std::to_string(save_layer_count_) + //
", from " + file_ + //
":" + std::to_string(line_);
if (detail_.length() > 0) {
label = label + " (" + detail_ + ")";
}
return label;
}
};
TEST_F(DisplayListTest, SaveLayerOneSimpleOpInheritsOpacity) {
SaveLayerOptions expected =
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity());
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1380,9 +1452,9 @@ TEST_F(DisplayListTest, SaveLayerOneSimpleOpInheritsOpacity) {
}
TEST_F(DisplayListTest, SaveLayerNoAttributesInheritsOpacity) {
SaveLayerOptions expected =
SaveLayerOptions::kNoAttributes.with_can_distribute_opacity();
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(
SaveLayerOptions::kNoAttributes.with_can_distribute_opacity());
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1395,8 +1467,8 @@ TEST_F(DisplayListTest, SaveLayerNoAttributesInheritsOpacity) {
}
TEST_F(DisplayListTest, SaveLayerTwoOverlappingOpsDoesNotInheritOpacity) {
SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(SaveLayerOptions::kWithAttributes);
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1411,12 +1483,13 @@ TEST_F(DisplayListTest, SaveLayerTwoOverlappingOpsDoesNotInheritOpacity) {
}
TEST_F(DisplayListTest, NestedSaveLayersMightInheritOpacity) {
SaveLayerOptions expected1 =
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
SaveLayerOptions expected2 = SaveLayerOptions::kWithAttributes;
SaveLayerOptions expected3 =
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
SaveLayerExpector expector({expected1, expected2, expected3});
SAVE_LAYER_EXPECTOR(expector);
expector //
.addExpectation(
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity())
.addExpectation(SaveLayerOptions::kWithAttributes)
.addExpectation(
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity());
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1435,11 +1508,12 @@ TEST_F(DisplayListTest, NestedSaveLayersMightInheritOpacity) {
}
TEST_F(DisplayListTest, NestedSaveLayersCanBothSupportOpacityOptimization) {
SaveLayerOptions expected1 =
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
SaveLayerOptions expected2 =
SaveLayerOptions::kNoAttributes.with_can_distribute_opacity();
SaveLayerExpector expector({expected1, expected2});
SAVE_LAYER_EXPECTOR(expector);
expector //
.addExpectation(
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity())
.addExpectation(
SaveLayerOptions::kNoAttributes.with_can_distribute_opacity());
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1455,8 +1529,8 @@ TEST_F(DisplayListTest, NestedSaveLayersCanBothSupportOpacityOptimization) {
}
TEST_F(DisplayListTest, SaveLayerImageFilterDoesNotInheritOpacity) {
SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(SaveLayerOptions::kWithAttributes);
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1472,8 +1546,8 @@ TEST_F(DisplayListTest, SaveLayerImageFilterDoesNotInheritOpacity) {
}
TEST_F(DisplayListTest, SaveLayerColorFilterDoesNotInheritOpacity) {
SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(SaveLayerOptions::kWithAttributes);
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1489,8 +1563,8 @@ TEST_F(DisplayListTest, SaveLayerColorFilterDoesNotInheritOpacity) {
}
TEST_F(DisplayListTest, SaveLayerSrcBlendDoesNotInheritOpacity) {
SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(SaveLayerOptions::kWithAttributes);
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1506,9 +1580,9 @@ TEST_F(DisplayListTest, SaveLayerSrcBlendDoesNotInheritOpacity) {
}
TEST_F(DisplayListTest, SaveLayerImageFilterOnChildInheritsOpacity) {
SaveLayerOptions expected =
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(
SaveLayerOptions::kWithAttributes.with_can_distribute_opacity());
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1523,8 +1597,8 @@ TEST_F(DisplayListTest, SaveLayerImageFilterOnChildInheritsOpacity) {
}
TEST_F(DisplayListTest, SaveLayerColorFilterOnChildDoesNotInheritOpacity) {
SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(SaveLayerOptions::kWithAttributes);
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -1539,8 +1613,8 @@ TEST_F(DisplayListTest, SaveLayerColorFilterOnChildDoesNotInheritOpacity) {
}
TEST_F(DisplayListTest, SaveLayerSrcBlendOnChildDoesNotInheritOpacity) {
SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
SaveLayerExpector expector(expected);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(SaveLayerOptions::kWithAttributes);
DisplayListBuilder builder;
DlOpReceiver& receiver = ToReceiver(builder);
@@ -4156,7 +4230,8 @@ TEST_F(DisplayListTest, MaxBlendModeInsideSaveLayer) {
builder.Restore();
auto dl = builder.Build();
EXPECT_EQ(dl->max_root_blend_mode(), DlBlendMode::kSrcOver);
SaveLayerExpector expector(DlBlendMode::kModulate);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(DlBlendMode::kModulate);
dl->Dispatch(expector);
EXPECT_TRUE(expector.all_expectations_checked());
}
@@ -4173,7 +4248,8 @@ TEST_F(DisplayListTest, MaxBlendModeInsideNonDefaultBlendedSaveLayer) {
builder.Restore();
auto dl = builder.Build();
EXPECT_EQ(dl->max_root_blend_mode(), DlBlendMode::kScreen);
SaveLayerExpector expector(DlBlendMode::kModulate);
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(DlBlendMode::kModulate);
dl->Dispatch(expector);
EXPECT_TRUE(expector.all_expectations_checked());
}
@@ -4237,7 +4313,10 @@ TEST_F(DisplayListTest, MaxBlendModeInsideComplexSaveLayers) {
auto dl = builder.Build();
EXPECT_EQ(dl->max_root_blend_mode(), DlBlendMode::kSrcOver);
SaveLayerExpector expector({DlBlendMode::kModulate, DlBlendMode::kScreen});
SAVE_LAYER_EXPECTOR(expector);
expector //
.addExpectation(DlBlendMode::kModulate)
.addExpectation(DlBlendMode::kScreen);
dl->Dispatch(expector);
EXPECT_TRUE(expector.all_expectations_checked());
}
@@ -4266,7 +4345,8 @@ TEST_F(DisplayListTest, BackdropDetectionSimpleSaveLayer) {
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(
SAVE_LAYER_EXPECTOR(expector);
expector.addExpectation(
SaveLayerOptions::kNoAttributes.with_can_distribute_opacity());
dl->Dispatch(expector);
EXPECT_TRUE(expector.all_expectations_checked());
@@ -4289,10 +4369,13 @@ TEST_F(DisplayListTest, BackdropDetectionNestedSaveLayer) {
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(),
});
SAVE_LAYER_EXPECTOR(expector);
expector //
.addExpectation(SaveLayerOptions::kNoAttributes //
.with_contains_backdrop_filter()
.with_content_is_unbounded())
.addExpectation(
SaveLayerOptions::kNoAttributes.with_can_distribute_opacity());
dl->Dispatch(expector);
EXPECT_TRUE(expector.all_expectations_checked());
}
@@ -5179,5 +5262,423 @@ TEST_F(DisplayListTest, ClipOvalRRectPathPromoteToClipOval) {
DisplayListsEQ_Verbose(dl, expect_dl);
}
TEST_F(DisplayListTest, BoundedRenderOpsDoNotReportUnbounded) {
static const SkRect root_cull = SkRect::MakeLTRB(100, 100, 200, 200);
static const SkRect draw_rect = SkRect::MakeLTRB(110, 110, 190, 190);
using Renderer = const std::function<void(DlCanvas&)>;
auto test_bounded = [](const std::string& label, const Renderer& renderer) {
{
DisplayListBuilder builder(root_cull);
renderer(builder);
auto display_list = builder.Build();
EXPECT_EQ(display_list->bounds(), draw_rect) << label;
EXPECT_FALSE(display_list->root_is_unbounded()) << label;
}
{
DisplayListBuilder builder(root_cull);
builder.SaveLayer(nullptr, nullptr);
renderer(builder);
builder.Restore();
auto display_list = builder.Build();
EXPECT_EQ(display_list->bounds(), draw_rect) << label;
EXPECT_FALSE(display_list->root_is_unbounded()) << label;
SAVE_LAYER_EXPECTOR(expector);
expector //
.addDetail(label)
.addExpectation([](const SaveLayerOptions& options) {
return !options.content_is_unbounded();
});
display_list->Dispatch(expector);
}
};
test_bounded("DrawLine", [](DlCanvas& builder) {
builder.DrawLine(
{draw_rect.left() + 1.0f, draw_rect.top() + 1.0f},
{draw_rect.right() - 1.0f, draw_rect.top() + 1.0f},
DlPaint().setStrokeWidth(2.0f).setStrokeCap(DlStrokeCap::kSquare));
builder.DrawLine(
{draw_rect.left() + 1.0f, draw_rect.bottom() - 1.0f},
{draw_rect.right() - 1.0f, draw_rect.bottom() - 1.0f},
DlPaint().setStrokeWidth(2.0f).setStrokeCap(DlStrokeCap::kSquare));
});
test_bounded("DrawDashedLine", [](DlCanvas& builder) {
builder.DrawDashedLine(
{draw_rect.left() + 1.0f, draw_rect.top() + 1.0f},
{draw_rect.right() - 1.0f, draw_rect.top() + 1.0f},
// must fill 80 x 80 square with on dashes at both
// ends - 40 + 25 + 40 == 105 so it will be on
// at both ends
40.0f, 25.0f,
DlPaint().setStrokeWidth(2.0f).setStrokeCap(DlStrokeCap::kSquare));
builder.DrawDashedLine(
{draw_rect.left() + 1.0f, draw_rect.bottom() - 1.0f},
{draw_rect.right() - 1.0f, draw_rect.bottom() - 1.0f},
// must fill 80 x 80 square with on dashes at both
// ends - 40 + 25 + 40 == 105 so it will be on
// at both ends
40.0f, 25.0f,
DlPaint().setStrokeWidth(2.0f).setStrokeCap(DlStrokeCap::kSquare));
});
test_bounded("DrawRect", [](DlCanvas& builder) {
builder.DrawRect(draw_rect, DlPaint());
});
test_bounded("DrawOval", [](DlCanvas& builder) {
builder.DrawOval(draw_rect, DlPaint());
});
test_bounded("DrawCircle", [](DlCanvas& builder) {
builder.DrawCircle(draw_rect.center(), draw_rect.width() * 0.5f, DlPaint());
});
test_bounded("DrawRRect", [](DlCanvas& builder) {
builder.DrawRRect(SkRRect::MakeRectXY(draw_rect, 5.0f, 5.0f), DlPaint());
});
test_bounded("DrawDRRect", [](DlCanvas& builder) {
builder.DrawDRRect(
SkRRect::MakeRectXY(draw_rect, 5.0f, 5.0f),
SkRRect::MakeRectXY(draw_rect.makeInset(10.0f, 10.0f), 5.0f, 5.0f),
DlPaint());
});
test_bounded("DrawArc", [](DlCanvas& builder) {
builder.DrawArc(draw_rect, 45.0f, 355.0f, false, DlPaint());
});
test_bounded("DrawPathEvenOdd", [](DlCanvas& builder) {
SkPath path = SkPath::Rect(draw_rect);
path.setFillType(SkPathFillType::kEvenOdd);
builder.DrawPath(path, DlPaint());
});
test_bounded("DrawPathWinding", [](DlCanvas& builder) {
SkPath path = SkPath::Rect(draw_rect);
path.setFillType(SkPathFillType::kWinding);
builder.DrawPath(path, DlPaint());
});
auto test_draw_points = [&test_bounded](PointMode mode) {
std::stringstream ss;
ss << "DrawPoints(" << mode << ")";
test_bounded(ss.str(), [mode](DlCanvas& builder) {
SkPoint points[4] = {
{draw_rect.left() + 1.0f, draw_rect.top() + 1.0f},
{draw_rect.right() - 1.0f, draw_rect.top() + 1.0f},
{draw_rect.right() - 1.0f, draw_rect.bottom() - 1.0f},
{draw_rect.left() + 1.0f, draw_rect.bottom() - 1.0f},
};
DlPaint paint;
paint.setStrokeWidth(2.0f);
paint.setDrawStyle(DlDrawStyle::kStroke);
// bounds accumulation doesn't examine the points to see if they
// have diagonals so Square caps may have their corners accumulated
// but Round caps will always pad by only half the stroke width.
paint.setStrokeCap(DlStrokeCap::kRound);
builder.DrawPoints(mode, 4, points, paint);
});
};
test_draw_points(PointMode::kPoints);
test_draw_points(PointMode::kLines);
test_draw_points(PointMode::kPolygon);
test_bounded("DrawVerticesTriangles", [](DlCanvas& builder) {
SkPoint points[6] = {
{draw_rect.left(), draw_rect.top()},
{draw_rect.right(), draw_rect.top()},
{draw_rect.right(), draw_rect.bottom()},
{draw_rect.right(), draw_rect.bottom()},
{draw_rect.left(), draw_rect.bottom()},
{draw_rect.left(), draw_rect.top()},
};
DlVertices::Builder vertices(DlVertexMode::kTriangles, 6,
DlVertices::Builder::kNone, 0);
vertices.store_vertices(points);
builder.DrawVertices(vertices.build(), DlBlendMode::kSrcOver, DlPaint());
});
test_bounded("DrawVerticesTriangleStrip", [](DlCanvas& builder) {
SkPoint points[6] = {
{draw_rect.left(), draw_rect.top()},
{draw_rect.right(), draw_rect.top()},
{draw_rect.right(), draw_rect.bottom()},
{draw_rect.left(), draw_rect.bottom()},
{draw_rect.left(), draw_rect.top()},
{draw_rect.right(), draw_rect.top()},
};
DlVertices::Builder vertices(DlVertexMode::kTriangleStrip, 6,
DlVertices::Builder::kNone, 0);
vertices.store_vertices(points);
builder.DrawVertices(vertices.build(), DlBlendMode::kSrcOver, DlPaint());
});
test_bounded("DrawVerticesTriangleFan", [](DlCanvas& builder) {
SkPoint points[6] = {
draw_rect.center(),
{draw_rect.left(), draw_rect.top()},
{draw_rect.right(), draw_rect.top()},
{draw_rect.right(), draw_rect.bottom()},
{draw_rect.left(), draw_rect.bottom()},
};
DlVertices::Builder vertices(DlVertexMode::kTriangleFan, 5,
DlVertices::Builder::kNone, 0);
vertices.store_vertices(points);
builder.DrawVertices(vertices.build(), DlBlendMode::kSrcOver, DlPaint());
});
test_bounded("DrawImage", [](DlCanvas& builder) {
auto image = MakeTestImage(draw_rect.width(), draw_rect.height(), 5);
builder.DrawImage(image, {draw_rect.left(), draw_rect.top()},
DlImageSampling::kLinear);
});
test_bounded("DrawImageRect", [](DlCanvas& builder) {
auto image = MakeTestImage(root_cull.width(), root_cull.height(), 5);
builder.DrawImageRect(image, draw_rect, DlImageSampling::kLinear);
});
test_bounded("DrawImageNine", [](DlCanvas& builder) {
auto image = MakeTestImage(root_cull.width(), root_cull.height(), 5);
SkIRect center = image->bounds().makeInset(10, 10);
builder.DrawImageNine(image, center, draw_rect, DlFilterMode::kLinear);
});
test_bounded("DrawTextBlob", [](DlCanvas& builder) {
SkFont font = CreateTestFontOfSize(20.0f);
sk_sp<SkTypeface> face = font.refTypeface();
ASSERT_TRUE(face);
ASSERT_TRUE(face->countGlyphs() > 0) << "No glyphs in font";
auto blob = SkTextBlob::MakeFromText("Hello", 5, font);
// Make sure the blob fits within the draw_rect bounds.
ASSERT_LT(blob->bounds().width(), draw_rect.width());
ASSERT_LT(blob->bounds().height(), draw_rect.height());
// Draw once at upper left and again at lower right to fill the bounds.
builder.DrawTextBlob(blob, draw_rect.left() - blob->bounds().left(),
draw_rect.top() - blob->bounds().top(), DlPaint());
builder.DrawTextBlob(blob, draw_rect.right() - blob->bounds().right(),
draw_rect.bottom() - blob->bounds().bottom(),
DlPaint());
});
test_bounded("DrawTextFrame", [](DlCanvas& builder) {
SkFont font = CreateTestFontOfSize(20.0f);
sk_sp<SkTypeface> face = font.refTypeface();
ASSERT_TRUE(face);
ASSERT_TRUE(face->countGlyphs() > 0) << "No glyphs in font";
auto blob = SkTextBlob::MakeFromText("Hello", 5, font);
auto frame = impeller::MakeTextFrameFromTextBlobSkia(blob);
// Make sure the blob fits within the draw_rect bounds.
ASSERT_LT(blob->bounds().width(), draw_rect.width());
ASSERT_LT(blob->bounds().height(), draw_rect.height());
// Draw once at upper left and again at lower right to fill the bounds.
builder.DrawTextFrame(frame, draw_rect.left() - blob->bounds().left(),
draw_rect.top() - blob->bounds().top(), DlPaint());
builder.DrawTextFrame(frame, draw_rect.right() - blob->bounds().right(),
draw_rect.bottom() - blob->bounds().bottom(),
DlPaint());
});
test_bounded("DrawBoundedDisplayList", [](DlCanvas& builder) {
DisplayListBuilder nested_builder(root_cull);
nested_builder.DrawRect(draw_rect, DlPaint());
auto nested_display_list = nested_builder.Build();
EXPECT_EQ(nested_display_list->bounds(), draw_rect);
ASSERT_FALSE(nested_display_list->root_is_unbounded());
builder.DrawDisplayList(nested_display_list);
});
test_bounded("DrawShadow", [](DlCanvas& builder) {
SkPath path = SkPath::Rect(draw_rect.makeInset(20, 20));
DlScalar elevation = 2.0f;
DlScalar dpr = 1.0f;
auto shadow_bounds =
DlCanvas::ComputeShadowBounds(path, elevation, dpr, SkMatrix::I());
// Make sure the shadow fits within the draw_rect bounds.
ASSERT_LT(shadow_bounds.width(), draw_rect.width());
ASSERT_LT(shadow_bounds.height(), draw_rect.height());
// Draw once at upper left and again at lower right to fill the bounds.
SkPath pathUL = //
SkPath(path).offset(draw_rect.left() - shadow_bounds.left(),
draw_rect.top() - shadow_bounds.top());
builder.DrawShadow(pathUL, DlColor::kMagenta(), elevation, true, dpr);
SkPath pathLR =
SkPath(path).offset(draw_rect.right() - shadow_bounds.right(),
draw_rect.bottom() - shadow_bounds.bottom());
builder.DrawShadow(pathLR, DlColor::kMagenta(), elevation, true, dpr);
});
for (int i = 0; i <= static_cast<int>(DlBlendMode::kLastMode); i++) {
DlBlendMode mode = static_cast<DlBlendMode>(i);
if (mode == DlBlendMode::kDst) {
// No way to make kDst a non-nop
continue;
}
std::stringstream ss;
ss << "DrawRectWith" << mode;
test_bounded(ss.str(), [mode](DlCanvas& builder) {
// alpha of 0x7f prevents kDstIn from being a nop
builder.DrawRect(draw_rect, DlPaint().setBlendMode(mode).setAlpha(0x7f));
});
}
}
TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) {
static const SkRect root_cull = SkRect::MakeLTRB(100, 100, 200, 200);
static const SkRect clip_rect = SkRect::MakeLTRB(120, 120, 180, 180);
static const SkRect draw_rect = SkRect::MakeLTRB(110, 110, 190, 190);
using Renderer = const std::function<void(DlCanvas&)>;
auto test_unbounded = [](const std::string& label, //
const Renderer& renderer,
int extra_save_layers = 0) {
{
DisplayListBuilder builder(root_cull);
renderer(builder);
auto display_list = builder.Build();
EXPECT_EQ(display_list->bounds(), root_cull) << label;
EXPECT_TRUE(display_list->root_is_unbounded()) << label;
}
{
DisplayListBuilder builder(root_cull);
builder.ClipRect(clip_rect);
renderer(builder);
auto display_list = builder.Build();
EXPECT_EQ(display_list->bounds(), clip_rect) << label;
EXPECT_FALSE(display_list->root_is_unbounded()) << label;
}
{
DisplayListBuilder builder(root_cull);
builder.SaveLayer(nullptr, nullptr);
renderer(builder);
builder.Restore();
auto display_list = builder.Build();
EXPECT_EQ(display_list->bounds(), root_cull) << label;
EXPECT_FALSE(display_list->root_is_unbounded()) << label;
SAVE_LAYER_EXPECTOR(expector);
expector //
.addDetail(label)
.addExpectation([](const SaveLayerOptions& options) {
return options.content_is_unbounded();
});
for (int i = 0; i < extra_save_layers; i++) {
expector.addOpenExpectation();
}
display_list->Dispatch(expector);
}
{
DisplayListBuilder builder(root_cull);
builder.SaveLayer(nullptr, nullptr);
builder.ClipRect(clip_rect);
renderer(builder);
builder.Restore();
auto display_list = builder.Build();
EXPECT_EQ(display_list->bounds(), clip_rect) << label;
EXPECT_FALSE(display_list->root_is_unbounded()) << label;
SAVE_LAYER_EXPECTOR(expector);
expector //
.addDetail(label)
.addExpectation([](const SaveLayerOptions& options) {
return !options.content_is_unbounded();
});
for (int i = 0; i < extra_save_layers; i++) {
expector.addOpenExpectation();
}
display_list->Dispatch(expector);
}
};
test_unbounded("DrawPaint", [](DlCanvas& builder) { //
builder.DrawPaint(DlPaint());
});
test_unbounded("DrawColor", [](DlCanvas& builder) {
builder.DrawColor(DlColor::kMagenta(), DlBlendMode::kSrc);
});
test_unbounded("Clear", [](DlCanvas& builder) { //
builder.Clear(DlColor::kMagenta());
});
test_unbounded("DrawPathEvenOddInverted", [](DlCanvas& builder) {
SkPath path = SkPath::Rect(draw_rect);
path.setFillType(SkPathFillType::kInverseEvenOdd);
builder.DrawPath(path, DlPaint());
});
test_unbounded("DrawPathWindingInverted", [](DlCanvas& builder) {
SkPath path = SkPath::Rect(draw_rect);
path.setFillType(SkPathFillType::kInverseWinding);
builder.DrawPath(path, DlPaint());
});
test_unbounded("DrawUnboundedDisplayList", [](DlCanvas& builder) {
DisplayListBuilder nested_builder(root_cull);
nested_builder.DrawPaint(DlPaint());
auto nested_display_list = nested_builder.Build();
EXPECT_EQ(nested_display_list->bounds(), root_cull);
ASSERT_TRUE(nested_display_list->root_is_unbounded());
builder.DrawDisplayList(nested_display_list);
});
test_unbounded("DrawRectWithUnboundedImageFilter", [](DlCanvas& builder) {
// clang-format off
const DlScalar matrix[20] = {
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
};
// clang-format on
auto unbounded_cf = DlMatrixColorFilter::Make(matrix);
// ColorFilter must modify transparent black to be "unbounded"
ASSERT_TRUE(unbounded_cf->modifies_transparent_black());
auto unbounded_if = DlColorFilterImageFilter::Make(unbounded_cf);
SkRect output_bounds;
// ImageFilter returns null from bounds queries if it is "unbounded"
ASSERT_EQ(unbounded_if->map_local_bounds(draw_rect, output_bounds),
nullptr);
builder.DrawRect(draw_rect, DlPaint().setImageFilter(unbounded_if));
});
test_unbounded(
"SaveLayerWithBackdropFilter",
[](DlCanvas& builder) {
auto filter = DlBlurImageFilter::Make(3.0f, 3.0f, DlTileMode::kMirror);
builder.SaveLayer(nullptr, nullptr, filter.get());
builder.Restore();
},
1);
}
} // namespace testing
} // namespace flutter

View File

@@ -78,6 +78,7 @@ sk_sp<DisplayList> DisplayListBuilder::Build() {
bool is_safe = is_ui_thread_safe_;
bool affects_transparency = current_layer().affects_transparent_layer;
bool root_has_backdrop_filter = current_layer().contains_backdrop_filter;
bool root_is_unbounded = current_layer().is_unbounded;
DlBlendMode max_root_blend_mode = current_layer().max_blend_mode;
sk_sp<DlRTree> rtree;
@@ -111,7 +112,8 @@ sk_sp<DisplayList> DisplayListBuilder::Build() {
return sk_sp<DisplayList>(new DisplayList(
std::move(storage_), bytes, count, nested_bytes, nested_count,
total_depth, bounds, opacity_compatible, is_safe, affects_transparency,
max_root_blend_mode, root_has_backdrop_filter, std::move(rtree)));
max_root_blend_mode, root_has_backdrop_filter, root_is_unbounded,
std::move(rtree)));
}
static constexpr DlRect kEmpty = DlRect();
@@ -629,6 +631,10 @@ void DisplayListBuilder::RestoreLayer() {
layer_op->options = layer_op->options.with_can_distribute_opacity();
}
if (current_layer().is_unbounded) {
layer_op->options = layer_op->options.with_content_is_unbounded();
}
// Ensure that the bounds transferred in the following call will be
// attributed to the index of the restore op.
FML_DCHECK(layer_op->restore_index == op_index_);
@@ -1575,7 +1581,9 @@ void DisplayListBuilder::DrawDisplayList(const sk_sp<DisplayList> display_list,
const SkRect bounds = display_list->bounds();
bool accumulated;
sk_sp<const DlRTree> rtree;
if (!rtree_data_.has_value() || !(rtree = display_list->rtree())) {
if (display_list->root_is_unbounded()) {
accumulated = AccumulateUnbounded();
} else if (!rtree_data_.has_value() || !(rtree = display_list->rtree())) {
accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags);
} else {
std::list<SkRect> rects =
@@ -1788,6 +1796,9 @@ bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds,
}
bool DisplayListBuilder::AccumulateUnbounded(const SaveInfo& save) {
if (!save.has_valid_clip) {
save.layer_info->is_unbounded = true;
}
SkRect global_clip = save.global_state.device_cull_rect();
SkRect layer_clip = save.global_state.local_cull_rect();
if (global_clip.isEmpty() || !save.layer_state.mapAndClipRect(&layer_clip)) {

View File

@@ -554,6 +554,7 @@ class DisplayListBuilder final : public virtual DlCanvas,
bool opacity_incompatible_op_detected = false;
bool affects_transparent_layer = false;
bool contains_backdrop_filter = false;
bool is_unbounded = false;
bool is_group_opacity_compatible() const {
return !opacity_incompatible_op_detected &&

View File

@@ -144,6 +144,8 @@ std::ostream& operator<<(std::ostream& os, const SaveLayerOptions& options) {
<< "can_distribute_opacity: " << options.can_distribute_opacity()
<< ", "
<< "contains_backdrop: " << options.contains_backdrop_filter()
<< ", "
<< "is_unbounded: " << options.content_is_unbounded()
<< ")";
}