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

RenderObjectより下の世界のFlutter

Fastriver
November 17, 2022
200

 RenderObjectより下の世界のFlutter

FlutterKaigi 2022(https://flutterkaigi.jp/2022/ )で発表した内容になります。

Fastriver

November 17, 2022
Tweet

Transcript

  1. @fastriver_org 自己紹介 - fastriver (@fastriver_org) - 慶應義塾大学理工学部 - Flutterでミニゲームを制作 →

    fastriver.dev 「Flatten me!」 Flutter Puzzle Hack提出作品 「蜿」 三田祭2020出展 「再実装Flutter (1)(2)」 技術書典13にて頒布 Zennでも販売中
  2. @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を組み合わせて作る
  3. @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を組み合わせて作る ?
  4. @fastriver_org Widgetから画面までの流れの概要 1. Widgetツリーの構築 2. Widgetツリー → Renderツリー 3. Renderツリー

    → Layerツリー 4. Layerツリーのラスタライズ 5. 表示 Widget ツリー Render ツリー Layer ツリー 表示 開発者 構築 描画パイプライン Rasterize Layout Paint Engine Framework
  5. @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ツリー
  6. @fastriver_org Renderツリーとは - 描画面では3つのツリーの中で主役 - WidgetツリーはConfigurationの提供 - ElementツリーはLifecycleの管理 - RenderObjectで構成される

    - レイアウトや描画計算などを担当 - 描画パイプラインで行われる - RenderツリーはWidgetツリーに対して少し小さい - RenderObjectWidgetを継承するWidgetはRenderObjectを生成 - InheritedWidgetやStatefulWidgetなどは生成しない https://youtu.be/zmbmrw07qBc より
  7. @fastriver_org 描画パイプライン(一般) ツリー 構築 レイアウト ラスタライズ ペイント 表示 コードをパース して木構造にする

    各ノードの大きさと位置 を決定する レイヤーに分けて canvasに命令を 書き込む 合成してひとつの画 像にする GPUに送信して 画面に表示
  8. @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; } }
  9. @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)); } } }); }
  10. @fastriver_org レイアウト - 各ノードのサイズ調整と位置調整を行う - 一度のツリー走査のみで全てのレイアウトを 決定 - 高速なレイアウトを実現 -

    各ノードを矩形として扱う - 親は子に制約を渡し、子は制約内で 自身のサイズを決める - 親は自身に対する子の相対的な位置(offset)を 決める 制約をつけて サイズ要求 サイズを 返す 子のオフセット を計算
  11. @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(); } } } } }
  12. @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 { ... } }
  13. @fastriver_org ペイント - 各ノードの情報を描画命令に変換 - 射影変換やフィルタが挟まる場合などには レイヤを追加しツリーを構成する - Layerツリー -

    Pictureオブジェクトへ描画命令を順に 書き込み、Engineに渡す - この時点ではまだ画像 (ビット列)に変換しない Picture: 1. (0,0,304,441)に緑色の矩形を描画 2. (134,191)に”Hello,”とテキスト描画 3. (78,210)にsize=30で”FlutterKaigi!”と テキスト描画
  14. @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); } } }
  15. @fastriver_org 構築手順 1. 親レイヤを渡してPaintingContextを作成 ◦ PaintingContextの役割は子レイヤを作ること ◦ 最初のPaintingContextはrootLayerを親とする 2. 子を順に走査し、RenderObject.paint()内でcanvasに命令を書き込む

    3. 全ての子孫ノードの走査が終わった場合 ◦ canvasを閉じてPictureLayerを作り、親レイヤの子に追加する 4. フィルタなどによりレイヤを追加する場合 ◦ 3と同様にPictureLayerを追加する ◦ その後新レイヤを子に追加し、新レイヤを親として PaintingContextを作り繰り返す
  16. @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); } }
  17. @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(); }
  18. @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; }
  19. @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_); }
  20. @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); } }
  21. @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); }
  22. @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 の描画の仕組みを理解する