mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Impeller] track path convexity and use it to simplify fill tessellation. (flutter/engine#41834)
We can do this on the GPU, but we'll need a fallback when we don't have polyline compute. From experimentation, Skia has already computed this value when Impeller gets that path. Additionally, this will allow us to speed up tessellation of known convex shapes like ovals that we don't want to add special cases for in the API.
This commit is contained in:
parent
af593c9777
commit
d64bc16426
@ -251,10 +251,8 @@ void Canvas::DrawRRect(Rect rect, Scalar corner_radius, const Paint& paint) {
|
||||
|
||||
GetCurrentPass().AddEntity(entity);
|
||||
return;
|
||||
} else {
|
||||
DrawPath(PathBuilder{}.AddRoundedRect(rect, corner_radius).TakePath(),
|
||||
paint);
|
||||
}
|
||||
DrawPath(PathBuilder{}.AddRoundedRect(rect, corner_radius).TakePath(), paint);
|
||||
}
|
||||
|
||||
void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
|
||||
@ -263,7 +261,11 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
|
||||
paint)) {
|
||||
return;
|
||||
}
|
||||
DrawPath(PathBuilder{}.AddCircle(center, radius).TakePath(), paint);
|
||||
auto circle_path = PathBuilder{}
|
||||
.AddCircle(center, radius)
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.TakePath();
|
||||
DrawPath(circle_path, paint);
|
||||
}
|
||||
|
||||
void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {
|
||||
|
||||
@ -845,6 +845,7 @@ void DlDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) {
|
||||
auto path =
|
||||
PathBuilder{}
|
||||
.AddLine(skia_conversions::ToPoint(p0), skia_conversions::ToPoint(p1))
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.TakePath();
|
||||
Paint paint = paint_;
|
||||
paint.style = Paint::Style::kStroke;
|
||||
@ -862,8 +863,10 @@ void DlDispatcher::drawOval(const SkRect& bounds) {
|
||||
canvas_.DrawCircle(skia_conversions::ToPoint(bounds.center()),
|
||||
bounds.width() * 0.5, paint_);
|
||||
} else {
|
||||
auto path =
|
||||
PathBuilder{}.AddOval(skia_conversions::ToRect(bounds)).TakePath();
|
||||
auto path = PathBuilder{}
|
||||
.AddOval(skia_conversions::ToRect(bounds))
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.TakePath();
|
||||
canvas_.DrawPath(path, paint_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,12 +109,15 @@ Path ToPath(const SkPath& path) {
|
||||
fill_type = FillType::kNonZero;
|
||||
break;
|
||||
}
|
||||
builder.SetConvexity(path.isConvex() ? Convexity::kConvex
|
||||
: Convexity::kUnknown);
|
||||
return builder.TakePath(fill_type);
|
||||
}
|
||||
|
||||
Path ToPath(const SkRRect& rrect) {
|
||||
return PathBuilder{}
|
||||
.AddRoundedRect(ToRect(rrect.getBounds()), ToRoundingRadii(rrect))
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.TakePath();
|
||||
}
|
||||
|
||||
|
||||
@ -2572,5 +2572,41 @@ TEST_P(EntityTest, TiledTextureContentsIsOpaque) {
|
||||
ASSERT_FALSE(contents.IsOpaque());
|
||||
}
|
||||
|
||||
TEST_P(EntityTest, TessellateConvex) {
|
||||
{
|
||||
// Sanity check simple rectangle.
|
||||
auto [pts, indices] =
|
||||
TessellateConvex(PathBuilder{}
|
||||
.AddRect(Rect::MakeLTRB(0, 0, 10, 10))
|
||||
.TakePath()
|
||||
.CreatePolyline(1.0));
|
||||
|
||||
std::vector<Point> expected = {
|
||||
{0, 0}, {10, 0}, {10, 10}, {0, 10}, //
|
||||
};
|
||||
std::vector<uint16_t> expected_indices = {0, 1, 2, 0, 2, 3};
|
||||
ASSERT_EQ(pts, expected);
|
||||
ASSERT_EQ(indices, expected_indices);
|
||||
}
|
||||
|
||||
{
|
||||
auto [pts, indices] =
|
||||
TessellateConvex(PathBuilder{}
|
||||
.AddRect(Rect::MakeLTRB(0, 0, 10, 10))
|
||||
.AddRect(Rect::MakeLTRB(20, 20, 30, 30))
|
||||
.TakePath()
|
||||
.CreatePolyline(1.0));
|
||||
|
||||
std::vector<Point> expected = {
|
||||
{0, 0}, {10, 0}, {10, 10}, {0, 10}, //
|
||||
{20, 20}, {30, 20}, {30, 30}, {20, 30} //
|
||||
};
|
||||
std::vector<uint16_t> expected_indices = {0, 1, 2, 0, 2, 3,
|
||||
0, 6, 7, 0, 7, 8};
|
||||
ASSERT_EQ(pts, expected);
|
||||
ASSERT_EQ(indices, expected_indices);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@ -16,6 +16,37 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
/// Given a convex polyline, create a triangle fan structure.
|
||||
std::pair<std::vector<Point>, std::vector<uint16_t>> TessellateConvex(
|
||||
Path::Polyline polyline) {
|
||||
std::vector<Point> output;
|
||||
std::vector<uint16_t> index;
|
||||
|
||||
for (auto j = 0u; j < polyline.contours.size(); j++) {
|
||||
auto [start, end] = polyline.GetContourPointBounds(j);
|
||||
auto center = polyline.points[start];
|
||||
|
||||
// Some polygons will not self close and an additional triangle
|
||||
// must be inserted, others will self close and we need to avoid
|
||||
// inserting an extra triangle.
|
||||
if (polyline.points[end - 1] == polyline.points[start]) {
|
||||
end--;
|
||||
}
|
||||
output.emplace_back(center);
|
||||
output.emplace_back(polyline.points[start + 1]);
|
||||
|
||||
for (auto i = start + 2; i < end; i++) {
|
||||
const auto& point_b = polyline.points[i];
|
||||
output.emplace_back(point_b);
|
||||
|
||||
index.emplace_back(0);
|
||||
index.emplace_back(i - 1);
|
||||
index.emplace_back(i);
|
||||
}
|
||||
}
|
||||
return std::make_pair(output, index);
|
||||
}
|
||||
|
||||
Geometry::Geometry() = default;
|
||||
|
||||
Geometry::~Geometry() = default;
|
||||
@ -103,8 +134,30 @@ GeometryResult FillPathGeometry::GetPositionBuffer(
|
||||
const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) {
|
||||
VertexBuffer vertex_buffer;
|
||||
auto& host_buffer = pass.GetTransientsBuffer();
|
||||
VertexBuffer vertex_buffer;
|
||||
|
||||
if (path_.GetFillType() == FillType::kNonZero && //
|
||||
path_.IsConvex()) {
|
||||
auto [points, indicies] = TessellateConvex(
|
||||
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()));
|
||||
|
||||
vertex_buffer.vertex_buffer = host_buffer.Emplace(
|
||||
points.data(), points.size() * sizeof(Point), alignof(Point));
|
||||
vertex_buffer.index_buffer = host_buffer.Emplace(
|
||||
indicies.data(), indicies.size() * sizeof(uint16_t), alignof(uint16_t));
|
||||
vertex_buffer.index_count = indicies.size();
|
||||
vertex_buffer.index_type = IndexType::k16bit;
|
||||
|
||||
return GeometryResult{
|
||||
.type = PrimitiveType::kTriangle,
|
||||
.vertex_buffer = vertex_buffer,
|
||||
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
|
||||
entity.GetTransformation(),
|
||||
.prevent_overdraw = false,
|
||||
};
|
||||
}
|
||||
|
||||
auto tesselation_result = renderer.GetTessellator()->Tessellate(
|
||||
path_.GetFillType(),
|
||||
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()),
|
||||
|
||||
@ -33,6 +33,11 @@ enum GeometryVertexType {
|
||||
kUV,
|
||||
};
|
||||
|
||||
/// @brief Given a polyline created from a convex filled path, perform a
|
||||
/// tessellation.
|
||||
std::pair<std::vector<Point>, std::vector<uint16_t>> TessellateConvex(
|
||||
Path::Polyline polyline);
|
||||
|
||||
class Geometry {
|
||||
public:
|
||||
Geometry();
|
||||
|
||||
@ -53,6 +53,14 @@ FillType Path::GetFillType() const {
|
||||
return fill_;
|
||||
}
|
||||
|
||||
bool Path::IsConvex() const {
|
||||
return convexity_ == Convexity::kConvex;
|
||||
}
|
||||
|
||||
void Path::SetConvexity(Convexity value) {
|
||||
convexity_ = value;
|
||||
}
|
||||
|
||||
Path& Path::AddLinearComponent(Point p1, Point p2) {
|
||||
linears_.emplace_back(p1, p2);
|
||||
components_.emplace_back(ComponentType::kLinear, linears_.size() - 1);
|
||||
|
||||
@ -34,6 +34,11 @@ enum class FillType {
|
||||
kAbsGeqTwo,
|
||||
};
|
||||
|
||||
enum class Convexity {
|
||||
kUnknown,
|
||||
kConvex,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief Paths are lightweight objects that describe a collection of
|
||||
/// linear, quadratic, or cubic segments. These segments may be
|
||||
@ -94,6 +99,8 @@ class Path {
|
||||
|
||||
FillType GetFillType() const;
|
||||
|
||||
bool IsConvex() const;
|
||||
|
||||
Path& AddLinearComponent(Point p1, Point p2);
|
||||
|
||||
Path& AddQuadraticComponent(Point p1, Point cp, Point p2);
|
||||
@ -148,6 +155,10 @@ class Path {
|
||||
std::optional<std::pair<Point, Point>> GetMinMaxCoveragePoints() const;
|
||||
|
||||
private:
|
||||
friend class PathBuilder;
|
||||
|
||||
void SetConvexity(Convexity value);
|
||||
|
||||
struct ComponentIndexPair {
|
||||
ComponentType type = ComponentType::kLinear;
|
||||
size_t index = 0;
|
||||
@ -159,6 +170,7 @@ class Path {
|
||||
};
|
||||
|
||||
FillType fill_ = FillType::kNonZero;
|
||||
Convexity convexity_ = Convexity::kUnknown;
|
||||
std::vector<ComponentIndexPair> components_;
|
||||
std::vector<LinearPathComponent> linears_;
|
||||
std::vector<QuadraticPathComponent> quads_;
|
||||
|
||||
@ -21,6 +21,7 @@ Path PathBuilder::CopyPath(FillType fill) const {
|
||||
Path PathBuilder::TakePath(FillType fill) {
|
||||
auto path = prototype_;
|
||||
path.SetFillType(fill);
|
||||
path.SetConvexity(convexity_);
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -103,6 +104,11 @@ PathBuilder& PathBuilder::SmoothQuadraticCurveTo(Point point, bool relative) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::SetConvexity(Convexity value) {
|
||||
convexity_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::CubicCurveTo(Point controlPoint1,
|
||||
Point controlPoint2,
|
||||
Point point,
|
||||
|
||||
@ -31,6 +31,8 @@ class PathBuilder {
|
||||
|
||||
const Path& GetCurrentPath() const;
|
||||
|
||||
PathBuilder& SetConvexity(Convexity value);
|
||||
|
||||
PathBuilder& MoveTo(Point point, bool relative = false);
|
||||
|
||||
PathBuilder& Close();
|
||||
@ -116,6 +118,7 @@ class PathBuilder {
|
||||
Point subpath_start_;
|
||||
Point current_;
|
||||
Path prototype_;
|
||||
Convexity convexity_;
|
||||
|
||||
Point ReflectedQuadraticControlPoint1() const;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user