## drawFrame
~~~
void drawFrame() {
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
~~~
`pipelineOwner.flushLayout()`:調用完成以后渲染流水線就進入了繪制(paint)階段.
`pipelineOwner.flushCompositingBits()`:更新render tree 中`RenderObject`的`_needsCompositing`標志位的。
`pipelineOwner.flushPaint()`: 完成繪制工作
`renderView.flushPaint()`: 把整個layer tree生成`scene`送到engine去顯示。
## flushCompositingBits
`flushCompositingBits()`源碼如下:
~~~
void flushCompositingBits() {
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
}
~~~
回頭看flushCompositingBits函數,主要做的事情就是:
1. 把列表`_nodesNeedingCompositingBitsUpdate`按照節點在樹中的深度排序。
2. 遍歷調用`node._updateCompositingBits()`
### _updateCompositingBits
~~~
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return;
final bool oldNeedsCompositing = _needsCompositing;
_needsCompositing = false;
visitChildren((RenderObject child) {
child._updateCompositingBits();
if (child.needsCompositing)
_needsCompositing = true;
});
if (isRepaintBoundary || alwaysNeedsCompositing)
_needsCompositing = true;
if (oldNeedsCompositing != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
}
~~~
_needsCompositing設成true兩種個情況:
1. 從當前節點往下找,如果某個子節點`_needsCompositing`為`true`
2. 當前子節點`isRepaintBoundary`為`true`或`alwaysNeedsCompositing`為`true`
如果`_needsCompositing`發生了變化,那么會調用`markNeedsPaint()`通知渲染流水線本`RenderObject`需要重繪了
### `RenderObject`的標志位
* `bool _needsCompositing`:標志自身或者某個孩子節點有合成層(compositing layer)。如果當前節點需要合成,那么所有祖先節點也都需要合成。
* `bool _needsCompositingBitsUpdate`:標志當前節點是否需要更新`_needsCompositing`。這個標志位由下方的`markNeedsCompositingBitsUpdate()`函數設置。
* `bool get isRepaintBoundary => false;`:標志當前節點是否與父節點分開來重繪。當這個標志位為`true`的時候,父節點重繪的時候子節點不一定也需要重繪,同樣的,當自身重繪的時候父節點不一定需要重繪。此標志位為`true`的`RenderObject`有render tree的根節點`RenderView`,有我們熟悉的`RenderRepaintBoundary`,`TextureBox`等。
* `bool get alwaysNeedsCompositing => false;`:標志當前節點是否總是需要合成。這個標志位為`true`的話意味著當前節點繪制的時候總是會新開合成層(composited layer)。例如`TextureBox`, 以及我們熟悉的顯示運行時性能的`RenderPerformanceOverlay`等。
### markNeedsCompositingBitsUpdate
在渲染流水線的構建階段,有些情況下render tree里的節點需要重新更新`_needsCompositing`,比如說render tree里節點的增加,刪除。這個標記工作由函數`markNeedsCompositingBitsUpdate()`完成。
~~~
void markNeedsCompositingBitsUpdate() {
if (_needsCompositingBitsUpdate)
return;
_needsCompositingBitsUpdate = true;
if (parent is RenderObject) {
final RenderObject parent = this.parent;
if (parent._needsCompositingBitsUpdate)
return;
if (!isRepaintBoundary && !parent.isRepaintBoundary) {
parent.markNeedsCompositingBitsUpdate();
return;
}
}
if (owner != null)
owner._nodesNeedingCompositingBitsUpdate.add(this);
}
~~~
這個調用會從當前節點往上找,把所有父節點的`_needsCompositingBitsUpdate`標志位都置位`true`。直到自己或者父節點的`isRepaintBoundary`為`true`。最后會把自己加入到`PipelineOwner`的`_nodesNeedingCompositingBitsUpdate`列表里面。而函數調用`pipelineOwner.flushCompositingBits()`正是用來處理這個列表的。
## flushPaint
### markNeedsPaint
當某個`RenderObject`需要被重繪的時候會調用`markNeedsPaint()`
~~~
void markNeedsPaint() {
if (_needsPaint)
return;
_needsPaint = true;
if (isRepaintBoundary) {
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent;
parent.markNeedsPaint();
} else {
if (owner != null)
owner.requestVisualUpdate();
}
}
~~~
函數`markNeedsPaint()`首先做的是把自己的標志位`_needsPaint`設置為`true`。然后會向上查找最近的一個`isRepaintBoundary`為`true`的祖先節點。直到找到這樣的節點,才會把這個節點加入到`_nodesNeedingPaint`列表中,也就是說,并不是任意一個需要重繪的`RenderObject`就會被加入這個列表,而是往上找直到找到最近的一個`isRepaintBoundary`為`true`才會放入這個列表,換句話說,這個列表里只有`isRepaintBoundary`為`true`這種類型的節點。也就是說重繪的起點是從“重繪邊界”開始的。
### flushPaint
的。
~~~
void flushPaint() {
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
} finally {
...
}
}
~~~
之前`flushLayout()`里的排序不同,這里的排序是深度度深的節點在前。在循環體里,會判斷當前節點的`_layer`屬性是否處于`attached`的狀態。如果`_layer.attached`為`true`的話調用`PaintingContext.repaintCompositedChild(node);`去做繪制,否則的話調用`node._skippedPaintingOnLayer()`將自身以及到上層繪制邊界之間的節點的`_needsPaint`全部置為`true`。這樣在下次`_layer.attached`變為`true`的時候會直接繪制。
從上述代碼也可以看出,重繪邊界相當于把Flutter的繪制做了分塊處理,重繪的從上層重繪邊界開始,到下層重繪邊界為止,在此之間的`RenderObject`都需要重繪,而邊界之外的就可能不需要重繪,這也是一個性能上的考慮,盡量避免不必要的繪制。所以如何合理安排`RepaintBoundary`是我們在做Flutter app的性能優化時候需要考慮的一個方向。
這里的`_layer`屬性就是我們之前說的圖層,這個屬性只有繪制邊界的`RenderObject`才會有值。一般的`RenderObject`這個屬性是`null`。
。
### repaintCompositedChild
~~~
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
if (child._layer == null) {
child._layer = OffsetLayer();
} else {
child._layer.removeAllChildren();
}
childContext ??= PaintingContext(child._layer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);
childContext.stopRecordingIfNeeded();
}
~~~
先檢查`RenderObject`的圖層屬性,為空則新建一個`OffsetLayer`實例。如果圖層已經存在的話就把孩子清空。
### PaintingContext
如果沒有`PaintingContext`的話會新建一個,然后讓開始繪制。我們先來看一下`PaintingContext`這個類:
~~~
class PaintingContext extends ClipContext {
@protected
PaintingContext(this._containerLayer, this.estimatedBounds)
final ContainerLayer _containerLayer;
final Rect estimatedBounds;
PictureLayer _currentLayer;
ui.PictureRecorder _recorder;
Canvas _canvas;
@override
Canvas get canvas {
if (_canvas == null)
_startRecording();
return _canvas;
}
void _startRecording() {
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder);
_containerLayer.append(_currentLayer);
}
void stopRecordingIfNeeded() {
if (!_isRecording)
return;
_currentLayer.picture = _recorder.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
~~~
類`PaintingContext`字面意思是繪制上下文,其屬性`_containerLayer`是容器圖層,來自構造時的入參。也就是說`PaintingContext`是和容器圖層關聯的。接下來還有`PictureLayer`類型的`_currentLayer`屬性, `ui.PictureRecorder`類型的`_recorder`屬性和我們熟悉的`Canvas`類型的屬性`_canvas`。函數`_startRecording()` 實例化了這幾個屬性。`_recorder`用來錄制繪制命令,`_canvas`綁定一個錄制器。最后,`_currentLayer`會作為子節點加入到`_containerLayer`中。有開始那么就會有結束,`stopRecordingIfNeeded()`用來結束當前繪制的錄制。結束時會把繪制完畢的`Picture`賦值給當前的`PictureLayer.picture`。
### paintWithContext
`RenderObject._paintWithContext()`開始繪制了,這個函數會直接調用到我們熟悉的`RenderObject.paint(context, offset)`,`paint()`由`RenderObject`子類自己實現
### RenderRepaintBoundary的paint
從之前的源碼分析我們知道繪制起點都是“繪制邊界”。這里我們就拿我們熟悉的一個“繪制邊界”,`RenderRepaintBoundary`,為例來走一下繪制流程,它的繪制函數的實現在`RenderProxyBoxMixin`類中:
:
~~~
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);
}
~~~
這個調用又回到了`PaintingContext`的`paintChild()`方法:
~~~
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
}
~~~
這里會檢查子節點是不是繪制邊界,如果不是的話,就是普通的繪制了,接著往下調用`_paintWithContext()`,繼續往當前的`PictureLayer`上繪制。如果是的話就把當前的繪制先停掉。然后調用`_compositeChild(child, offset);`
~~~
void _compositeChild(RenderObject child, Offset offset) {
if (child._needsPaint) {
repaintCompositedChild(child, debugAlsoPaintedParent: true);
}
child._layer.offset = offset;
appendLayer(child._layer);
}
~~~
如果這個子繪制邊界被標記為需要重繪的話,那么就調用`repaintCompositedChild()`來重新生成圖層然后重繪。如果這個子繪制邊界**沒有**被標記為需要重繪的話,就跳過了重新生成圖層和重繪。最后只需要把子圖層加入到當前容器圖層中就行了。
### RenderObject的paint
這里就拿Flutter app出錯控件的繪制做個例子:
~~~
void paint(PaintingContext context, Offset offset) {
try {
context.canvas.drawRect(offset & size, Paint() .. color = backgroundColor);
double width;
if (_paragraph != null) {
// See the comment in the RenderErrorBox constructor. This is not the
// code you want to be copying and pasting. :-)
if (parent is RenderBox) {
final RenderBox parentBox = parent;
width = parentBox.size.width;
} else {
width = size.width;
}
_paragraph.layout(ui.ParagraphConstraints(width: width));
context.canvas.drawParagraph(_paragraph, offset);
}
} catch (e) {
// Intentionally left empty.
}
}
~~~
這看起來就像個正常的繪制了,我們會用來自`PaintingContext`的畫布`canvas`來繪制矩形,繪制文本等等。從前面的分析也可以看出,這里的繪制都是在一個`PictureLayer`的圖層上所做的。
## renderView.compositeFrame()
這里的`renderView`就是我們之前說的render tree的根節點。這個函數調用主要是把整個layer tree生成`scene`送到engine去顯示。
~~~
void compositeFrame() {
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
_window.render(scene);
scene.dispose();
} finally {
Timeline.finishSync();
}
}
~~~
`ui.SceneBuilder()`最終調用Native方法`SceneBuilder_constructor`。也就是說`ui.SceneBuilder`實例是由engine創建的。接下來就是調用`layer.buildScene(builder)`方法,這個方法會返回一個`ui.Scene`實例。由于方法`compositeFrame()`的調用者是`renderView`。所以這里這個`layer`是來自`renderView`的屬性,我們前面說過只有繪制邊界節點才有`layer`。所以可見render tree的根節點`renderView`也是一個繪制邊界。那么這個`layer`是從哪里來的呢?在文章《Flutter框架分析(二)-- 初始化》我們講過,框架初始化的過程中`renderView`會調度開天辟地的第一幀:
~~~
void scheduleInitialFrame() {
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
owner.requestVisualUpdate();
}
Layer _updateMatricesAndCreateNewRootLayer() {
_rootTransform = configuration.toMatrix();
final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
rootLayer.attach(this);
return rootLayer;
}
void scheduleInitialPaint(ContainerLayer rootLayer) {
_layer = rootLayer;
owner._nodesNeedingPaint.add(this);
}
復制代碼
~~~
在方法`_updateMatricesAndCreateNewRootLayer()`中,我們看到這里實例化了一個`TransformLayer`。`TransformLayer`繼承自`OffsetLayer`。構造時需要傳入`Matrix4`類型的參數`transform`。這個`Matrix4`其實和我們在Android中見到的`Matrix`是一回事。代表著矩陣變換。這里的`transform`來自我們之前講過的`ViewConfiguration`,它就是把設備像素比例轉化成了矩陣的形式。最終這個`layer`關聯上了`renderView`。所以這里這個`TransformLayer`其實也是layer tree的根節點了。
回到我們的繪制流程。`layer.buildScene(builder);`這個調用我們自然是去 `TransformLayer`里找了,但這個方法是在其父類`OffsetLayer`內,從這個調用開始就都是對圖層進行操作,最終把layer tree轉換為場景`scene`:
~~~
ui.Scene buildScene(ui.SceneBuilder builder) {
List<PictureLayer> temporaryLayers;
updateSubtreeNeedsAddToScene();
addToScene(builder);
final ui.Scene scene = builder.build();
return scene;
}
復制代碼
~~~
函數調用`updateSubtreeNeedsAddToScene();`會遍歷layer tree來設置`_subtreeNeedsAddToScene`標志位,如果有任意子圖層的添加、刪除操作,則該子圖層及其祖先圖層都會被置上`_subtreeNeedsAddToScene`標志位。然后會調用addToScene(builder);
~~~
@override
@override
ui.EngineLayer addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
_lastEffectiveTransform = transform;
final Offset totalOffset = offset + layerOffset;
if (totalOffset != Offset.zero) {
_lastEffectiveTransform = Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0.0)
..multiply(_lastEffectiveTransform);
}
builder.pushTransform(_lastEffectiveTransform.storage);
addChildrenToScene(builder);
builder.pop();
return null; // this does not return an engine layer yet.
}
復制代碼
~~~
`builder.pushTransform`會調用到engine層。相當于告訴engine這里我要加一個變換圖層。然后調用`ddChildrenToScene(builder)`將子圖層加入場景中,完了還要把之前壓棧的變換圖層出棧。
~~~
void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
Layer child = firstChild;
while (child != null) {
if (childOffset == Offset.zero) {
child._addToSceneWithRetainedRendering(builder);
} else {
child.addToScene(builder, childOffset);
}
child = child.nextSibling;
}
}
復制代碼
~~~
這就是遍歷添加子圖層的調用。主要還是逐層向下的調用`addToScene()`。這個方法不同的圖層會有不同的實現,對于容器類圖層而言,主要就是做三件事:1.添加自己圖層的效果然后入棧,2.添加子圖層,3. 出棧。
在所有圖層都處理完成之后。回到`renderView.compositeFrame()`,可見最后會把處理完得到的場景通過`_window.render(scene);`調用送入engine去顯示了。