在連續寫了Android DisplayList 構建過程和Android 同步DisplayList信息后,接下來就是繪制DisplayList了
不過有個問題? 要將這些DisplayList繪制到什么地方去呢? 答案是Surface的Buffer中,
在syncFrameState
中會調用makeCurrent
會將EGL surface與RenderThread綁定,在綁定過程中會觸發Native Window(也就是Surface)dequeueBuffer/requestBuffer去向SurfaceFlinger申請一塊圖形緩沖區(也就是一塊與SurfaceFlinger都同時映射到的一塊共享匿名內存, 由GraphicBuffer表示出map的地址,文件句柄相關). 這部分內容請參考SurfaceFlinger中Buffer的創建與顯示. 所以在繪制之前Buffer已經準備好啦!
回到DrawFrameTask::run
, 調用CanvasContext->draw便開始繪制DisplayList了
void DrawFrameTask::run() {
...
if (CC_LIKELY(canDrawThisFrame)) {
context->draw();
}
...
}
一、 計算dirty區域
Android 同步DisplayList信息已經計算出了臟區域,但是這個臟區域是畫布的臟區域,畫布是無限大的,那么有可能dirty區域就是無限大的。事實上如果第一次繪制時確實是無限大的。
而現在計算臟區域是相對于具體的屏幕。
在 CanvasContext::draw函數里
SkRect dirty;
mDamageAccumulator.finish(&dirty); //從DamageAccumulator中獲得臟區域
由Android 同步DisplayList信息可知,在完成syncFrameState(也就是DisplayList信息同步完成)后,當前的臟區域保存在DamageAccumulator里, 通過調用DamageAccumulator.finish后就可以獲得臟區域。
本文的例子是第一次繪制,所以這里的臟區域是整張畫布,也就是無限大的區域。
Frame frame = mEglManager.beginFrame(mEglSurface); //獲得frame
if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
dirty.setEmpty();
mLastFrameWidth = frame.width();
mLastFrameHeight = frame.height();
} else if (mHaveNewSurface || frame.bufferAge() == 0) {
// New surface needs a full draw
dirty.setEmpty();
} else {
if (!dirty.isEmpty() && !dirty.intersect(0, 0, frame.width(), frame.height())) {
dirty.setEmpty();
}
profiler().unionDirty(&dirty);
}
if (dirty.isEmpty()) {
dirty.set(0, 0, frame.width(), frame.height());
}
bufferAge something... (略過)
mEglManager.damageFrame(frame, dirty); //通過android extension調用
beginFrame函數有兩個作用
一個是查詢真正畫布(mEGLSurface)的大小(長,寬),
本例的大小是(1200x1920)另一個是校驗 EGLDisplay和 Surface
最后臟區域就被重新設置成了(0, 0, 1200, 1920)了, 并通過damageFrame中的eglSetDamageRegionKHR來設置 dirty 區域,以標明臟區域。
二、繪制
臟區域已經計算好,并且在EGLsurface中設置了臟區域,那么下面就要繪制了
回到Canvas::draw()函數
2.1 創建 FrameBuilder
auto& caches = Caches::getInstance();
FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), mLightGeometry, caches);
FrameBuilder::FrameBuilder(...) {
// Prepare to defer Fbo0
//生成一個默認的LayerBuilder, FB0
auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip));
mLayerBuilders.push_back(fbo0);
mLayerStack.push_back(0);
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
lightGeometry.center);
}
在FrameBuilder構造函數中,會生成一幅默認的LayerBuilder, 也就是FB0, 由mLayerBuilders/mLayerStack指定。本例并沒有其它的Layer, 所以后續的的操作都是在這個默認的LayerBuilder中進行的。
FrameBuilder 摘自 Android N中UI硬件渲染(hwui)的HWUI_NEW_OPS(基于Android 7.1)
FrameBuilder 管理某一幀的構建,用于處理,優化和存儲從RenderNode和LayerUpdateQueue中來的渲染命令。
FrameBuilder主要工作是算出來最后的繪制狀態(主要是裁剪, 透明度計算,以及矩陣計算), 并且對每個繪制命令合并、重新排序,以提高繪制效率。
同時它的replayBakedOps()方法還用于該幀的繪制命令重放。一幀中可能需要繪制多個層,每一層的上下文都會存在相應的LayerBuilder中。在FrameBuilder中通過mLayerBuilders和mLayerStack存儲一個layer stack。它替代了原Snapshot類的一部分功能。LayerBuilder:
用于存儲"繪制某一層"的操作和狀態。對于所有View通用,即如果View有render layer,它對應一個FBO;如果對于普通View,它對應的是SurfaceFlinger提供的surface。 其中的mBatches存儲了當前層defer后(即batch/merge好)的繪制操作。
創建好FrameBuilder后,調用frameBuilder.deferRenderNodeScene(mRenderNodes, mContentDrawBounds);
來延遲 RenderNode的處理? 奇怪,按理說,DisplayList 都準備好了,Dirty區域也已經計算出來了,為啥還不直接繪制,還要延遲處理呢?
2.2 deferRenderNodeScene
延遲處理主要是對繪制命令進行合并,這樣有什么好處呢????
void FrameBuilder::deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
const Rect& contentDrawBounds) {
if (nodes.size() == 1) {
if (!nodes[0]->nothingToDraw()) {
deferRenderNode(*nodes[0]);
}
return;
}
...
}
在本例中,只有一個RootRenderNode, 來看下notingToDraw的判斷條件
bool nothingToDraw() const {
const Outline& outline = properties().getOutline();
return mDisplayList == nullptr
|| properties().getAlpha() <= 0
|| (outline.getShouldClip() && outline.isEmpty())
|| properties().getScaleX() == 0
|| properties().getScaleY() == 0;
}
noting to draw的判斷條件
- 沒有DisplayList -- 沒有繪制命令還畫啥呢
- 全透明的View -- 透明還畫啥呢?
- View是可裁剪,且被裁剪區域為(0, 0, 0, 0) -- 意思是已經把View裁剪沒啦
- 水平(scaleX)或垂直(scaleY)縮放為0時 -- 意思是view 縮放到無限小了,
deferRenderNode開始具體去推遲RenderNode draw了
void FrameBuilder::deferRenderNode(RenderNode& renderNode) {
renderNode.computeOrdering();
mCanvasState.save(SaveFlags::MatrixClip);
deferNodePropsAndOps(renderNode);
mCanvasState.restore();
}
computeOrdering 找到那些需要投影到它的Background上的子RenderNode, 這些RenderNode被稱為Projected RenderNode. 參考老羅的Android應用程序UI硬件加速渲染的Display List構建過程分析一文, 但Projected RenderNode不在本文講解范圍。
CanvasState.save 在Android DisplayList 構建過程中有講,就是生成一個新的快照來保存經過計算的RenderNode的相關屬性到快照中
CanvasState.restore()與CanvasState.save相互對應
接著看deferNodePropsAndOps
void FrameBuilder::deferNodePropsAndOps(RenderNode& node) {
const RenderProperties& properties = node.properties();
const Outline& outline = properties.getOutline();
//如果View的left,top不在原點(0,0), 則將坐標平移到具體的(left, top)
if (properties.getLeft() != 0 || properties.getTop() != 0) {
mCanvasState.translate(properties.getLeft(), properties.getTop());
}
//對靜態矩陣或Animation矩陣進行計算
if (properties.getStaticMatrix()) {
mCanvasState.concatMatrix(*properties.getStaticMatrix());
} else if (properties.getAnimationMatrix()) {
mCanvasState.concatMatrix(*properties.getAnimationMatrix());
}
//對View本身的轉換矩陣進行計算
if (properties.hasTransformMatrix()) {
if (properties.isTransformTranslateOnly()) {
mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY());
} else {
mCanvasState.concatMatrix(*properties.getTransformMatrix());
}
}
const int width = properties.getWidth();
const int height = properties.getHeight();
int clipFlags = properties.getClippingFlags();
//計算快照中的alpha值,這個和layer相關,略過
if (properties.getAlpha() < 1) {
...
}
//是否需要裁剪
if (clipFlags) {
Rect clipRect;
//拿到裁剪區域
properties.getClippingRectForFlags(clipFlags, &clipRect);
//開始裁剪,這個針對的快照中mClipArea, 它代表裁剪區域,也就是求兩個區域的交集。
mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
SkRegion::kIntersect_Op);
}
//和RevealAnimator有關,outline 略過...
if (properties.getRevealClip().willClip()) {
Rect bounds;
properties.getRevealClip().getBounds(&bounds);
mCanvasState.setClippingRoundRect(mAllocator,
bounds, properties.getRevealClip().getRadius());
} else if (properties.getOutline().willClip()) {
mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
}
// 接下來會deferNodeOps, 但是考慮是否reject, 一般這里都為false
//1. 判斷View的裁剪區域是否為空,不能把View裁剪為空的
//2. 設置了裁剪區域,但是最后裁剪出來的區域與原本View的大小都沒有交集了,這種情況會reject,
bool quickRejected = mCanvasState.currentSnapshot()->getRenderTargetClip().isEmpty()
|| (properties.getClipToBounds()
&& mCanvasState.quickRejectConservative(0, 0, width, height));
if (!quickRejected) {
...
deferNodeOps(node);
...
}
}
下面看下deferNodeOps
#define OP_RECEIVER(Type) \
[](FrameBuilder& frameBuilder, const RecordedOp& op) {
frameBuilder.defer##Type(static_cast<const Type&>(op)); },
void FrameBuilder::deferNodeOps(const RenderNode& renderNode) {
typedef void (*OpDispatcher) (FrameBuilder& frameBuilder, const RecordedOp& op);
static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER);
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
const DisplayList& displayList = *(renderNode.getDisplayList());
//chunks記錄著ops的位置
for (auto& chunk : displayList.getChunks()) {
//計算子View Z軸的大小,并接順序保存在 zTranslatedNodes中, 這里略過
FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
//略過,defer3dChildren只針對Z軸有效的情況
defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes);
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
const RecordedOp* op = displayList.getOps()[opIndex];
//與所以的RecordedOp執行對應的defer動作
receivers[op->opId](*this, *op);
...
}
defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes);
}
}
receivers是一組函數指針數組,它與RecordedOp子類相對應, deferXXXX, 如下所示
receivers[0] = (*deferRenderNodeOp)(FrameBuilder& frameBuilder, const RecordedOp& op)
receivers[1] = (*deferCirclePropsOp)(FrameBuilder& frameBuilder, const RecordedOp& op)
receivers[2] = (*deferRoundRectPropsOp)(FrameBuilder& frameBuilder, const RecordedOp& op)
...
receivers[21] = (*deferRectOp)(FrameBuilder& frameBuilder, const RecordedOp& op)
...
而RecordedOp分為兩類,一類是RenderNodeOp, 一類是specific的RecordedOp也就是繪制命令, 針對這兩種情況
2.2.1 RenderNodeOp
從名字大概可以猜測它的意思,也就是推遲下一個RenderNode, 這樣就能遞歸遍歷整個DisplayList Tree.
void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) {
if (!op.skipInOrderDraw) {
deferRenderNodeOpImpl(op);
}
}
void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) return;
//生成一個新的快照用來保存新的RenderNode相關信息
int count = mCanvasState.save(SaveFlags::MatrixClip);
//進行相應的矩陣計算,
//注意,這里的op.localClip與op.localMatrix都是在canvas里進行操作的(如 canvas.translate ...),而非View的屬性
// apply state from RecordedOp (clip first, since op's clip is transformed by current matrix)
mCanvasState.writableSnapshot()->applyClip(op.localClip,
*mCanvasState.currentSnapshot()->transform);
mCanvasState.concatMatrix(op.localMatrix);
// then apply state from node properties, and defer ops
deferNodePropsAndOps(*op.renderNode);
mCanvasState.restoreToCount(count);
}
從deferRenderNodeOpImpl主要工作之一就是應用canvas做的一些矩陣運算(translate/scale 裁剪), 另一個就是遞歸調用子RenderNode,這樣就能遍歷完所有的DisplayList相關的繪制命令,以及計算出canvas的矩陣。
2.2.2 具體的RecordedOp, 以deferRectOp為例
deferRectOp就是處理具體的繪制命令
void FrameBuilder::deferRectOp(const RectOp& op) {
deferStrokeableOp(op, tessBatchId(op));
}
BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
...
currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
return bakedState;
}
tessBatchId(op)通過Paint去獲得batch id, 對于一般的繪制形狀的命令如(RectOp,ArcOp,OvalOp,RoundRectOp)都是Vertics類型.
deferStrokeableOp會生成一個BakedOpState,然后通過deferUnmergeableOp
將BakedOpState插入到Layer的對應的mBatches里面, 同時插入到mBatchLookup,一個查找表,里面的元素表示的是不可merge的ops.
在deferUnmergeableOp
, 肯定也對應有deferMergeableOp
. mMergingBatchLookup是一個可merge ops的查找表,
如圖所示
這樣經過所有的2.2.1節對所有的RenderNode遞歸調用后,所有的Ops都已經插入到LayerBuilder中的mBatches里,并且將Mergeable的op插入到mMergingBatchLookup,將unMergeable的op插入到了mBatchLookup中了。
2.3 replayBakedOps
伴隨著DisplayList信息全部已經保存到了LayerBuilder里了, 下一步就是將這些繪制命令真正的繪制出來,最終結果就是轉換成openGL API接口,
在CanvasContext::draw()
里,具體是通過 replayBakedOps
命令操作的。
BakedOpRenderer renderer(caches, mRenderThread.renderState(),
mOpaque, mLightInfo);
frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
bool drew = renderer.didDraw();
template <typename StaticDispatcher, typename Renderer>
void replayBakedOps(Renderer& renderer) {
// Replay through layers in reverse order, since layers
// later in the list will be drawn by earlier ones
for (int i = mLayerBuilders.size() - 1; i >= 1; i--) { //針對其它layer, 本例代碼并沒有其它layer,這里直接就略過
...
}
if (CC_LIKELY(mDrawFbo0)) { //默認的layer FB0
const LayerBuilder& fbo0 = *(mLayerBuilders[0]);
renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
renderer.endFrame(fbo0.repaintRect);
}
...
}
startFrame通過調用 glViewPort來設置視見區位置, 并且通過glBindFramebuffer
將要渲染的數據綁定到fb0中.
void LayerBuilder::replayBakedOpsImpl(void* arg,
BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const {
for (const BatchBase* batch : mBatches) {
size_t size = batch->getOps().size();
if (size > 1 && batch->isMerging()) {
int opId = batch->getOps()[0]->op->opId;
const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch);
MergedBakedOpList data = {
batch->getOps().data(),
size,
mergingBatch->getClipSideFlags(),
mergingBatch->getClipRect()
};
mergedReceivers[opId](arg, data);
} else {
for (const BakedOpState* op : batch->getOps()) {
unmergedReceivers[op->op->opId](arg, *op);
}
}
}
}
而replayBakedOpsImpl遍歷LayerBuilder中的mBatches(記錄了所有的繪制操作),然后分別針對Merged(如onMergedBitmapOps)或unmerged(如onRectOp)的op調用具體的函數將繪制命令封裝成Glop,
然后通過BakedOpRenderer中的renderGlop將Glop轉換成openGL函數. 這部分涉及 openGL相關命令,就不繼續下去。
2.4 swapBuffer
2.3小節已經將繪制操作全部轉換成openGl了, 最后就通過EglManager的eglSwapBuffersWithDamageKHR去swap back buffer和front buffer, 這樣新的一幀數據就顯示出來了
mEglManager.swapBuffers(frame, screenDirty)
eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects, screenDirty.isEmpty() ? 0 : 1);