Slide 1

Slide 1 text

Flutter CustomPaint 박제창(@Dreamwalker) 1 VOL. 1

Slide 2

Slide 2 text

박제창 @Dreamwalker Flutter Seoul GDG Golang Korea 2

Slide 3

Slide 3 text

Agenda 1 2 3 4 Intro What’s Skia CustomPainter & CustomPaint How to render Text Widget 3

Slide 4

Slide 4 text

CustomPaint CustomPainer 사용하시나요? 4

Slide 5

Slide 5 text

그렇다면 우리는 CustomPaint를 왜 ? 사용하게 될까요? 5

Slide 6

Slide 6 text

Rendering 6

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

● 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

Slide 10

Slide 10 text

Material Material 3 is the latest version of Google’s open-source design system. Design and build beautiful, usable products with Material 3. 10

Slide 11

Slide 11 text

Cupertino 11 Beautiful and high-fidelity widgets for current iOS design language.

Slide 12

Slide 12 text

Custom UI 12 https://dribbble.com/shots/22568342-Football-NFT-App https://dribbble.com/shots/22556777-Intermittent-Fasting-Mobile-App-Design

Slide 13

Slide 13 text

Skia The 2D Graphics Library 13

Slide 14

Slide 14 text

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+

Slide 15

Slide 15 text

Skia 15

Slide 16

Slide 16 text

16 SkCanvas

Slide 17

Slide 17 text

void draw(SkCanvas* canvas) { SkPaint p; p.setColor(SK_ColorRED); p.setAntiAlias(true); p.setStyle(SkPaint::kStroke_Style); p.setStrokeWidth(10); canvas->drawLine(20, 20, 100, 100, p); } 17

Slide 18

Slide 18 text

@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

Slide 19

Slide 19 text

Skia는 Flutter 프레임워크에서 어디에 들어가있는걸까요?? 19

Slide 20

Slide 20 text

@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, Double, Double, Double, Double, Handle, Handle)>(symbol: 'Canvas::drawLine') external void _drawLine(double x1, double y1, double x2, double y2, List? paintObjects, ByteData paintData); 20 flutter/engine/lib/ui/painting.dart Flutter / ffi

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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(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

Slide 24

Slide 24 text

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를 사용하여 선을 그리는 과정

Slide 25

Slide 25 text

CustomPainter CustomPaint 25

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

CustomPaint( painter: Earth(), child: Text("Flutter"), ) 27 CustomPaint

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

@override RenderCustomPaint createRenderObject(BuildContext context) { return RenderCustomPaint( painter: painter, foregroundPainter: foregroundPainter, preferredSize: size, isComplex: isComplex, willChange: willChange, ); } CustomPainter? get painter => _painter; CustomPainter? _painter; 29

Slide 30

Slide 30 text

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(); } }

Slide 31

Slide 31 text

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.

Slide 32

Slide 32 text

CustomClipper 32 class RoundClipper extends CustomClipper{ @override getClip(Size size) { // TODO: implement getClip throw UnimplementedError(); } @override bool shouldReclip(covariant CustomClipper oldClipper) { // TODO: implement shouldReclip throw UnimplementedError(); } }

Slide 33

Slide 33 text

CustomPainter & CustomClipper 33 https://pixabay.com/ko/photos/%EA%B3%B5%EC%98%88-%EB%8F%84%EC%98%88-%EB%8F%84%EC%9 8%88%EA%B0%80-%EB%8F%84%EC%9E%90%EA%B8%B0-5276736/ https://pixabay.com/ko/photos/%EC%9E%A5%EB%AF%B8-%EA%BD%83%EB%93%A4-%ED%99%94%EA%B0%80-% ED%8E%98%EC%9D%B8%ED%8A%B8-3411110/

Slide 34

Slide 34 text

How to render Text Widget 34

Slide 35

Slide 35 text

● StatelessWidget ● Text Widget ● RichText ● RenderParagraph ● TextPainter How to render Text Widget Flutter가 Text Widget을 랜더링 하는 방식 35

Slide 36

Slide 36 text

@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

Slide 37

Slide 37 text

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 ? [textSpan!] : null, ), ); 37 Flutter

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

/// A render object that displays a paragraph of text. class RenderParagraph extends RenderBox with ContainerRenderObjectMixin, 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

Slide 40

Slide 40 text

class TextPainter { void paint(Canvas canvas, Offset offset) { final _TextPainterLayoutCacheWithOffset? layoutCache = _layoutCache; ///... canvas.drawParagraph(layoutCache.paragraph, offset + layoutCache.paintOffset); } 40 Flutter

Slide 41

Slide 41 text

abstract class Canvas { factory Canvas(PictureRecorder recorder, [ Rect? cullRect ]) = _NativeCanvas; void drawParagraph(Paragraph paragraph, Offset offset); } 41 Flutter

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

// 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, Pointer, Double, Double)> (symbol: 'Paragraph::paint') external void _paint(_NativeCanvas canvas, double x, double y); 43 Flutter

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

// Implementation of Paragraph based on Skia's text layout module. class ParagraphSkia : public Paragraph { public: ParagraphSkia(std::unique_ptr paragraph, std::vector&& 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 paragraph_; 45 /engine/third_party/txt/src/skia/paragraph_skia.h engine

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

class DisplayListParagraphPainter : public skt::ParagraphPainter { public: DisplayListParagraphPainter(DisplayListBuilder* builder, const std::vector& 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

Slide 48

Slide 48 text

class ParagraphPainter { public: virtual void drawTextBlob(const sk_sp& blob, SkScalar x, SkScalar y, const SkPaintOrID& paint) = 0; virtual void drawTextShadow(const sk_sp& 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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

namespace skia { namespace textlayout { class ParagraphPainter; class Paragraph { public: Paragraph(ParagraphStyle style, sk_sp 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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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 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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

class DisplayListParagraphPainter : public skt::ParagraphPainter { void drawTextBlob(const sk_sp& blob, SkScalar x, SkScalar y, const SkPaintOrID& paint) override { if (!blob) { return; } size_t paint_id = std::get(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

Slide 56

Slide 56 text

class DisplayListParagraphPainter : public skt::ParagraphPainter { void drawTextBlob(const sk_sp& blob, SkScalar x, SkScalar y, const SkPaintOrID& paint) override { if (!blob) { return; } size_t paint_id = std::get(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

Slide 57

Slide 57 text

#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

Slide 58

Slide 58 text

namespace flutter { class DisplayListBuilder final :public virtual DlCanvas, public SkRefCnt, virtual DlOpReceiver, DisplayListOpFlags { // |DlCanvas| void DrawTextBlob(const sk_sp& blob, SkScalar x, SkScalar y, const DlPaint& paint) override; void drawTextFrame(const std::shared_ptr& text_frame, SkScalar x, SkScalar y) override; void DrawTextFrame(const std::shared_ptr& text_frame, SkScalar x, SkScalar y, const DlPaint& paint) override; 58 engine/display_list/dl_builder.h skia engine

Slide 59

Slide 59 text

void DisplayListBuilder::DrawTextBlob(const sk_sp& 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

Slide 60

Slide 60 text

void DisplayListBuilder::drawTextBlob(const sk_sp 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(0, 1, blob, x, y); UpdateLayerOpacityCompatibility(false); UpdateLayerResult(result); } } 60 /engine/display_list/dl_builder.cc skia engine

Slide 61

Slide 61 text

template 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(storage_.get() + used_); used_ += size; new (op) T{std::forward(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(0, 1, blob, x, y);

Slide 62

Slide 62 text

// 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 blob, SkScalar x, SkScalar y) : x(x), y(y), blob(std::move(blob)) {} const SkScalar x; const SkScalar y; const sk_sp 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(0, 1, blob, x, y);

Slide 63

Slide 63 text

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 blob, SkScalar x, SkScalar y) = 0; virtual void drawTextFrame( const std::shared_ptr& text_frame, SkScalar x, SkScalar y) = 0; 63 /engine/display_list/dl_op_records.h skia engine

Slide 64

Slide 64 text

// 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 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_;

Slide 65

Slide 65 text

void DlSkCanvasDispatcher::drawTextBlob(const sk_sp blob, SkScalar x, SkScalar y) { canvas_->drawTextBlob(blob, x, y, paint()); } 65 /engine/display_list/skia/dl_sk_dispatcher.cc skia engine

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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 로 선을 그리고자 한다면

Slide 68

Slide 68 text

68 Text( 'flutter',); RichTex† RenderParagraph TextPainter drawParagraph @Native (symbol: 'Paragraph::paint') ParagraphSkia ParagraphImpl DisplayListParagraphPainter DisplayListBuilder DlSkCanvasDispatcher impeller::DlDispatcher

Slide 69

Slide 69 text

To be Continued 69

Slide 70

Slide 70 text

THANK YOU! v 70 박제창(@Dreamwalker)