Android 繪制DisplayList

在連續(xù)寫了Android DisplayList 構(gòu)建過程Android 同步DisplayList信息后,接下來就是繪制DisplayList了

不過有個問題? 要將這些DisplayList繪制到什么地方去呢? 答案是Surface的Buffer中,

syncFrameState中會調(diào)用makeCurrent會將EGL surface與RenderThread綁定,在綁定過程中會觸發(fā)Native Window(也就是Surface)dequeueBuffer/requestBuffer去向SurfaceFlinger申請一塊圖形緩沖區(qū)(也就是一塊與SurfaceFlinger都同時映射到的一塊共享匿名內(nèi)存, 由GraphicBuffer表示出map的地址,文件句柄相關(guān)). 這部分內(nèi)容請參考SurfaceFlinger中Buffer的創(chuàng)建與顯示. 所以在繪制之前Buffer已經(jīng)準(zhǔn)備好啦!

回到DrawFrameTask::run, 調(diào)用CanvasContext->draw便開始繪制DisplayList了

void DrawFrameTask::run() {
    ...
    if (CC_LIKELY(canDrawThisFrame)) {
        context->draw();
    }
    ...
}

一、 計(jì)算dirty區(qū)域

Android 同步DisplayList信息已經(jīng)計(jì)算出了臟區(qū)域,但是這個臟區(qū)域是畫布的臟區(qū)域,畫布是無限大的,那么有可能dirty區(qū)域就是無限大的。事實(shí)上如果第一次繪制時確實(shí)是無限大的。

而現(xiàn)在計(jì)算臟區(qū)域是相對于具體的屏幕。

在 CanvasContext::draw函數(shù)里

    SkRect dirty;
    mDamageAccumulator.finish(&dirty);  //從DamageAccumulator中獲得臟區(qū)域

Android 同步DisplayList信息可知,在完成syncFrameState(也就是DisplayList信息同步完成)后,當(dāng)前的臟區(qū)域保存在DamageAccumulator里, 通過調(diào)用DamageAccumulator.finish后就可以獲得臟區(qū)域。

本文的例子是第一次繪制,所以這里的臟區(qū)域是整張畫布,也就是無限大的區(qū)域。

    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調(diào)用 

beginFrame函數(shù)有兩個作用

  • 一個是查詢真正畫布(mEGLSurface)的大小(長,寬),
    本例的大小是(1200x1920)

  • 另一個是校驗(yàn) EGLDisplay和 Surface

最后臟區(qū)域就被重新設(shè)置成了(0, 0, 1200, 1920)了, 并通過damageFrame中的eglSetDamageRegionKHR來設(shè)置 dirty 區(qū)域,以標(biāo)明臟區(qū)域。

二、繪制

臟區(qū)域已經(jīng)計(jì)算好,并且在EGLsurface中設(shè)置了臟區(qū)域,那么下面就要繪制了

回到Canvas::draw()函數(shù)

2.1 創(chuàng)建 FrameBuilder

auto& caches = Caches::getInstance();
FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), mLightGeometry, caches);

FrameBuilder::FrameBuilder(...) {
    // Prepare to defer Fbo0
    //生成一個默認(rèn)的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構(gòu)造函數(shù)中,會生成一幅默認(rèn)的LayerBuilder, 也就是FB0, 由mLayerBuilders/mLayerStack指定。本例并沒有其它的Layer, 所以后續(xù)的的操作都是在這個默認(rèn)的LayerBuilder中進(jìn)行的。

圖1 FrameBuilder
  • FrameBuilder 摘自 Android N中UI硬件渲染(hwui)的HWUI_NEW_OPS(基于Android 7.1)
    FrameBuilder 管理某一幀的構(gòu)建,用于處理,優(yōu)化和存儲從RenderNode和LayerUpdateQueue中來的渲染命令。
    FrameBuilder主要工作是算出來最后的繪制狀態(tài)(主要是裁剪, 透明度計(jì)算,以及矩陣計(jì)算), 并且對每個繪制命令合并、重新排序,以提高繪制效率。
    同時它的replayBakedOps()方法還用于該幀的繪制命令重放。一幀中可能需要繪制多個層,每一層的上下文都會存在相應(yīng)的LayerBuilder中。在FrameBuilder中通過mLayerBuilders和mLayerStack存儲一個layer stack。它替代了原Snapshot類的一部分功能。

  • LayerBuilder:
    用于存儲"繪制某一層"的操作和狀態(tài)。對于所有View通用,即如果View有render layer,它對應(yīng)一個FBO;如果對于普通View,它對應(yīng)的是SurfaceFlinger提供的surface。 其中的mBatches存儲了當(dāng)前層defer后(即batch/merge好)的繪制操作。

創(chuàng)建好FrameBuilder后,調(diào)用frameBuilder.deferRenderNodeScene(mRenderNodes, mContentDrawBounds); 來延遲 RenderNode的處理? 奇怪,按理說,DisplayList 都準(zhǔn)備好了,Dirty區(qū)域也已經(jīng)計(jì)算出來了,為啥還不直接繪制,還要延遲處理呢?

2.2 deferRenderNodeScene

延遲處理主要是對繪制命令進(jìn)行合并,這樣有什么好處呢????

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是可裁剪,且被裁剪區(qū)域?yàn)?0, 0, 0, 0) -- 意思是已經(jīng)把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應(yīng)用程序UI硬件加速渲染的Display List構(gòu)建過程分析一文, 但Projected RenderNode不在本文講解范圍。

CanvasState.save 在Android DisplayList 構(gòu)建過程中有講,就是生成一個新的快照來保存經(jīng)過計(jì)算的RenderNode的相關(guān)屬性到快照中

CanvasState.restore()與CanvasState.save相互對應(yīng)

接著看deferNodePropsAndOps

void FrameBuilder::deferNodePropsAndOps(RenderNode& node) {
    const RenderProperties& properties = node.properties();
    const Outline& outline = properties.getOutline();
    
    //如果View的left,top不在原點(diǎn)(0,0), 則將坐標(biāo)平移到具體的(left, top)
    if (properties.getLeft() != 0 || properties.getTop() != 0) {
        mCanvasState.translate(properties.getLeft(), properties.getTop());
    }
    //對靜態(tài)矩陣或Animation矩陣進(jìn)行計(jì)算
    if (properties.getStaticMatrix()) {
        mCanvasState.concatMatrix(*properties.getStaticMatrix());
    } else if (properties.getAnimationMatrix()) {
        mCanvasState.concatMatrix(*properties.getAnimationMatrix());
    }
    //對View本身的轉(zhuǎn)換矩陣進(jìn)行計(jì)算
    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();
    //計(jì)算快照中的alpha值,這個和layer相關(guān),略過
    if (properties.getAlpha() < 1) {
        ...
    }
    //是否需要裁剪
    if (clipFlags) {
        Rect clipRect;
        //拿到裁剪區(qū)域
        properties.getClippingRectForFlags(clipFlags, &clipRect);
        //開始裁剪,這個針對的快照中mClipArea, 它代表裁剪區(qū)域,也就是求兩個區(qū)域的交集。
        mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
                SkRegion::kIntersect_Op);
    }
    //和RevealAnimator有關(guān),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的裁剪區(qū)域是否為空,不能把View裁剪為空的
    //2. 設(shè)置了裁剪區(qū)域,但是最后裁剪出來的區(qū)域與原本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()) {
        //計(jì)算子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執(zhí)行對應(yīng)的defer動作
            receivers[op->opId](*this, *op);
            ...
        }   
        defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes);
    }   
}   

receivers是一組函數(shù)指針數(shù)組,它與RecordedOp子類相對應(yīng), 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相關(guān)信息
    int count = mCanvasState.save(SaveFlags::MatrixClip);

    //進(jìn)行相應(yīng)的矩陣計(jì)算, 
    //注意,這里的op.localClip與op.localMatrix都是在canvas里進(jìn)行操作的(如 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主要工作之一就是應(yīng)用canvas做的一些矩陣運(yùn)算(translate/scale 裁剪), 另一個就是遞歸調(diào)用子RenderNode,這樣就能遍歷完所有的DisplayList相關(guān)的繪制命令,以及計(jì)算出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的對應(yīng)的mBatches里面, 同時插入到mBatchLookup,一個查找表,里面的元素表示的是不可merge的ops.

deferUnmergeableOp, 肯定也對應(yīng)有deferMergeableOp. mMergingBatchLookup是一個可merge ops的查找表,

如圖所示

LayerBuilder圖

這樣經(jīng)過所有的2.2.1節(jié)對所有的RenderNode遞歸調(diào)用后,所有的Ops都已經(jīng)插入到LayerBuilder中的mBatches里,并且將Mergeable的op插入到mMergingBatchLookup,將unMergeable的op插入到了mBatchLookup中了。

2.3 replayBakedOps

伴隨著DisplayList信息全部已經(jīng)保存到了LayerBuilder里了, 下一步就是將這些繪制命令真正的繪制出來,最終結(jié)果就是轉(zhuǎn)換成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)) { //默認(rèn)的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通過調(diào)用 glViewPort來設(shè)置視見區(qū)位置, 并且通過glBindFramebuffer將要渲染的數(shù)據(jù)綁定到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調(diào)用具體的函數(shù)將繪制命令封裝成Glop,
然后通過BakedOpRenderer中的renderGlop將Glop轉(zhuǎn)換成openGL函數(shù). 這部分涉及 openGL相關(guān)命令,就不繼續(xù)下去。

2.4 swapBuffer

2.3小節(jié)已經(jīng)將繪制操作全部轉(zhuǎn)換成openGl了, 最后就通過EglManager的eglSwapBuffersWithDamageKHR去swap back buffer和front buffer, 這樣新的一幀數(shù)據(jù)就顯示出來了

mEglManager.swapBuffers(frame, screenDirty)

eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects, screenDirty.isEmpty() ? 0 : 1);

參考

EGL doc

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,224評論 6 529
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,916評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,014評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,466評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,245評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,795評論 1 320
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,869評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,010評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,524評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,487評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,634評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,173評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,884評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,282評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,541評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,236評論 3 388
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,623評論 2 370

推薦閱讀更多精彩內(nèi)容