## 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去显示了。