[Impeller] compute path bounds once, use Skia computed bounds where possible. (flutter/engine#45456)

Now that Paths are externally immutable, we can compute the bounds once at construction. This also allows the Skia conversions to set the already computed bounds.
This commit is contained in:
Jonah Williams
2023-09-05 10:44:35 -07:00
committed by GitHub
parent d98faa8d1c
commit ccb41f292c
6 changed files with 62 additions and 2 deletions

View File

@@ -122,6 +122,8 @@ Path ToPath(const SkPath& path, Point shift) {
builder.SetConvexity(path.isConvex() ? Convexity::kConvex
: Convexity::kUnknown);
builder.Shift(shift);
auto sk_bounds = path.getBounds().makeOutset(shift.x, shift.y);
builder.SetBounds(ToRect(sk_bounds));
return builder.TakePath(fill_type);
}
@@ -129,6 +131,7 @@ Path ToPath(const SkRRect& rrect) {
return PathBuilder{}
.AddRoundedRect(ToRect(rrect.getBounds()), ToRoundingRadii(rrect))
.SetConvexity(Convexity::kConvex)
.SetBounds(ToRect(rrect.getBounds()))
.TakePath();
}

View File

@@ -2422,6 +2422,25 @@ TEST(GeometryTest, PathShifting) {
ASSERT_EQ(cubic.p2, Point(31, 31));
}
TEST(GeometryTest, PathBuilderWillComputeBounds) {
PathBuilder builder;
auto path_1 = builder.AddLine({0, 0}, {1, 1}).TakePath();
ASSERT_EQ(path_1.GetBoundingBox().value(), Rect::MakeLTRB(0, 0, 1, 1));
auto path_2 = builder.AddLine({-1, -1}, {1, 1}).TakePath();
// Verify that PathBuilder recomputes the bounds.
ASSERT_EQ(path_2.GetBoundingBox().value(), Rect::MakeLTRB(-1, -1, 1, 1));
// PathBuilder can set the bounds to whatever it wants
auto path_3 = builder.AddLine({0, 0}, {1, 1})
.SetBounds(Rect::MakeLTRB(0, 0, 100, 100))
.TakePath();
ASSERT_EQ(path_3.GetBoundingBox().value(), Rect::MakeLTRB(0, 0, 100, 100));
}
} // namespace testing
} // namespace impeller

View File

@@ -397,14 +397,19 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
}
std::optional<Rect> Path::GetBoundingBox() const {
return computed_bounds_;
}
void Path::ComputeBounds() {
auto min_max = GetMinMaxCoveragePoints();
if (!min_max.has_value()) {
return std::nullopt;
computed_bounds_ = std::nullopt;
return;
}
auto min = min_max->first;
auto max = min_max->second;
const auto difference = max - min;
return Rect{min.x, min.y, difference.x, difference.y};
computed_bounds_ = Rect{min.x, min.y, difference.x, difference.y};
}
std::optional<Rect> Path::GetTransformedBoundingBox(
@@ -461,4 +466,8 @@ std::optional<std::pair<Point, Point>> Path::GetMinMaxCoveragePoints() const {
return std::make_pair(min.value(), max.value());
}
void Path::SetBounds(Rect rect) {
computed_bounds_ = rect;
}
} // namespace impeller

View File

@@ -138,6 +138,8 @@ class Path {
void SetFillType(FillType fill);
void SetBounds(Rect rect);
Path& AddLinearComponent(Point p1, Point p2);
Path& AddQuadraticComponent(Point p1, Point cp, Point p2);
@@ -146,6 +148,12 @@ class Path {
Path& AddContourComponent(Point destination, bool is_closed = false);
/// @brief Called by `PathBuilder` to compute the bounds for certain paths.
///
/// `PathBuilder` may set the bounds directly, in case they come from a source
/// with already computed bounds, such as an SkPath.
void ComputeBounds();
void SetContourClosed(bool is_closed);
void Shift(Point shift);
@@ -178,6 +186,8 @@ class Path {
std::vector<QuadraticPathComponent> quads_;
std::vector<CubicPathComponent> cubics_;
std::vector<ContourComponent> contours_;
std::optional<Rect> computed_bounds_;
};
} // namespace impeller

View File

@@ -22,6 +22,10 @@ Path PathBuilder::TakePath(FillType fill) {
auto path = prototype_;
path.SetFillType(fill);
path.SetConvexity(convexity_);
if (!did_compute_bounds_) {
path.ComputeBounds();
}
did_compute_bounds_ = false;
return path;
}
@@ -458,4 +462,10 @@ PathBuilder& PathBuilder::Shift(Point offset) {
return *this;
}
PathBuilder& PathBuilder::SetBounds(Rect bounds) {
prototype_.SetBounds(bounds);
did_compute_bounds_ = true;
return *this;
}
} // namespace impeller

View File

@@ -97,6 +97,14 @@ class PathBuilder {
/// `offset`.
PathBuilder& Shift(Point offset);
/// @brief Set the bounding box that will be used by `Path.GetBoundingBox` in
/// place of performing the computation.
///
/// When Impeller recieves Skia Path objects, many of these already
/// have computed bounds. This method is used to avoid needlessly
/// recomputing these bounds.
PathBuilder& SetBounds(Rect bounds);
struct RoundingRadii {
Point top_left;
Point bottom_left;
@@ -133,6 +141,7 @@ class PathBuilder {
Point current_;
Path prototype_;
Convexity convexity_;
bool did_compute_bounds_ = false;
PathBuilder& AddRoundedRectTopLeft(Rect rect, RoundingRadii radii);