Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
RenderObjectより下の世界のFlutter
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Fastriver
November 17, 2022
440
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
RenderObjectより下の世界のFlutter
FlutterKaigi 2022(
https://flutterkaigi.jp/2022/
)で発表した内容になります。
Fastriver
November 17, 2022
More Decks by Fastriver
See All by Fastriver
Flutter WebでもJamstackしたい
fastriver
1
2.4k
Featured
See All Featured
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.9k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
420
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
370
How to Talk to Developers About Accessibility
jct
2
540
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
830
Paper Plane (Part 1)
katiecoart
PRO
1
11k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.1k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3k
The Language of Interfaces
destraynor
162
27k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
My Coaching Mixtape
mlcsv
0
310
Transcript
@fastriver_org RenderObjectより 下の世界のFlutter @fastriver_org
@fastriver_org 自己紹介 - fastriver (@fastriver_org) - 慶應義塾大学理工学部 - Flutterでミニゲームを制作 →
fastriver.dev 「Flatten me!」 Flutter Puzzle Hack提出作品 「蜿」 三田祭2020出展 「再実装Flutter (1)(2)」 技術書典13にて頒布 Zennでも販売中
@fastriver_org セッションの方針 - FlutterのRenderObject以下のコードを読み解き、主に描画に関する下回り部分が どのように作られているのかを考える - Flutterでアプリを作るための方法論などはあまり気にしない スライド中のソースコードについて - 視認性から一部を省略している
- 特にEngineは変更が速いので最新のコードとは異なる可能性がある
@fastriver_org Flutterによる画面の構築 void main() { runApp(ColoredBox( color: Colors.green, child: Center(
child: Column( mainAxisSize: MainAxisSize.min, children: [ RichText( text: const TextSpan(text: "Hello,"), textDirection: TextDirection.ltr, ), RichText( text: const TextSpan( text: "FlutterKaigi!", style: TextStyle(fontSize: 30)), textDirection: TextDirection.ltr, ), ], ), ), )); } Widgetを組み合わせて作る
@fastriver_org Flutterによる画面の構築 void main() { runApp(ColoredBox( color: Colors.green, child: Center(
child: Column( mainAxisSize: MainAxisSize.min, children: [ RichText( text: const TextSpan(text: "Hello,"), textDirection: TextDirection.ltr, ), RichText( text: const TextSpan( text: "FlutterKaigi!", style: TextStyle(fontSize: 30)), textDirection: TextDirection.ltr, ), ], ), ), )); } Widgetを組み合わせて作る ?
@fastriver_org Flutterの全体像 - Framework/Engine/Embedderの 3層からなる - Framework内でWidgetを並べ、 Engineで画像にして表示 - 画面への書き込みにはSkiaを利用
- 将来的には変更されるかも https://docs.flutter.dev/resources/architectural-overview https://skia.org/
@fastriver_org Widgetから画面までの流れの概要 1. Widgetツリーの構築 2. Widgetツリー → Renderツリー 3. Renderツリー
→ Layerツリー 4. Layerツリーのラスタライズ 5. 表示 Widget ツリー Render ツリー Layer ツリー 表示 開発者 構築 描画パイプライン Rasterize Layout Paint Engine Framework
@fastriver_org Widgetツリー → Renderツリー ColoredBox color=green Center Column RichText RichText
size=30 RenderObjectTo WidgetAdapter SingleChildElement MultiChildElement MultiChild Element MultiChild Element RenderObjectTo WidgetElement SingleChildElement _RenderColoredBox color=green RenderPositionedBox RenderFlex Render Paragraph Render Paragraph size=30 RenderView Widgetツリー Elementツリー Renderツリー
@fastriver_org Renderツリーとは - 描画面では3つのツリーの中で主役 - WidgetツリーはConfigurationの提供 - ElementツリーはLifecycleの管理 - RenderObjectで構成される
- レイアウトや描画計算などを担当 - 描画パイプラインで行われる - RenderツリーはWidgetツリーに対して少し小さい - RenderObjectWidgetを継承するWidgetはRenderObjectを生成 - InheritedWidgetやStatefulWidgetなどは生成しない https://youtu.be/zmbmrw07qBc より
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org 描画パイプライン(一般) ツリー 構築 レイアウト ラスタライズ ペイント 表示 コードをパース して木構造にする
各ノードの大きさと位置 を決定する レイヤーに分けて canvasに命令を 書き込む 合成してひとつの画 像にする GPUに送信して 画面に表示
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org Flutterでの具体的な流れ 1. Widgetツリーを書く 2. Renderツリーの構築 3. レイアウト 4. ペイント
5. Layerツリーの構築 6. ラスタライズ 7. 画面への書き出し
@fastriver_org Flutter Frameworkでの描画パイプライン - PipelineOwnerが担当 - RendererBinding.drawFrame()で実行 void drawFrame() {
assert(renderView != null); pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint(); if (sendFramesToEngine) { renderView.compositeFrame(); pipelineOwner.flushSemantics(); _firstFrameSent = true; } }
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org runApp()から描画パイプラインまで runApp() …→WidgetsBinding.attachRootWidget() →SchedulerBinding.ensureVisualUpdate() …→window.scheduleFrame() …→Engine::ScheduleFrame() →Animator::RequestFrame() →Animator::AwaitVSync() VSyncを待つ
void Animator::AwaitVSync() { waiter_->AsyncWaitForVsync( [self = weak_factory_.GetWeakPtr()]( std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) { if (self) { if (self->CanReuseLastLayerTree()) { self->DrawLastLayerTree(std::move(frame_timings_recorder)); } else { self->BeginFrame(std::move(frame_timings_recorder)); } } }); }
@fastriver_org runApp()から描画パイプラインまで Animator::AwaitVSync()でVSyncを待つ →Animator::BeginFrame() …→Engine::BeginFrame() …→PlatformConfiguration::BeginFrame() …→SchedulerBinding.handleDrawFrame() …→RendererBinding.drawFrame() 描画パイプラインを実行 Framework
Engine 画面を 書き換えたい schedule() VSync beginFrame() drawFrame() 描画パイプライン 表示 ensure VisualUpdate()
@fastriver_org setState()から描画パイプラインまで State.setState() →Element.markNeedsBuild() →BuildOwner.scheduleBuildFor() →BuildOwner.onBuildScheduled() →WidgetsBinding._handleBuildScheduled() →SchedulerBinding.ensureVisualUpdate() …VSyncを待つ... …→RendererBinding.drawFrame()
描画パイプラインを実行 Framework Engine 画面を 書き換えたい schedule() VSync beginFrame() drawFrame() 描画パイプライン 表示 ensure VisualUpdate()
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org レイアウト - 各ノードのサイズ調整と位置調整を行う - 一度のツリー走査のみで全てのレイアウトを 決定 - 高速なレイアウトを実現 -
各ノードを矩形として扱う - 親は子に制約を渡し、子は制約内で 自身のサイズを決める - 親は自身に対する子の相対的な位置(offset)を 決める 制約をつけて サイズ要求 サイズを 返す 子のオフセット を計算
@fastriver_org PipelineOwner.flushLayout() - 登録されたノードの レイアウト処理を順に呼び出す - 起動時にRenderViewを登録 - Renderツリーの根ノード RenderView._layoutWithoutResize()
→RenderView.performLayout() void flushLayout() { try { while (_nodesNeedingLayout.isNotEmpty) { final List<RenderObject> dirtyNodes = _nodesNeedingLayout; _nodesNeedingLayout = <RenderObject>[]; dirtyNodes.sort((RenderObject a, RenderObject b) => a.depth - b.depth); for (int i = 0; i < dirtyNodes.length; i++) { final RenderObject node = dirtyNodes[i]; if (node._needsLayout && node.owner == this) { node._layoutWithoutResize(); } } } } }
@fastriver_org ツリー全体のレイアウト - RenderObject.performLayout()内で各個のレイアウトを決定 - 内部で子のlayout()を制約付きで呼び出し、ツリーの先端まで実行 - 自身のsizeと必要なら子のoffsetを決める - 子のoffsetは子のRenderObject.parentDataに格納
_layoutWithoutR esize() layout() performLayout() layout() flushLayout() performLayout() size parentData 制約 制約 親 親 子 子
@fastriver_org サイズの決定とレイアウト制約 - 子へのサイズ制約としてBoxConstraintsを渡 す - BoxConstraintsが持つのは 縦横それぞれの最大最小サイズの情報 - 子は制約内で大きさを決定
- 結果はsizeプロパティに格納 - 制約が解決できなければエラー max min
@fastriver_org 子のオフセットの決定 - 自身のサイズと子のサイズの決定後、それらを使って子の位置を計算 - 親の左上の位置からのずれで表現 - 結果は子のRenderObject.parentDataに格納 Centerの場合 Columnの場合
@fastriver_org 例: RenderPositionedBox PositionedやAlignのRenderObject 1. 子のlayout()を呼ぶ 2. 自身のサイズを子のサイズと 自身への制約から決定 3.
子のoffsetをalignmentと自身の サイズ、子のサイズから決定 void performLayout() { if (child != null) { child!.layout(constraints.loosen(), parentUsesSize: true); size = constraints.constrain(child!.size); final BoxParentData childParentData = child!.parentData! as BoxParentData; childParentData.offset = alignment!.alongOffset(size - child!.size as Offset); } else { ... } }
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org ペイント - 各ノードの情報を描画命令に変換 - 射影変換やフィルタが挟まる場合などには レイヤを追加しツリーを構成する - Layerツリー -
Pictureオブジェクトへ描画命令を順に 書き込み、Engineに渡す - この時点ではまだ画像 (ビット列)に変換しない Picture: 1. (0,0,304,441)に緑色の矩形を描画 2. (134,191)に”Hello,”とテキスト描画 3. (78,210)にsize=30で”FlutterKaigi!”と テキスト描画
@fastriver_org PipelineOwner.flushPaint() - 登録されたノードを順に処理 - PaintingContext.repaintComposited Child()の引数に渡す - 起動時にRenderViewを登録 -
Renderツリーの根ノード PaintingContextがLayerツリーの構築を担 当 void flushPaint() { try { final List<RenderObject> dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = <RenderObject>[]; for (final RenderObject node in dirtyNodes..sort( (RenderObject a, RenderObject b) => b.depth - a.depth)) { PaintingContext.repaintCompositedChild(node); } } }
@fastriver_org PaintingContextによるLayerツリー構築 - レイアウトと異なりPaintingContext主体でツリーを走査 - PaintingContextは引数で渡される - RenderObject.paint()内でcanvasに命令を書き込む - PaintingContext.paintChild()に子を渡して続ける
PaintingContext.paintChild() _paintWithContext() paint() _paintWithContext() PaintingContext .repaintCompositedChild() 親 子 親 子
@fastriver_org 構築手順 1. 親レイヤを渡してPaintingContextを作成 ◦ PaintingContextの役割は子レイヤを作ること ◦ 最初のPaintingContextはrootLayerを親とする 2. 子を順に走査し、RenderObject.paint()内でcanvasに命令を書き込む
3. 全ての子孫ノードの走査が終わった場合 ◦ canvasを閉じてPictureLayerを作り、親レイヤの子に追加する 4. フィルタなどによりレイヤを追加する場合 ◦ 3と同様にPictureLayerを追加する ◦ その後新レイヤを子に追加し、新レイヤを親として PaintingContextを作り繰り返す
@fastriver_org 構築手順(単一のcanvas) TransformLayer PictureLayer root TransformLayer(root) PaintingContext canvas Renderツリー Layerツリー
@fastriver_org 構築手順(複数のcanvas) TransformLayer PictureLayer ClipRect root TransformLayer(root) PaintingContext canvas Renderツリー
Layerツリー ClipRectLayer PaintingContext canvas ClipRectLayer PictureLayer
@fastriver_org 例: _RenderColoredBox Container内部などで使われているColoredBoxのRenderObject 矩形を指定の色で塗る - sizeとoffsetを使い自身の専有部分を 矩形で塗りつぶし - 子を持つ場合は
PaintingContext.paintChild()に子と 現在のoffsetを渡し、子のペイントを 行う void paint(PaintingContext context, Offset offset) { if (size > Size.zero) { context.canvas.drawRect( offset & size, Paint()..color = color); } if (child != null) { context.paintChild(child!, offset); } }
@fastriver_org 例: RenderClipRect ClipRectのRenderObject - Layerを挿入するために PaintingContext.pushClipRect()を呼ぶ - 内部ではpushLayer()が呼ばれる -
現在のcanvasを止めてレイヤを子に追加 - 新しいcontextを作って続きのpaintを実行 void paint(PaintingContext context, Offset offset) { if (child != null) { if (clipBehavior != Clip.none) { _updateClip(); layer = context.pushClipRect( needsCompositing, offset, _clip!, super.paint, clipBehavior: clipBehavior, oldLayer: layer as ClipRectLayer?, ); } } } void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect? childPaintBounds }) { stopRecordingIfNeeded(); appendLayer(childLayer); final PaintingContext childContext = createChildContext(childLayer, childPaintBounds); painter(childContext, offset); childContext.stopRecordingIfNeeded(); }
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org LayerツリーをEngineへ送信 - RenderView.compositeFrame() - DartのLayerからC++のLayerをSceneBuilderを使って複製 - C++のLayerツリーの参照を持つSceneオブジェクトを作成 void compositeFrame()
{ try { final ui.SceneBuilder builder = ui.SceneBuilder(); final ui.Scene scene = layer!.buildScene(builder); _window.render(scene); scene.dispose(); } }
@fastriver_org SceneBuilderによるツリーの複製 TransformLayer PictureLayer ClipRectLayer PictureLayer TransformLayer DisplayListLayer ClipRectLayer DisplayListLayer
ContainerLayer Framework(Dart) Engine(C++)
@fastriver_org 描画パイプライン詳細 • 描画パイプライン(一般) • Flutterの描画パイプライン ◦ 描画パイプラインが呼ばれるタイミング ◦ レイアウト
◦ ペイント ◦ Engineへの送信 ◦ ラスタライズ ◦ 画面への描画
@fastriver_org ラスタライズ RenderView.compositeFrame()内で_window.render()が呼ばれている →FlutterView.render() …→Engine::Render() …→Rasterizer::DrawToSurface() …→ScopedFrame::Raster() - Preroll()とPaint()が順に呼ばれる -
Layerツリーを2回走査する RasterStatus CompositorContext::ScopedFrame::Raster( flutter::LayerTree& layer_tree) { bool root_needs_readback = layer_tree.Preroll(*this, ignore_raster_cache); //... layer_tree.Paint(*this, ignore_raster_cache); //... return RasterStatus::kSuccess; }
@fastriver_org Preroll() - 描画の準備をするためのパス - パス内で行うこと: - 描画範囲の計算(paintBounds) - PlatformViewの変形用の
射影変換行列の計算 - canvasのキャッシュ など void DisplayListLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { DisplayList* disp_list = display_list(); SkMatrix child_matrix = matrix; if (context->raster_cache) { child_matrix = RasterCacheUtil::GetIntegralTransCTM(child_matrix); } AutoCache cache = AutoCache(display_list_raster_cache_item_.get(), context, child_matrix); if (disp_list->can_apply_group_opacity()) { context->subtree_can_inherit_opacity = true; } set_paint_bounds(bounds_); }
@fastriver_org Paint() DisplayListLayerの持つ描画情報を画面用のcanvasに書き込むパス void DisplayListLayer::Paint(PaintContext& context) const { SkAutoCanvasRestore save(context.leaf_nodes_canvas,
true); context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); if (context.raster_cache) { context.leaf_nodes_canvas->setMatrix(RasterCacheUtil::GetIntegralTransCTM( context.leaf_nodes_canvas->getTotalMatrix())); } //... if (context.leaf_nodes_builder) { //... } else { display_list()->RenderTo(context.leaf_nodes_canvas, context.inherited_opacity); } }
@fastriver_org Layerによるフィルタの適用 ContainerLayer DisplayListLayer TransformLayer DisplayListLayer Hello Flutter 中央で反転 Hello
Flutter void TransformLayer::Paint(PaintContext& context) const { SkAutoCanvasRestore save(context.internal_nodes_canvas, true); context.internal_nodes_canvas->concat(transform_); PaintChildren(context); }
@fastriver_org まとめ - Flutterは、WidgetツリーからRenderツリーを構成し、それを入力として 描画パイプラインを動かすことで画面の表示を行う - 描画パイプラインはVSyncに合わせて実行される - Framework側でレイアウトとペイント、Engine側でラスタライズ Widget
ツリー Render ツリー Layer ツリー 表示 開発者 構築 描画パイプライン Rasterize Layout Paint Engine Framework
@fastriver_org 参考 - GitHub - flutter/flutter - GitHub - flutter/engine
- Flutter architectural overview - Rendering on screen of fluent rendering - 前端知识 - Exploration of the Flutter Rendering Mechanism from Architecture to Source Code - Alibaba Cloud Community - Flutter Engineについて解説 - C++のリファレンスにして勉強してみよう - Qiita - Flutterレンダリングパイプライン入門 | CyberAgent Developers Blog - Flutter の描画の仕組みを理解する