Skip to content

Commit c4d8870

Browse files
authored
[DisplayList] DlPath supports generic path dispatching (#164753)
There are different ways to iterate over an SkPath or an impeller::Path and various points in the engine source tree we have boilerplate duplicates of this code to transfer the contents of the DlPath wrapper object into some platform-specific path. This PR adds a dispatch/receiver mechanism to read back the contents of a DlPath - independent of whether it is backed by an SkPath or an impeller::Path - in a simpler form that avoids potential mistakes in the various conversion methods. See DlPathReceiver and DlPath::Dispatch in the dl_path.h file
1 parent 05c868e commit c4d8870

File tree

7 files changed

+667
-219
lines changed

7 files changed

+667
-219
lines changed

engine/src/flutter/display_list/geometry/dl_path.cc

Lines changed: 194 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@
88
#include "flutter/impeller/geometry/path_builder.h"
99
#include "impeller/geometry/path.h"
1010

11+
namespace {
12+
inline constexpr flutter::DlPathFillType ToDlFillType(SkPathFillType sk_type) {
13+
switch (sk_type) {
14+
case SkPathFillType::kEvenOdd:
15+
return impeller::FillType::kOdd;
16+
case SkPathFillType::kWinding:
17+
return impeller::FillType::kNonZero;
18+
case SkPathFillType::kInverseEvenOdd:
19+
case SkPathFillType::kInverseWinding:
20+
FML_UNREACHABLE();
21+
}
22+
}
23+
24+
inline constexpr SkPathFillType ToSkFillType(flutter::DlPathFillType dl_type) {
25+
switch (dl_type) {
26+
case impeller::FillType::kOdd:
27+
return SkPathFillType::kEvenOdd;
28+
case impeller::FillType::kNonZero:
29+
return SkPathFillType::kWinding;
30+
}
31+
}
32+
} // namespace
33+
1134
namespace flutter {
1235

1336
using Path = impeller::Path;
@@ -120,6 +143,22 @@ const Path& DlPath::GetPath() const {
120143
return path.value();
121144
}
122145

146+
void DlPath::Dispatch(DlPathReceiver& receiver) const {
147+
if (data_->sk_path_original) {
148+
auto& sk_path = data_->sk_path;
149+
FML_DCHECK(sk_path.has_value());
150+
if (sk_path.has_value()) {
151+
DispatchFromSkiaPath(sk_path.value(), receiver);
152+
}
153+
} else {
154+
auto& path = data_->path;
155+
FML_DCHECK(path.has_value());
156+
if (path.has_value()) {
157+
DispatchFromImpellerPath(path.value(), receiver);
158+
}
159+
}
160+
}
161+
123162
void DlPath::WillRenderSkPath() const {
124163
if (data_->render_count >= kMaxVolatileUses) {
125164
auto& sk_path = data_->sk_path;
@@ -252,26 +291,94 @@ DlPath DlPath::operator+(const DlPath& other) const {
252291
return DlPath(path);
253292
}
254293

255-
SkPath DlPath::ConvertToSkiaPath(const Path& path, const DlPoint& shift) {
256-
SkPath sk_path;
257-
sk_path.setFillType(ToSkFillType(path.GetFillType()));
294+
static void ReduceConic(DlPathReceiver& receiver,
295+
const DlPoint& p1,
296+
const DlPoint& cp,
297+
const DlPoint& p2,
298+
DlScalar weight) {
299+
// We might eventually have conic conversion math that deals with
300+
// degenerate conics gracefully (or have all receivers just handle
301+
// them directly). But, until then, we will just convert them to a
302+
// pair of quads and accept the results as "close enough".
303+
if (p1 != cp) {
304+
if (cp != p2) {
305+
std::array<DlPoint, 5> points;
306+
impeller::ConicPathComponent conic(p1, cp, p2, weight);
307+
conic.SubdivideToQuadraticPoints(points);
308+
receiver.QuadTo(points[1], points[2]);
309+
receiver.QuadTo(points[3], points[4]);
310+
} else {
311+
receiver.LineTo(cp);
312+
}
313+
} else if (cp != p2) {
314+
receiver.LineTo(p2);
315+
}
316+
}
317+
318+
namespace {
319+
class SkiaPathReceiver final : public DlPathReceiver {
320+
public:
321+
void SetPathInfo(DlPathFillType fill_type, bool is_convex) override {
322+
sk_path_.setFillType(ToSkFillType(fill_type));
323+
}
324+
void MoveTo(const DlPoint& p2) override { sk_path_.moveTo(ToSkPoint(p2)); }
325+
void LineTo(const DlPoint& p2) override { sk_path_.lineTo(ToSkPoint(p2)); }
326+
void QuadTo(const DlPoint& cp, const DlPoint& p2) override {
327+
sk_path_.quadTo(ToSkPoint(cp), ToSkPoint(p2));
328+
}
329+
bool ConicTo(const DlPoint& cp, const DlPoint& p2, DlScalar weight) override {
330+
sk_path_.conicTo(ToSkPoint(cp), ToSkPoint(p2), weight);
331+
return true;
332+
}
333+
void CubicTo(const DlPoint& cp1,
334+
const DlPoint& cp2,
335+
const DlPoint& p2) override {
336+
sk_path_.cubicTo(ToSkPoint(cp1), ToSkPoint(cp2), ToSkPoint(p2));
337+
}
338+
void Close() override { sk_path_.close(); }
339+
340+
SkPath TakePath() { return sk_path_; }
341+
342+
private:
343+
SkPath sk_path_;
344+
};
345+
} // namespace
346+
347+
SkPath DlPath::ConvertToSkiaPath(const Path& path) {
348+
SkiaPathReceiver receiver;
349+
350+
DispatchFromImpellerPath(path, receiver);
351+
352+
return receiver.TakePath();
353+
}
354+
355+
void DlPath::DispatchFromImpellerPath(const impeller::Path& path,
356+
DlPathReceiver& receiver) {
258357
bool subpath_needs_close = false;
259358
std::optional<DlPoint> pending_moveto;
260359

261-
auto resolve_moveto = [&pending_moveto, &sk_path]() {
360+
auto resolve_moveto = [&receiver, &pending_moveto]() {
262361
if (pending_moveto.has_value()) {
263-
sk_path.moveTo(ToSkPoint(pending_moveto.value()));
362+
receiver.MoveTo(pending_moveto.value());
264363
pending_moveto.reset();
265364
}
266365
};
267366

367+
// The Impeller Point Count is way overestimated due to duplicate
368+
// points between elements.
369+
receiver.RecommendSizes(path.GetComponentCount(), path.GetPointCount());
370+
std::optional<DlRect> bounds = path.GetBoundingBox();
371+
if (bounds.has_value()) {
372+
receiver.RecommendBounds(bounds.value());
373+
}
374+
receiver.SetPathInfo(path.GetFillType(), path.IsConvex());
268375
for (auto it = path.begin(), end = path.end(); it != end; ++it) {
269376
switch (it.type()) {
270377
case ComponentType::kContour: {
271378
const impeller::ContourComponent* contour = it.contour();
272379
FML_DCHECK(contour != nullptr);
273380
if (subpath_needs_close) {
274-
sk_path.close();
381+
receiver.Close();
275382
}
276383
pending_moveto = contour->destination;
277384
subpath_needs_close = contour->IsClosed();
@@ -281,45 +388,96 @@ SkPath DlPath::ConvertToSkiaPath(const Path& path, const DlPoint& shift) {
281388
const impeller::LinearPathComponent* linear = it.linear();
282389
FML_DCHECK(linear != nullptr);
283390
resolve_moveto();
284-
sk_path.lineTo(ToSkPoint(linear->p2));
391+
receiver.LineTo(linear->p2);
285392
break;
286393
}
287394
case ComponentType::kQuadratic: {
288395
const impeller::QuadraticPathComponent* quadratic = it.quadratic();
289396
FML_DCHECK(quadratic != nullptr);
290397
resolve_moveto();
291-
sk_path.quadTo(ToSkPoint(quadratic->cp), ToSkPoint(quadratic->p2));
398+
receiver.QuadTo(quadratic->cp, quadratic->p2);
292399
break;
293400
}
294401
case ComponentType::kConic: {
295402
const impeller::ConicPathComponent* conic = it.conic();
296403
FML_DCHECK(conic != nullptr);
297404
resolve_moveto();
298-
sk_path.conicTo(ToSkPoint(conic->cp), ToSkPoint(conic->p2),
299-
conic->weight.x);
405+
if (!receiver.ConicTo(conic->cp, conic->p2, conic->weight.x)) {
406+
ReduceConic(receiver, conic->p1, conic->cp, conic->p2,
407+
conic->weight.x);
408+
}
300409
break;
301410
}
302411
case ComponentType::kCubic: {
303412
const impeller::CubicPathComponent* cubic = it.cubic();
304413
FML_DCHECK(cubic != nullptr);
305414
resolve_moveto();
306-
sk_path.cubicTo(ToSkPoint(cubic->cp1), ToSkPoint(cubic->cp2),
307-
ToSkPoint(cubic->p2));
415+
receiver.CubicTo(cubic->cp1, cubic->cp2, cubic->p2);
308416
break;
309417
}
310418
}
311419
}
312420
if (subpath_needs_close) {
313-
sk_path.close();
421+
receiver.Close();
314422
}
315-
316-
return sk_path;
317423
}
318424

319-
Path DlPath::ConvertToImpellerPath(const SkPath& path, const DlPoint& shift) {
320-
if (path.isEmpty() || !shift.IsFinite()) {
425+
namespace {
426+
class ImpellerPathReceiver final : public DlPathReceiver {
427+
public:
428+
void RecommendSizes(size_t verb_count, size_t point_count) override {
429+
// Reserve a path size with some arbitrarily additional padding.
430+
builder_.Reserve(point_count + 8, verb_count + 8);
431+
}
432+
void RecommendBounds(const DlRect& bounds) override {
433+
builder_.SetBounds(bounds);
434+
}
435+
void SetPathInfo(DlPathFillType fill_type, bool is_convex) override {
436+
this->fill_type_ = fill_type;
437+
builder_.SetConvexity(is_convex ? Convexity::kConvex //
438+
: Convexity::kUnknown);
439+
}
440+
void MoveTo(const DlPoint& p2) override { builder_.MoveTo(p2); }
441+
void LineTo(const DlPoint& p2) override { builder_.LineTo(p2); }
442+
void QuadTo(const DlPoint& cp, const DlPoint& p2) override {
443+
builder_.QuadraticCurveTo(cp, p2);
444+
}
445+
// For legacy compatibility we do not override ConicTo to let the dispatcher
446+
// convert conics to quads until we update Impeller for full support of
447+
// rational quadratics
448+
void CubicTo(const DlPoint& cp1,
449+
const DlPoint& cp2,
450+
const DlPoint& p2) override {
451+
builder_.CubicCurveTo(cp1, cp2, p2);
452+
}
453+
void Close() override { builder_.Close(); }
454+
455+
impeller::Path TakePath() { return builder_.TakePath(fill_type_); }
456+
457+
private:
458+
PathBuilder builder_;
459+
DlPathFillType fill_type_;
460+
};
461+
} // namespace
462+
463+
Path DlPath::ConvertToImpellerPath(const SkPath& path) {
464+
if (path.isEmpty()) {
321465
return Path{};
322466
}
467+
468+
ImpellerPathReceiver receiver;
469+
470+
DispatchFromSkiaPath(path, receiver);
471+
472+
return receiver.TakePath();
473+
}
474+
475+
void DlPath::DispatchFromSkiaPath(const SkPath& path,
476+
DlPathReceiver& receiver) {
477+
if (path.isEmpty()) {
478+
return;
479+
}
480+
323481
auto iterator = SkPath::Iter(path, false);
324482

325483
struct PathData {
@@ -328,67 +486,47 @@ Path DlPath::ConvertToImpellerPath(const SkPath& path, const DlPoint& shift) {
328486
};
329487
};
330488

331-
PathBuilder builder;
332489
PathData data;
333-
// Reserve a path size with some arbitrarily additional padding.
334-
builder.Reserve(path.countPoints() + 8, path.countVerbs() + 8);
490+
491+
receiver.RecommendSizes(path.countVerbs(), path.countPoints());
492+
receiver.RecommendBounds(ToDlRect(path.getBounds()));
493+
receiver.SetPathInfo(ToDlFillType(path.getFillType()), path.isConvex());
335494
auto verb = SkPath::Verb::kDone_Verb;
336495
do {
337496
verb = iterator.next(data.points);
338497
switch (verb) {
339498
case SkPath::kMove_Verb:
340-
builder.MoveTo(ToDlPoint(data.points[0]));
499+
receiver.MoveTo(ToDlPoint(data.points[0]));
341500
break;
342501
case SkPath::kLine_Verb:
343-
builder.LineTo(ToDlPoint(data.points[1]));
502+
receiver.LineTo(ToDlPoint(data.points[1]));
344503
break;
345504
case SkPath::kQuad_Verb:
346-
builder.QuadraticCurveTo(ToDlPoint(data.points[1]),
347-
ToDlPoint(data.points[2]));
505+
receiver.QuadTo(ToDlPoint(data.points[1]), ToDlPoint(data.points[2]));
348506
break;
349507
case SkPath::kConic_Verb:
350-
// We might eventually have conic conversion math that deals with
351-
// degenerate conics gracefully (or just handle them directly),
352-
// but until then, we will detect and ignore them.
353-
if (data.points[0] != data.points[1]) {
354-
if (data.points[1] != data.points[2]) {
355-
std::array<DlPoint, 5> points;
356-
impeller::ConicPathComponent conic(
357-
ToDlPoint(data.points[0]), ToDlPoint(data.points[1]),
358-
ToDlPoint(data.points[2]), iterator.conicWeight());
359-
conic.SubdivideToQuadraticPoints(points);
360-
builder.QuadraticCurveTo(points[1], points[2]);
361-
builder.QuadraticCurveTo(points[3], points[4]);
362-
} else {
363-
builder.LineTo(ToDlPoint(data.points[1]));
364-
}
365-
} else if (data.points[1] != data.points[2]) {
366-
builder.LineTo(ToDlPoint(data.points[2]));
508+
if (!receiver.ConicTo(ToDlPoint(data.points[1]),
509+
ToDlPoint(data.points[2]),
510+
iterator.conicWeight())) {
511+
ReduceConic(receiver, //
512+
ToDlPoint(data.points[0]), //
513+
ToDlPoint(data.points[1]), //
514+
ToDlPoint(data.points[2]), //
515+
iterator.conicWeight());
367516
}
368517
break;
369518
case SkPath::kCubic_Verb:
370-
builder.CubicCurveTo(ToDlPoint(data.points[1]),
371-
ToDlPoint(data.points[2]),
372-
ToDlPoint(data.points[3]));
519+
receiver.CubicTo(ToDlPoint(data.points[1]), //
520+
ToDlPoint(data.points[2]), //
521+
ToDlPoint(data.points[3]));
373522
break;
374523
case SkPath::kClose_Verb:
375-
builder.Close();
524+
receiver.Close();
376525
break;
377526
case SkPath::kDone_Verb:
378527
break;
379528
}
380529
} while (verb != SkPath::Verb::kDone_Verb);
381-
382-
DlRect bounds = ToDlRect(path.getBounds());
383-
if (!shift.IsZero()) {
384-
builder.Shift(shift);
385-
bounds = bounds.Shift(shift);
386-
}
387-
388-
builder.SetConvexity(path.isConvex() ? Convexity::kConvex
389-
: Convexity::kUnknown);
390-
builder.SetBounds(bounds);
391-
return builder.TakePath(ToDlFillType(path.getFillType()));
392530
}
393531

394532
} // namespace flutter

0 commit comments

Comments
 (0)