Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Flutter CustomPaint @Flutter MeetUp(In Songdo)...

JaiChangPark
September 22, 2023

Flutter CustomPaint @Flutter MeetUp(In Songdo) - 박제창

https://festa.io/events/3887

Flutter CustomPaint @Flutter MeetUp(In Songdo) - 박제창

JaiChangPark

September 22, 2023
Tweet

More Decks by JaiChangPark

Other Decks in Programming

Transcript

  1. Agenda 1 2 3 4 Intro What’s Skia CustomPainter &

    CustomPaint How to render Text Widget 3
  2. Rendering The engine is responsible for rasterizing composited scenes whenever

    a new frame needs to be painted. It provides the low-level implementation of Flutter’s core API, including graphics (through Impeller on iOS and coming to Android, and Skia on other platforms) text layout. 7
  3. Skia is an open source 2D graphics library which provides

    common APIs that work across a variety of hardware and software platforms. It serves as the graphics engine for Google Chrome and ChromeOS, Android, Flutter, and many other products. https://github.com/google/skia Skia The 2D Graphics Library 8
  4. • Predictable performance: Impeller compiles all shaders and reflection offline

    at build time. It builds all pipeline state objects upfront. The engine controls caching and caches explicitly. • Instrumentable: Impeller tags and labels all graphics resources like textures, and buffers. It can capture and persist animations to disk without affecting per-frame rendering performance. • Portable: Flutter doesn’t tie Impeller to a specific client rendering API. You can author shaders once and convert them to backend-specific formats as necessary. • Leverages modern graphics APIs: Impeller uses, but doesn’t depend on, features available in modern APIs like Metal and Vulkan. • Leverages concurrency: Impeller can distribute single-frame workloads across multiple threads if necessary. Impeller Impeller provides a new rendering runtime for Flutter. 9
  5. Material Material 3 is the latest version of Google’s open-source

    design system. Design and build beautiful, usable products with Material 3. 10
  6. Skia 14 Platforms • Windows 7, 8, 8.1, 10 •

    macOS 10.13 or later • iOS 11 or later • Android 4.1 (JellyBean) or later • Ubuntu 18.04+, Debian 10+, openSUSE 15.2+, or Fedora Linux 32+
  7. @override void paint(Canvas canvas, Size size) { final Paint paint

    = Paint() ..color = Colors.red ..strokeWidth = 10; canvas.drawLine( const Offset(20, 20), const Offset(100, 100), paint, ); } 18
  8. @override void drawLine(Offset p1, Offset p2, Paint paint) { assert(_offsetIsValid(p1));

    assert(_offsetIsValid(p2)); _drawLine(p1.dx, p1.dy, p2.dx, p2.dy, paint._objects, paint._data); } @Native<Void Function(Pointer<Void>, Double, Double, Double, Double, Handle, Handle)>(symbol: 'Canvas::drawLine') external void _drawLine(double x1, double y1, double x2, double y2, List<Object?>? paintObjects, ByteData paintData); 20 flutter/engine/lib/ui/painting.dart Flutter / ffi
  9. void Canvas::drawLine(double x1, double y1, double x2, double y2, Dart_Handle

    paint_objects, Dart_Handle paint_data) { Paint paint(paint_objects, paint_data); FML_DCHECK(paint.isNotNull()); if (display_list_builder_) { DlPaint dl_paint; paint.paint(dl_paint, kDrawLineFlags); builder()->DrawLine(SkPoint::Make(SafeNarrow(x1), SafeNarrow(y1)), SkPoint::Make(SafeNarrow(x2), SafeNarrow(y2)), dl_paint); } } 21 /engine/lib/ui/painting/canvas.cc engine
  10. class DisplayListBuilder final : public virtual DlCanvas, public SkRefCnt, virtual

    DlOpReceiver, DisplayListOpFlags { // |DlCanvas| void DrawLine(const SkPoint& p0, const SkPoint& p1, const DlPaint& paint) override; void DrawCircle(const SkPoint& center, SkScalar radius,const DlPaint& paint) override; // |DlCanvas| void DrawRRect(const SkRRect& rrect, const DlPaint& paint) override; // |DlCanvas| void DrawDRRect(const SkRRect& outer, const SkRRect& inner, const DlPaint& paint) override; // |DlCanvas| void DrawPath(const SkPath& path, const DlPaint& paint) override; 22 /engine/display_list/dl_builder.h engine
  11. void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) { SkRect bounds

    = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); DisplayListAttributeFlags flags = (bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags : kDrawHVLineFlags; OpResult result = PaintResult(current_, flags); if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) { Push<DrawLineOp>(0, 1, p0, p1); CheckLayerOpacityCompatibility(); UpdateLayerResult(result); } } void DisplayListBuilder::DrawLine(const SkPoint& p0, const SkPoint& p1, const DlPaint& paint) { SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawLineFlags); drawLine(p0, p1); } 23 /engine/display_list/dl_builder.cc engine
  12. 24 canvas.drawLine( const Offset(20, 20), const Offset(100, 100), paint, );

    Canvas _drawLine flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart ffi CustomPainter Canvas::drawLine builder()->DrawLine(SkPoint::Make(SafeNarrow(x1), SafeNarrow(y1)), SkPoint::Make(SafeNarrow(x2), SafeNarrow(y2)), dl_paint); /engine/lib/ui/painting/canvas.cc /engine/display_list/dl_builder.cc DisplayListBuilder::drawLine SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); /engine/display_list/dl_builder.cc CustomPaint Widget으로 CustomPainter를 사용하여 선을 그리는 과정
  13. When asked to paint, CustomPaint first asks its painter to

    paint on the current canvas, then it paints its child, and then, after painting its child, it asks its foregroundPainter to paint. The coordinate system of the canvas matches the coordinate system of the CustomPaint object. The painters are expected to paint within a rectangle starting at the origin and encompassing a region of the given size. To enforce painting within those bounds, consider wrapping this CustomPaint with a ClipRect widget. Painters are implemented by subclassing CustomPainter. Custom painters normally size themselves to their child. If they do not have a child, they attempt to size themselves to the size, which defaults to Size.zero. size must not be null. A widget that provides a canvas on which to draw during the paint phase. CustomPaint 26 Widget
  14. class CustomPaint extends SingleChildRenderObjectWidget { const CustomPaint({ super.key, this.painter, this.foregroundPainter,

    this.size = Size.zero, this.isComplex = false, this.willChange = false, super.child, }) final CustomPainter? painter; 28
  15. @override RenderCustomPaint createRenderObject(BuildContext context) { return RenderCustomPaint( painter: painter, foregroundPainter:

    foregroundPainter, preferredSize: size, isComplex: isComplex, willChange: willChange, ); } CustomPainter? get painter => _painter; CustomPainter? _painter; 29
  16. CustomPainter 30 class Earth extends CustomPainter { @override void paint(Canvas

    canvas, Size size) { // TODO: implement paint } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { // TODO: implement shouldRepaint throw UnimplementedError(); } }
  17. CustomPainter 31 • The paint method is called whenever the

    custom object needs to be repainted. • The shouldRepaint method is called when a new instance of the class is provided, to check if the new instance actually represents different information.
  18. CustomClipper 32 class RoundClipper extends CustomClipper<Path>{ @override getClip(Size size) {

    // TODO: implement getClip throw UnimplementedError(); } @override bool shouldReclip(covariant CustomClipper oldClipper) { // TODO: implement shouldReclip throw UnimplementedError(); } }
  19. • StatelessWidget • Text Widget • RichText • RenderParagraph •

    TextPainter How to render Text Widget Flutter가 Text Widget을 랜더링 하는 방식 35
  20. @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor:

    Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( const Text( 'You have pushed the button this many times:', ), 36 Flutter
  21. class Text extends StatelessWidget { @override Widget build(BuildContext context) {

    Widget result = RichText( textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start, textDirection: textDirection, locale: locale, softWrap: softWrap ?? defaultTextStyle.softWrap, overflow: overflow ?? effectiveTextStyle?.overflow ?? defaultTextStyle.overflow, ///.. text: TextSpan( style: effectiveTextStyle, text: data, children: textSpan != null ? <InlineSpan>[textSpan!] : null, ), ); 37 Flutter
  22. class RichText extends MultiChildRenderObjectWidget { @override RenderParagraph createRenderObject(BuildContext context) {

    assert(textDirection != null || debugCheckHasDirectionality(context)); return RenderParagraph(text, textAlign: textAlign, textDirection: textDirection ?? Directionality.of(context), softWrap: softWrap, overflow: overflow, textScaler: textScaler, maxLines: maxLines, strutStyle: strutStyle, textWidthBasis: textWidthBasis, textHeightBehavior: textHeightBehavior, locale: locale ?? Localizations.maybeLocaleOf(context), registrar: selectionRegistrar, selectionColor: selectionColor, ); } 38 Flutter
  23. /// A render object that displays a paragraph of text.

    class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBox, TextParentData>, RenderInlineChildrenContainerDefaults, RelayoutWhenSystemFontsChangeMixin { final TextPainter _textPainter; _textPainter = TextPainter( text: text, textAlign: textAlign, textDirection: textDirection, textScaler: textScaler == TextScaler.noScaling ? TextScaler.linear(textScaleFactor) : textScaler, maxLines: maxLines, ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null, locale: locale, strutStyle: strutStyle, textWidthBasis: textWidthBasis, textHeightBehavior: textHeightBehavior, ) 39 Flutter
  24. class TextPainter { void paint(Canvas canvas, Offset offset) { final

    _TextPainterLayoutCacheWithOffset? layoutCache = _layoutCache; ///... canvas.drawParagraph(layoutCache.paragraph, offset + layoutCache.paintOffset); } 40 Flutter
  25. abstract class Canvas { factory Canvas(PictureRecorder recorder, [ Rect? cullRect

    ]) = _NativeCanvas; void drawParagraph(Paragraph paragraph, Offset offset); } 41 Flutter
  26. base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas { @override void

    drawParagraph(Paragraph paragraph, Offset offset) { final _NativeParagraph nativeParagraph = paragraph as _NativeParagraph; assert(!nativeParagraph.debugDisposed); assert(_offsetIsValid(offset)); assert(!nativeParagraph._needsLayout); nativeParagraph._paint(this, offset.dx, offset.dy); } 42 Flutter
  27. // Redirecting the paint function in this way solves some

    dependency problems // in the C++ code. If we straighten out the C++ dependencies, we can remove // this indirection. @Native<Void Function(Pointer<Void>, Pointer<Void>, Double, Double)> (symbol: 'Paragraph::paint') external void _paint(_NativeCanvas canvas, double x, double y); 43 Flutter
  28. class SkCanvas; namespace txt { // Interface for text layout

    engines. The current implementation is based on // Skia's SkShaper/SkParagraph text layout module. class Paragraph { // Paints the laid out text onto the supplied DisplayListBuilder at // (x, y) offset from the origin. Only valid after Layout() is called. virtual bool Paint(flutter::DisplayListBuilder* builder, double x, double y) = 0; 44 /engine/third_party/txt/src/txt/paragraph.h engine
  29. // Implementation of Paragraph based on Skia's text layout module.

    class ParagraphSkia : public Paragraph { public: ParagraphSkia(std::unique_ptr<skia::textlayout::Paragraph> paragraph, std::vector<flutter::DlPaint>&& dl_paints, bool impeller_enabled); bool Paint(flutter::DisplayListBuilder* builder, double x, double y) override; private: TextStyle SkiaToTxt(const skia::textlayout::TextStyle& skia); std::unique_ptr<skia::textlayout::Paragraph> paragraph_; 45 /engine/third_party/txt/src/skia/paragraph_skia.h engine
  30. bool ParagraphSkia::Paint(DisplayListBuilder* builder, double x, double y) { DisplayListParagraphPainter painter(builder,

    dl_paints_, impeller_enabled_); paragraph_->paint(&painter, x, y); return true; } 46 /engine/third_party/txt/src/skia/paragraph_skia.cc engine
  31. class DisplayListParagraphPainter : public skt::ParagraphPainter { public: DisplayListParagraphPainter(DisplayListBuilder* builder, const

    std::vector<DlPaint>& dl_paints, bool impeller_enabled) : builder_(builder), dl_paints_(dl_paints), impeller_enabled_(impeller_enabled) {} 47 /engine/third_party/txt/src/skia/paragraph_skia.cc engine
  32. class ParagraphPainter { public: virtual void drawTextBlob(const sk_sp<SkTextBlob>& blob, SkScalar

    x, SkScalar y, const SkPaintOrID& paint) = 0; virtual void drawTextShadow(const sk_sp<SkTextBlob>& blob, SkScalar x, SkScalar y, SkColor color, SkScalar blurSigma) = 0; virtual void drawRect(const SkRect& rect, const SkPaintOrID& paint) = 0; virtual void drawFilledRect(const SkRect& rect, const DecorationStyle& decorStyle) = 0; virtual void drawPath(const SkPath& path, const DecorationStyle& decorStyle) = 0; virtual void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const DecorationStyle& decorStyle) = 0; virtual void clipRect(const SkRect& rect) = 0; virtual void translate(SkScalar dx, SkScalar dy) = 0; 48 skia/modules/skparagraph/include/ParagraphPainter.h Skia
  33. bool ParagraphSkia::Paint(DisplayListBuilder* builder, double x, double y) { DisplayListParagraphPainter painter(builder,

    dl_paints_, impeller_enabled_); paragraph_->paint(&painter, x, y); return true; } 49 /engine/third_party/txt/src/skia/paragraph_skia.cc engine
  34. namespace skia { namespace textlayout { class ParagraphPainter; class Paragraph

    { public: Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts); virtual void paint(SkCanvas* canvas, SkScalar x, SkScalar y) = 0; virtual void paint(ParagraphPainter* painter, SkScalar x, SkScalar y) = 0; 50 /skia/modules/skparagraph/include/Paragraph.h skia
  35. class ParagraphImpl final : public Paragraph { public: void paint(SkCanvas*

    canvas, SkScalar x, SkScalar y) override; void paint(ParagraphPainter* canvas, SkScalar x, SkScalar y) override; } 51 /skia/modules/skparagraph/src/ParagraphImpl.h skia
  36. void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) { CanvasParagraphPainter painter(canvas);

    paint(&painter, x, y); } void ParagraphImpl::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) { for (auto& line : fLines) { line.paint(painter, x, y); } } 52 skia/modules/skparagraph/src/ParagraphImpl.cpp skia
  37. namespace skia { namespace textlayout { class ParagraphImpl; class TextLine

    { public: void paint(ParagraphPainter* painter, SkScalar x, SkScalar y); struct TextBlobRecord { void paint(ParagraphPainter* painter, SkScalar x, SkScalar y); sk_sp<SkTextBlob> fBlob; SkPoint fOffset = SkPoint::Make(0.0f, 0.0f); ParagraphPainter::SkPaintOrID fPaint; SkRect fBounds = SkRect::MakeEmpty(); bool fClippingNeeded = false; SkRect fClipRect = SkRect::MakeEmpty(); // Extra fields only used for the (experimental) visitor 53 /skia/modules/skparagraph/src/TextLine.h skia
  38. void TextLine::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) { for (auto&

    record : fTextBlobCache) { record.paint(painter, x, y); } } void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) { if (fClippingNeeded) { painter->save(); painter->clipRect(fClipRect.makeOffset(x, y)); } painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint); if (fClippingNeeded) { painter->restore(); } } 54 /skia/modules/skparagraph/src/TextLine.cpp skia
  39. class DisplayListParagraphPainter : public skt::ParagraphPainter { void drawTextBlob(const sk_sp<SkTextBlob>& blob,

    SkScalar x, SkScalar y, const SkPaintOrID& paint) override { if (!blob) { return; } size_t paint_id = std::get<PaintID>(paint); FML_DCHECK(paint_id < dl_paints_.size()); #ifdef IMPELLER_SUPPORTS_RENDERING /// code #endif // IMPELLER_SUPPORTS_RENDERING builder_->DrawTextBlob(blob, x, y, dl_paints_[paint_id]); } 55 /Users/jaichang/engine/third_party/txt/src/skia/paragraph_skia.cc skia engine
  40. class DisplayListParagraphPainter : public skt::ParagraphPainter { void drawTextBlob(const sk_sp<SkTextBlob>& blob,

    SkScalar x, SkScalar y, const SkPaintOrID& paint) override { if (!blob) { return; } size_t paint_id = std::get<PaintID>(paint); FML_DCHECK(paint_id < dl_paints_.size()); #ifdef IMPELLER_SUPPORTS_RENDERING /// code #endif // IMPELLER_SUPPORTS_RENDERING builder_->DrawTextBlob(blob, x, y, dl_paints_[paint_id]); } 56 /Users/jaichang/engine/third_party/txt/src/skia/paragraph_skia.cc skia engine
  41. #ifdef IMPELLER_SUPPORTS_RENDERING if (impeller_enabled_) { if (ShouldRenderAsPath(dl_paints_[paint_id])) { auto path

    = skia::textlayout::Paragraph::GetPath(blob.get()); auto transformed = path.makeTransform(SkMatrix::Translate( x + blob->bounds().left(), y + blob->bounds().top())); builder_->DrawPath(transformed, dl_paints_[paint_id]); return; } builder_->DrawTextFrame(impeller::MakeTextFrameFromTextBlobSkia(blob), x, y, dl_paints_[paint_id]); return; } 57 /Users/jaichang/engine/third_party/txt/src/skia/paragraph_skia.cc skia engine
  42. namespace flutter { class DisplayListBuilder final :public virtual DlCanvas, public

    SkRefCnt, virtual DlOpReceiver, DisplayListOpFlags { // |DlCanvas| void DrawTextBlob(const sk_sp<SkTextBlob>& blob, SkScalar x, SkScalar y, const DlPaint& paint) override; void drawTextFrame(const std::shared_ptr<impeller::TextFrame>& text_frame, SkScalar x, SkScalar y) override; void DrawTextFrame(const std::shared_ptr<impeller::TextFrame>& text_frame, SkScalar x, SkScalar y, const DlPaint& paint) override; 58 engine/display_list/dl_builder.h skia engine
  43. void DisplayListBuilder::DrawTextBlob(const sk_sp<SkTextBlob>& blob, SkScalar x, SkScalar y, const DlPaint&

    paint) { SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawTextBlobFlags); drawTextBlob(blob, x, y); } 59 /engine/display_list/dl_builder.cc skia engine
  44. void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y) { DisplayListAttributeFlags

    flags = kDrawTextBlobFlags; OpResult result = PaintResult(current_, flags); if (result == OpResult::kNoEffect) { return; } bool unclipped = AccumulateOpBounds(blob->bounds().makeOffset(x, y), flags); #if defined(OS_FUCHSIA) unclipped = true; #endif // OS_FUCHSIA if (unclipped) { Push<DrawTextBlobOp>(0, 1, blob, x, y); UpdateLayerOpacityCompatibility(false); UpdateLayerResult(result); } } 60 /engine/display_list/dl_builder.cc skia engine
  45. template <typename T, typename... Args> void* DisplayListBuilder::Push(size_t pod, int render_op_inc,

    Args&&... args) { size_t size = SkAlignPtr(sizeof(T) + pod); FML_DCHECK(size < (1 << 24)); if (used_ + size > allocated_) { static_assert(is_power_of_two(DL_BUILDER_PAGE), "This math needs updating for non-pow2."); // Next greater multiple of DL_BUILDER_PAGE. allocated_ = (used_ + size + DL_BUILDER_PAGE) & ~(DL_BUILDER_PAGE - 1); storage_.realloc(allocated_); FML_DCHECK(storage_.get()); memset(storage_.get() + used_, 0, allocated_ - used_); } FML_DCHECK(used_ + size <= allocated_); auto op = reinterpret_cast<T*>(storage_.get() + used_); used_ += size; new (op) T{std::forward<Args>(args)...}; op->type = T::kType; op->size = size; render_op_count_ += render_op_inc; op_index_++; return op + 1; } 61 /engine/display_list/dl_builder.cc skia engine Push<DrawTextBlobOp>(0, 1, blob, x, y);
  46. // 4 byte header + 8 payload bytes + an

    aligned pointer take 24 bytes // (4 unused to align the pointer) struct DrawTextBlobOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawTextBlob; DrawTextBlobOp(const sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y) : x(x), y(y), blob(std::move(blob)) {} const SkScalar x; const SkScalar y; const sk_sp<SkTextBlob> blob; void dispatch(DispatchContext& ctx) const { if (op_needed(ctx)) { ctx.receiver.drawTextBlob(blob, x, y); } } }; 62 /engine/display_list/dl_op_records.h skia engine Push<DrawTextBlobOp>(0, 1, blob, x, y);
  47. class DlOpReceiver { virtual void drawColor(DlColor color, DlBlendMode mode) =

    0; virtual void drawPaint() = 0; virtual void drawLine(const SkPoint& p0, const SkPoint& p1) = 0; virtual void drawRect(const SkRect& rect) = 0; virtual void drawOval(const SkRect& bounds) = 0; virtual void drawCircle(const SkPoint& center, SkScalar radius) = 0; virtual void drawRRect(const SkRRect& rrect) = 0; virtual void drawDRRect(const SkRRect& outer, const SkRRect& inner) = 0; virtual void drawPath(const SkPath& path) = 0; virtual void drawTextBlob(const sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y) = 0; virtual void drawTextFrame( const std::shared_ptr<impeller::TextFrame>& text_frame, SkScalar x, SkScalar y) = 0; 63 /engine/display_list/dl_op_records.h skia engine
  48. // A DisplayList can be read back by implementing the

    DlOpReceiver virtual // methods (with help from some of the classes in the utils file) and // passing an instance to the Dispatch() method, or it can be rendered // to Skia using a DlSkCanvasDispatcher. void DlSkCanvasDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y) { canvas_->drawTextBlob(blob, x, y, paint()); } 64 /engine/display_list/skia/dl_sk_dispatcher.cc skia engine private: SkCanvas* canvas_;
  49. 66 canvas_->drawTextBlob(blob, x, y, paint()); Skia API Graphic Library Backend

    Default OpenGL Vulkan ANGEL etc.. GPU DRM, KMS etc Kernel Graphic Library Display Controller Screen
  50. 67 double x1 = 10; double y1 = 10; double

    x2 = 20; double y2 = 20; x1 = 2*x1 / w - 1; y1 = 2*y1 / h - 1; x2 = 2*x2 / w - 1; y2 = 2*y2 / h - 1; glBegin(GL_LINES); glVertex2f(x1, y1); glVertex2f(x2, y2); glEnd(); OpenGL 로 선을 그리고자 한다면
  51. 68 Text( 'flutter',); RichTex† RenderParagraph TextPainter drawParagraph @Native<Void Function(> (symbol:

    'Paragraph::paint') ParagraphSkia ParagraphImpl DisplayListParagraphPainter DisplayListBuilder DlSkCanvasDispatcher impeller::DlDispatcher