Open35

Flutter内部コードを探る

fastriverfastriver

Flutterの実行ファイルを起動したとき何が起こるのか

Flutter for Windows のコードを読む

用語

  • {app}: Flutterアプリのルートディレクトリ
  • {flutter}: flutter/flutterのルートディレクトリ
  • {engine}: flutter/engineのルートディレクトリ
fastriverfastriver

{app}/windows/runner/main.cppにmain関数であるwWinMain()が存在

fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
  • FlutterWindowsView
    • WindowBindingHandler::SetView()にthisを入れる
      • これでWindowBindingHandlerから来たイベントをFlutterWindowsViewのメソッドで受け取れるようになる
fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
fastriverfastriver
  • RendererBinding.drawFrame()
    • PipelineOwner.flushLayout()呼ぶ
      • 再計算の必要があるRenderObjectのレイアウトをすべて更新する
      • RenderObject.sizeに大きさ、RenderObject.parentData.offsetにオフセットを書き込む
      • _nodesNeedingLayoutにはrenderViewがいる
      • renderView._layoutWithoutResize()呼ぶ -> RenderObject._layoutWithoutResize()
        • RenderView.performLayout()呼ぶ
          • childのRenderObject.layout({画面サイズ}.tight)呼ぶ
        • RenderObject.markNeedsSemanticsUpdate()呼ぶ
        • RenderObject.markNeedsPaint()呼ぶ
    • PipelineOwner.flushCompositingBits()呼ぶ
      • RenderツリーのRenderObject.needsCompositingを更新
    • PipelineOwner.flushPaint()呼ぶ
      • RenderObject._layerHandle.layerにpaint
      • 描画結果はlayer.picture
      • _nodesNeedingPaintごとに処理する
      • renderView_nodesNeedingPaint内に存在
      • renderView._layerHandle.layer!.attachedはtrue
        • PaintingContext.repaintCompositedChild(renderView)呼ぶ
    • RenderView.compositeFrame()呼ぶ
      • builder = ui.SceneBuilder()でSceneBuilderを作成
      • scene = RenderView.layer.buildScene(builder)でsceneを作成
      • _window.render(scene)でsceneをengineに送る
    • PipelineOwner.flushSemantics()呼ぶ
fastriverfastriver

コメントメモ:

  • Element.updateChild(): 子を新しい設定で更新するメソッド。このメソッドはWidgetシステムのコアである。
fastriverfastriver

下の場合を考えよう。

runApp(Align(
  child: RichText(
    text: const TextSpan(
        children: [TextSpan(text: "Hello, "), TextSpan(text: "world!")]),
    textDirection: TextDirection.ltr,
  ),
));

ここまでで以下のツリーが作成されている(parent -> child)

  • RenderObjectToWidgetAdapter
    • Align
      • RichText
  • RenderObjectToWidgetElement
    • SingleChildRenderObjectElement
      • MultiChildRenderObjectElement
  • RenderView
    • RenderPositionedBox
      • RenderParagraph
fastriverfastriver

続き

  • RenderView.performLayout()
    • RenderPositionedBox.layout({画面サイズ}.tight) -> RenderObject.layout
      • RenderObject._relayoutBoundaryを指定(自分)
      • RenderPositionedBox.sizedByParentはFalseのはず
      • RenderPositionedBox.performLayout()呼ぶ
        • child.layout({画面サイズ}.loosen, parentUsesSize: true)呼ぶ -> RenderObject.layout
          • RenderObject._relayoutBoundaryは親と同じやつ
          • visitChildren(_cleanChildRelayoutBoundary);も呼ぶんじゃないかなぁ
          • RenderBox.performResize()呼ぶ
            • RenderParagraph.computeDryLayout(constraints)呼ぶ
              • RenderParagraph._layoutChildren(constraints, dry: true)呼ぶ
                • childrenはないので空の配列が返る?
              • TextPainter.setPlaceholderDimensions([])呼ぶ
                • 空なのでなにもしない
              • RenderParagraph._layoutText()に幅の制約を与えて呼ぶ
                • TextPainter.layout()に幅の制約を与えて呼ぶ
          • RenderParagraph.performLayout()呼ぶ
            • RenderParagraph._layoutChildren(constraints)呼ぶ
            • RenderParagraph._layoutText()に幅の制約を与えて呼ぶ
            • _textPainter.sizeをsizeとする
            • オーバーフローしてたらその処理
          • RenderObject.markNeedsSemanticsUpdate()呼ぶ
          • RenderObject.markNeedsPaint()呼ぶ
        • shrinkWrapXはfalseなので縦横double.infinityを要求する→親の最大大きさになる
        • RenderAligningShiftedBox.alignChild()呼ぶ
          • RenderAligningShiftedBox._resolve()呼ぶ
            • alignmentとtextDirectionを解決する(Alignmentだとそのまま)
          • RenderPositionedBoxと子の左上の距離(offset)を計算してchild.parentData.offsetにセット
      • RenderObject.markNeedsSemanticsUpdate()呼ぶ
      • RenderObject.markNeedsPaint()呼ぶ
        • PipelineOwner.requestVisualUpdate()呼ぶ
          • SchedulerBinding.ensureVisualUpdate()呼ぶ
fastriverfastriver
  • PaintingContext.repaintCompositedChild(renderView)
    • OffsetLayer? childLayer = renderView._layerHandle.layer == rootLayer
    • childLayer.removeAllChildren()呼ぶ
    • childContext = PaintingContext(childLayer, renderView.paintBounds)
      • paintBoundsはoffsetとsizeを合わせた矩形情報
    • renderView._paintWithContext(childContext, Offset.zero)呼ぶ
      • renderView.paint(childContext, Offset.zero)呼ぶ
        • childContext.paintChild(renderView.child, Offset.zero)呼ぶ
          • RenderPositionedBox._paintWithContext(childContext, Offset.zero)呼ぶ
            • RenderPositionedBox(=ShiftedBox).paint(childContext, Offset.zero)呼ぶ
              • childContext.paintChild(RenderPositionedBox.child, RenderPositionedBox.child.parentData.offset + Offset.zero)呼ぶ
                • RenderParagraph._paintWithContext(childContext, offset)呼ぶ
                  • RenderParagraph.paint(childContext, offset)呼ぶ
                    • RenderParagraph._layoutTextWithConstraints()呼ぶ
                      • RenderParagraph._layoutText()に幅の制約を与えて呼ぶ
                        • サイズ更新
                    • _textPainter.paint(childContext.canvas, offset)でcanvasに描画
                    • canvasを最初に使うときに_currentLayer=PictureLayer()を生成し、childLayerの子として追加
    • childContext.stopRecordingIfNeeded()呼ぶ
      • _currentLayer.pictureに描画結果を格納

ここまででLayerツリーは以下のようになっている

  • TransformLayer
    • dpiで変形する情報を持つ
    • PictureLayer
      • rootの大きさで文字がかかれたpictureを持つ
fastriverfastriver
  • RenderView.isRepaintBoundary == true
  • RenderPositionedBox.isRepaintBoundary == false
  • RenderParagraph.isRepaintBoundary == false
fastriverfastriver
  • RenderView.layer.buildScene(builder)
    • {engine}/SceneBuilderがスタックを使ってlayerツリーをengineに移す
    • updateSubtreeNeedsAddToScene()呼ぶ
      • 自身とサブツリーの`_needsAddToSceneを更新する
      • 自身ないしは子の_needsAddToSceneがtrueならtrue
      • TransformLayerPictureLayerは共にtrue
    • TransformLayer.addToScene(builder)呼ぶ
      • builder.pushTransform()でtransform情報をengineに積む
      • ContainerLayer.addChildrenToScene()呼ぶ
        • 子ごとにchild.addToScene(builder)呼ぶ
        • PictureLayer.addToScene()
          • builder.addPicture(picture)で描画をengineに追加
      • builder.pop()呼ぶ
    • return builder.build()
      • Sceneを作成
      • Sceneはlayerツリーの参照を持ってるだけだと思う

https://github.com/flutter/engine/blob/fa06eb819ff58affafcaa667cb5ed8b18f0f8dbe/lib/ui/compositing/scene_builder.cc
https://github.com/flutter/engine/blob/fa06eb819ff58affafcaa667cb5ed8b18f0f8dbe/lib/ui/compositing/scene.cc

ここまででSceneが持っているツリー

  • ContainerLayer (RootLayerとして追加された)
    • TransformLayer
      • PictureLayer
fastriverfastriver
fastriverfastriver
  • LayerTreeはあまり再利用しない
    • _needsPaintでマークされた場合子は再生成
      • isRepaintBoundary == trueかつ_needsPaint == falseなら、layerHandleの参照から再利用する
      • RenderTransformとかもpaint()内で再利用してるわね
  • SkPictureはRasterCacheにてPicture単位でキャッシュされる
    • 再利用できると判断されればキャッシュがDrawされる
    • RepaintBoundaryの説明にもそれっぽいことが書いてある

RepaintBoundary has the further side-effect of possibly hinting to the engine that it should further optimize animation performance if the render subtree behind the RepaintBoundary is sufficiently complex and is static while the surrounding tree changes frequently.

fastriverfastriver
  • CacheはRasterCacheKey::Mapというstd::unordered_mapに保存される
  • キーはRasterCacheKeyで、id・type・matrixを持つ
  • 値はRasterCache::Entryで、このフレームで使われたか・アクセス回数・RasterCacheResultを持つ
  • RasterCacheResultはSkImageとSkRectを持つ
  • RasterCache::Prepare()を呼んだときにCacheを新規作成
    • PictureLayerだとPreroll内で呼んでいる
fastriverfastriver
  • RenderObject._relayoutBoundary
    • https://juejin.cn/post/6981368741653119013
    • ツリー中で再レイアウトする部分を決定
    • _relayoutBoundary != this の場合親を探索
    • _relayoutBoundary == this の場合自身で再レイアウトの伝搬を停止
      • 親はrelayoutされない
fastriverfastriver
  • sizedByParent == trueの場合
    • layout()内でperformResize()が呼ばれ、ここでサイズを決定する
    • performLayout()内ではサイズを変更してはいけない
  • sizedByParent == falseの場合
    • performResize()は呼ばれない
    • performLayout()内でサイズを適宜変更する
  • performResize()はRenderBoxではcomputeDryLayout()で更新される
fastriverfastriver

HitTest詳細

  • hitTestではRenderObjectの左上からのローカルな座標が渡される
    • 子のhitTestを呼ぶときにOffset分を引いて流している
  • pointerの位置から見た場合、Offsetの適用はその分を引くこと
    • HitTestResultのStackには-offsetを積む
  • RenderTransformなどはそのままtransformをStackに積む
  • Stackに積まれた変換行列はそこまでの累積をHitTestEntryの追加時に渡す
    • 根を右に置いて左から掛け合わせることで、順に変換したのと同等の変換行列になっている
  • EntryのtransformはGestureBinding.dispatchEventで使われる
fastriverfastriver
  • abstract class PointerEvent with Diagnosticable
  • mixin _PointerEventDescription on PointerEvent
  • abstract class _AbstractPointerEvent implements PointerEvent
  • abstract class _TransformedPointerEvent extends _AbstractPointerEvent with Diagnosticable, _PointerEventDescription
  • mixin _CopyPointerXxxEvent on PointerEvent
  • class PointerXxxEvent extends PointerEvent with _PointerEventDescription, _CopyPainterXxxEvent
  • class _TransformedPointerXxxEvent extends _TransformedPointerEvent with _CopyPointerXxxEvent implements PointerXxxEvent

fastriverfastriver

Layerについて

  • RasterではPaint前にPrerollを呼ぶ
    • サイズ確定のため?
    • PictureLayer -> 持っているpictureからrectを計算
    • MutatorsStackはPlatformViewの変形用
    • TransformLayer -> context.cull_rectに逆行列を埋めて子のrectを計算
      • 子のrectを変換して自分のrectとする
    • read_backはよくわからないけどフラグでオン・オフできるからなくてもよいのかな
fastriverfastriver
  • GLFWKeyCallback()
    • TextInputPlugin::KeyboardHook()
    • KeyEventHandler::KeyboardHook()
      • BasicMessageChannel::Send()
        • BinaryMessenger::Send()
          • BinaryMessengerはPluginRegistrarのBinaryMessengerImpl
            • FlutterDesktopMessengerSend()
              • FlutterDesktopMessengerSendWithReply()
                • FlutterEngineSendPlatformMessage()
                  • EmbedderEngine::SendPlatformMessage()
                    • PlatformView::DispatchPlatformMessage()
                      • Shell::OnPlatformViewDispatchPlatformMessage()
                        • (in UI TaskRunner) Engine::DispatchPlatformMessage()
                          • RuntimeController::DispatchPlatformMessage()
                            • PlatformConfiguration::DispatchPlatformMessage()
                              • (hooks.dart) _dispatchPlatformMessage()
                                • PlatformDispatcher._dispatchPlatformMessage()
                                  • ChannelBuffers.push()
                                    • _Channel.push()
                                      • "flutter/keyevent"のcallbackが設定されていれば呼び出し
                                        • 後述
                                      • 設定されていなければqueueに保存
                                        • これは設定されたときにdrain()で全て呼び出される
  • ServicesBinding.initInstances()
    • ServicesBinding._initKeyboard()
      • window.onKeyData = _keyEventManager.handleKeyData
        • PlatformDispatcher.setOnKeyData
          • ChannelBuffers.setListener()
            • _Channel.setListener()
              • "flutter/keyData"にcallbackを登録
      • SystemChannels.keyEvent.setMessageHandlerにKeyEventManager.handleRawKeyMessageを指定
        • _DefaultBinaryMessenger.setMessageHandler()
          • ui.channelBuffers.setListener()
            • "flutter/keyEvent"にcallbackを追加
  • つまりcallbackはKeyEventManager.handleRawKeyMessage()
fastriverfastriver

needsAddToSceneの調査

  • trueになる時
    • Layerの生成時
    • markNeedsAddToScene()が呼ばれた時
    • updateSubtreeNeedsAddToScene()でalwaysNeedsAddToSceneがtrue
    • updateSubtreeNeddsAddToScene()で子のneedsAddToSceneがtrue
  • falseになる時
    • ContainerLayer.buildScene()が呼ばれた後
    • Layer._addToSceneWithRetainedRendering()が呼ばれた後
      • ContainerLayer.addChildrenToScene()から呼ばれる
        • これは各addToScene()から呼ばれる
      • どちらもaddToScene()が呼ばれた後である
  • markNeedsAddToScene()の呼ばれるタイミング
    • engineLayerが設定された時
    • layerのプロパティが変更された時
  • needsAddToSceneの使われるタイミング
    • Layer._addToSceneWithRetainedRendering()のみ
      • falseかつengineLayerがある場合addRetainedを呼び処理を省略
      • ContainerLayer.addChildrenToScene()からのみ呼ばれる
fastriverfastriver

SceneBuilderのoldLayerの役割

  • pushXxxを呼ぶとき一緒にoldLayerを渡すことができる
  • oldLayerはインスタンスが使い回されるわけではない
    • 毎回新たにLayerが生成される
  • 新しいLayerはoldLayerからoriginal_layer_idを継承する
  • 同じidを持つかどうかはLayer.IsReplacing()で確認される
    • ContainerLayer::DiffChildren()で利用
      • ContainerLayer::Diff()で利用
        • ここで使い回せるかどうかを判定している?
  • addRetained()を使うとインスタンスを使い回すことができる
    • needsAddToSceneがfalseの場合