Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android系統教程 >> 安卓省電與加速 >> Android應用程序UI硬件加速渲染的Display List渲染過程分析

Android應用程序UI硬件加速渲染的Display List渲染過程分析

編輯:安卓省電與加速

在硬件加速渲染環境中,Android應用程序窗口的UI渲染是分兩步進行的。第一步是構建Display List,發生在應用程序進程的Main Thread中;第二步是渲染Display List,發生在應用程序進程的Render Thread中。Display List的渲染不是簡單地執行繪制命令,而是包含了一系列優化操作,例如繪制命令的合並執行。本文就詳細分析Display List的渲染過程。

 

從前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文可以知道,Android應用程序窗口的Root Render Node的Display List,包含了Android應用程序窗口所有的繪制命令,因此我們只要對Root Render Node的Display List進行渲染,就可以得到整個Android應用程序窗口的UI。

Android應用程序窗口的Display List的構建是通過Display List Renderer進行的,而渲染是通過Open GL Renderer進行的,如圖1所示:

/

圖1 Android應用程序窗口的Display List的渲染示意圖

從圖1可以知道,Open GL Renderer只作用在Android應用程序窗口的Root Render Node的Display List上,這是因為Root Render Node的Display List包含了Android應用程序窗口所有的繪制命令。

Android應用程序窗口的Display List的渲染是由Render Thread執行的,不過是由Main Thread通知Render Thread執行的,如圖2所示:

/

圖2 Main Thread向Render Thread發起渲染命令

從圖2可以知道。Main Thread通過向Render Thread的TaskQueue添加一個drawFrame任務來通知Render Thread渲染Android應用程序窗口的UI。

從前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文還可以知道,Android應用程序窗口的Display List構建完成之後,Main Thread就馬上向Render Thread發出渲染命令,如下所示:

 

public class ThreadedRenderer extends HardwareRenderer {  
    ......  
  
    @Override  
    void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {  
        ......  
  
        updateRootDisplayList(view, callbacks);  
        ......  
  
        if (attachInfo.mPendingAnimatingRenderNodes != null) {  
            final int count = attachInfo.mPendingAnimatingRenderNodes.size();  
            for (int i = 0; i < count; i++) {  
                registerAnimatingRenderNode(  
                        attachInfo.mPendingAnimatingRenderNodes.get(i));  
            }  
            attachInfo.mPendingAnimatingRenderNodes.clear();  
            // We don't need this anymore as subsequent calls to  
            // ViewRootImpl#attachRenderNodeAnimator will go directly to us.  
            attachInfo.mPendingAnimatingRenderNodes = null;  
        }  
  
        int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,  
                recordDuration, view.getResources().getDisplayMetrics().density);  
        if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
            attachInfo.mViewRootImpl.invalidate();
        }
 
    }  
  
    ......  
} 
這個函數定義在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。

 

ThreadedRenderer類的成員函數draw主要執行三個操作:

1. 調用成員函數updateRootDisplayList構建或者更新應用程序窗口的Root Render Node的Display List。

2. 調用成員函數registerAnimationRenderNode注冊應用程序窗口動畫相關的Render Node。

3. 調用成員函數nSyncAndDrawFrame渲染應用程序窗口的Root Render Node的Display List。

其中,第一個操作在前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文已經分析,第二個操作在接下來的一篇文章中分析,這篇文章主要關注第三個操作,即應用程序窗口的Root Render Node的Display List的渲染過程,即ThreadedRenderer類的成員函數nSyncAndDrawFrame的實現。

ThreadedRenderer類的成員函數nSyncAndDrawFrame是一個JNI函數,由Native層的函數android_view_ThreadedRenderer_syncAndDrawFrame實現,如下所示:

 

static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) {
    RenderProxy* proxy = reinterpret_cast(proxyPtr);
    return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density);
}

這個函數定義在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

參數proxyPtr描述的是一個RenderProxy對象,這裡調用它的成員函數syncAndDrawFrame渲染應用程序窗口的Display List。

RenderProxy類的成員函數syncAndDrawFrame的實現如下所示:

 

int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
        float density) {
    mDrawFrameTask.setDensity(density);
    return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos);
}
這個函數定義在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp。

 

RenderProxy類的成員變量mDrawFrameTask指向的是一個DrawFrameTask對象。在前面Android應用程序UI硬件加速渲染環境初始化過程分析一文提到,這個DrawFrameTask對象描述的是一個用來執行渲染任務的Task,這裡調用它的成員函數drawFrame渲染應用程序窗口的下一幀,也就是應用程序窗口的Display List。

DrawFrameTask的成員函數drawFrame的實現如下所示:

 

int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
    ......

    mSyncResult = kSync_OK;
    ......

    postAndWait();

    ......

    return mSyncResult;
}
這個函數定義在文件frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp中。

 

DrawFrameTask的成員函數drawFrame最主要的操作就是調用另外一個成員函數postAndWait往Render Thread的Task Queue拋一個消息,並且進入睡眠狀態,等待Render Thread在合適的時候喚醒。

DrawFrameTask的成員函數postAndWait的實現如下所示:

 

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    mRenderThread->queue(this);
    mSignal.wait(mLock);
}
這個函數定義在文件frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp中。

 

由於DrawFrameTask類描述的就是一個可以添加到Render Thread的Task Queue的Task,因此DrawFrameTask的成員函數postAndWait就將當前正在處理的DrawFrameTask對象添加到由成員變量mRenderThread描述的Render Thread的Task Queue,並且在另外一個成員變量mSignal描述的一個條件變量上進行等待。

從前面Android應用程序UI硬件加速渲染環境初始化過程分析一文可以知道,添加到Render Thread的Task Queue的Task被處理時,它的成員函數run就會被調用,因此接下來DrawFrameTask類的成員函數run就會被調用,它的實現如下所示:

 

void DrawFrameTask::run() {
    ......

    bool canUnblockUiThread;
    bool canDrawThisFrame;
    {
        TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
        canUnblockUiThread = syncFrameState(info);
        canDrawThisFrame = info.out.canDrawThisFrame;
    }

    // Grab a copy of everything we need
    CanvasContext* context = mContext;

    // From this point on anything in this is *UNSAFE TO ACCESS*
    if (canUnblockUiThread) {
        unblockUiThread();
    }

    if (CC_LIKELY(canDrawThisFrame)) {
        context->draw();
    }

    if (!canUnblockUiThread) {
        unblockUiThread();
    }
}
這個函數定義在文件frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp中。

要理解這個函數首先要理解應用程序進程的Main Thread和Render Thread是如何協作的。從前面的分析可以知道,Main Thread請求Render Thread執行Draw Frame Task的時候,不能馬上返回,而是進入等待狀態。等到Render Thread從Main Thread同步完繪制所需要的信息之後,Main Thread才會被喚醒。

那麼,Render Thread要從Main Thread同步什麼信息呢?原來,Main Thread和Render Thread都各自維護了一份應用程序窗口視圖信息。各自維護了一份應用程序窗口視圖信息的目的,就是為了可以互不干擾,進而實現最大程度的並行。其中,Render Thread維護的應用程序窗口視圖信息是來自於Main Thread的。因此,當Main Thread維護的應用程序窗口信息發生了變化時,就需要同步到Render Thread去。

應用程序窗口的視圖信息包含圖1所示的各個Render Node的Display List、Property以及Display List引用的Bitmap。在RenderNode類中,有六個成員變量是與Display List和Property相關的,如下所示:

 

class RenderNode : public VirtualLightRefBase {
public:
    ......

    ANDROID_API void setStagingDisplayList(DisplayListData* newData);
    ......

    const RenderProperties& stagingProperties() {
        return mStagingProperties;
    }
    ......

private:
    ......

    uint32_t mDirtyPropertyFields;
    RenderProperties mProperties;
    RenderProperties mStagingProperties;

    bool mNeedsDisplayListDataSync;
    // WARNING: Do not delete this directly, you must go through deleteDisplayListData()!
    DisplayListData* mDisplayListData;
    DisplayListData* mStagingDisplayListData;
 
    ......
};
這個類定義在文件frameworks/base/libs/hwui/RenderNode.h中。

 

其中,成員變量mStagingProperties描述的Render Properties和成員變量mStagingDisplayListData描述的Display List Data由Main Thread維護,而成員變量mProperties描述的Render Properties和成員變量mDisplayListData描述的Display List Data由Render Thread維護。

這一點可以從前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文看出。當Main Thread構建完成應用程序窗口的Display List之後,就會調用RenderNode類的成員函數setStagingDisplayList將其設置到Root Render Node的成員變量mStagingDisplayListData中去。而當應用程序窗口某一個View的Property發生變化時,就會調用RenderNode類的成員函數mutateStagingProperties獲得成員變量mStagingProperties描述的Render Properties,進而修改相應的Property。

當Main Thread維護的Render Properties發生變化時,成員變量mDirtyPropertyFields的值就不等於0,其中不等於0的位就表示是哪一個具體的Property發生了變化,而當Main Thread維護的Display List Data發生變化時,成員變量mNeedsDisplayListDataSync的值就等於true,表示要從Main Thread同步到Render Thread。

另外,在前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文分析將一個Bitmap繪制命令轉化為一個DrawBitmapOp記錄在Display List時,Bitmap會被增加一個引用,如下所示:

 

status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
    bitmap = refBitmap(bitmap);
    paint = refPaint(paint);

    addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint));
    return DrawGlInfo::kStatusDone;
}
這個函數定義在文件frameworks/base/libs/hwui/DisplayListRenderer.cpp中。

 

參數bitmap描述的SkBitmap通過調用DisplayListRenderer類的成員函數refBitmap進行使用,它的實現如下所示:

 

class ANDROID_API DisplayListRenderer: public StatefulBaseRenderer {
public:
    ......

    inline const SkBitmap* refBitmap(const SkBitmap* bitmap) {
        ......
        mDisplayListData->bitmapResources.add(bitmap);
        mCaches.resourceCache.incrementRefcount(bitmap);
        return bitmap;
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DisplayListRenderer.h中。

 

DisplayListRenderer類的成員函數refBitmap在增加參烽bitmap描述的一個SkBitmap的引用計數之前,會將它保存在成員變量mDisplayListData指向的一個DisplayListData對象的成員變量bitmapResources描述的一個Vector中。

上述情況是針對調用GLES20Canvas類的以下的成員函數drawBitmap繪制一個Bitmap發生的情況:

 

class GLES20Canvas extends HardwareCanvas {
    ......

    @Override
    public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
        throwIfCannotDraw(bitmap);
        final long nativePaint = paint == null ? 0 : paint.mNativePaint;
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
    }

    ......
}
這個函數定義在文件frameworks/base/core/java/android/view/GLES20Canvas.java。

 

我們還可以調用GLES20Canvas類的另外一個重載版本的成員函數drawBitmap繪制一個Bitmap,如下所示:

 

class GLES20Canvas extends HardwareCanvas {
    ......

    @Override
    public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
            int width, int height, boolean hasAlpha, Paint paint) {
        ......

        final long nativePaint = paint == null ? 0 : paint.mNativePaint;
        nDrawBitmap(mRenderer, colors, offset, stride, x, y,
                width, height, hasAlpha, nativePaint);
    }

    ......
}
這個函數定義在文件frameworks/base/core/java/android/view/GLES20Canvas.java。

 

GLES20Canvas類這個重載版本的成員函數drawBitmap通過一個int數組來指定要繪制的Bitmap。這個int數組是由應用程序自己管理的,並且會被封裝成一個SkBitmap,最終由DisplayListRenderer類的成員函數drawBitmapData將該Bitmap繪制命令封裝成一個DrawBitmapDataOp記錄在Display List中,如下所示:

 

status_t DisplayListRenderer::drawBitmapData(const SkBitmap* bitmap, const SkPaint* paint) {
    bitmap = refBitmapData(bitmap);
    paint = refPaint(paint);

    addDrawOp(new (alloc()) DrawBitmapDataOp(bitmap, paint));
    return DrawGlInfo::kStatusDone;
}
這個函數定義在文件frameworks/base/libs/hwui/DisplayListRenderer.cpp中。

 

DisplayListRenderer類的成員函數drawBitmapData通過另外一個成員函數refBitmapData來增加參數bitmap描述的SkBitmap的引用,如下所示:

 

class ANDROID_API DisplayListRenderer: public StatefulBaseRenderer {
public:
    ......

    inline const SkBitmap* refBitmapData(const SkBitmap* bitmap) {
        mDisplayListData->ownedBitmapResources.add(bitmap);
        mCaches.resourceCache.incrementRefcount(bitmap);
        return bitmap;
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DisplayListRenderer.cpp中。

 

與前面分析的DisplayListRenderer類的成員函數refBitmap不同,DisplayListRenderer類的成員函數refBitmapData將參數bitmap描述的SkBitmap保存在成員變量mDisplayListData指向的一個DisplayListData對象的成員變量ownedBitmapResources描述的一個Vector中。這是由於前者引用的SkBitmap使用的底層存儲是由應用程序提供和管理的,而後者引用的SkBitmap使用的底層存儲是在SkBitmap內部創建和管理的。這個區別在接下來分析Bitmap的同步過程時會進一步得到體現。

Display List引用的Bitmap的同步方式與Display List和Render Property的同步方式有所不同。在同步Bitmap的時候,Bitmap將作為一個Open GL紋理上傳到GPU去被Render Thread使用。

有了這些背景知識之後 ,再回到DrawFrameTask類的成員函數run中,它的執行邏輯如下所示:

1. 調用成員函數syncFrameState將應用程序窗口的Display List、Render Property以及Display List引用的Bitmap等信息從Main Thread同步到Render Thread中。注意,在這個同步過程中,Main Thread是處於等待狀態的。

2. 如果成員函數syncFrameState能順利地完成信息同步,那麼它的返回值canUnblockUiThread就會等於true,表示在Render Thread渲染應用程序窗口的下一幀之前,就可以喚醒Main Thread了。否則的話,就要等到Render Thread渲染應用程序窗口的下一幀之後,才能喚醒Main Thread。喚醒Render Thread是通過調用成員函數unblockUiThread來完成的,如下所示:

 

void DrawFrameTask::unblockUiThread() {
    AutoMutex _lock(mLock);
    mSignal.signal();
}
這個函數定義在frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp中。

 

前面Main Thread就剛好是等待在DrawFrameTask類的成員變量mSignal描述的一個條件變量之上的,所以現在Render Thread就通過這個條件變量來喚醒它。

3. 調用成員變量mContext描述的一個CanvasContext對象的成員函數draw渲染應用程序窗口的Display List,不過前提是當前幀能夠進行繪制。什麼時候當前幀能夠進行繪制不能繪制呢?我們知道,應用程序進程繪制好一個窗口之後,得到的圖形緩沖區要交給Surface Flinger進行合成,最後才能顯示在屏幕上。Surface Flinger為每一個窗口都維護了一個圖形緩沖區隊列。當這個隊列等待合成的圖形緩沖區的個數大於等於2時,就表明Surface Flinger太忙了。因此這時候就最好不再向它提交圖形緩沖區,這就意味著應用程序窗口的當前幀不能繪制了,也就是會被丟棄。

注意,Render Thread渲染應用程序窗口的Display List的時候,Main Thread有可能是處於等待狀態,也有可能不是處於等待狀態。這取決於前面的信息同步結果。信息同步結果是通過一個TreeInfo來描述的。當Main Thread不是處於等待狀態時,它就可以馬上處理其它事情了,例如構建應用程序窗口下一幀時使用的Display List。這樣就可以做到Render Thread在繪制應用程序窗口的當前幀的同時,Main Thread可以並且地去構建應用程序窗口的下一幀的Display List。這一點也是Android 5.0引進Render Thread的好處所在。

接下來,我們就先分析應用程序窗口繪制信息的同步過程,即DrawFrameTask類的成員函數syncFrameState的實現,接著再分析應用程序窗口的Display List的渲染過程,即CanvasContext類的成員函數draw的實現。

DrawFrameTask類的成員函數syncFrameState的實現如下所示:

 

bool DrawFrameTask::syncFrameState(TreeInfo& info) {
    ......
   
    Caches::getInstance().textureCache.resetMarkInUse();

    for (size_t i = 0; i < mLayers.size(); i++) {
        mContext->processLayerUpdate(mLayers[i].get());
    }
    ......
    mContext->prepareTree(info);

    if (info.out.hasAnimations) {
        if (info.out.requiresUiRedraw) {
            mSyncResult |= kSync_UIRedrawRequired;
        }
    }
    // If prepareTextures is false, we ran out of texture cache space
    return info.prepareTextures;
}
這個函數定義在文件frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp中。

應用程序進程有一個Caches單例。這個Caches單例有一個成員變量textureCache,它指向的是一個TextureCache對象。這個TextureCache對象用來緩存應用程序窗口在渲染過程中用過的Open GL紋理。在同步應用程序窗口繪制信息之前,DrawFrameTask類的成員函數syncFrameState首先調用這個TextureCache對象的成員函數resetMarkInUse將緩存的Open GL紋理標記為未使用狀態。

在前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文提到,當TextureView有更新時,Native層會將一個與它關聯的DeferredLayerUpdater對象保存在DrawFrameTask類的成員變量mLayers描述的一個vector中。也就是說,保存在這個vector中的DeferredLayerUpdater對象,都是需要進一步處理的。需要做的處理就是從與TextureView關聯的SurfaceTexture中讀出下一個可用的圖形緩沖區,並且將該圖形緩沖區封裝成一個Open GL紋理。這是通過調用DrawFrameTask類的成員變量mContext指向的一個CanvasContext對象的成員函數processLayerUpdate來實現的。

CanvasContext類的成員函數processLayerUpdate的實現如下所示:

 

void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
    bool success = layerUpdater->apply();
    ......
}
這個函數定義在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。

 

CanvasContext類的成員函數processLayerUpdate主要是調用參數layerUpdater描述的一個DeferredLayerUpdater對象的成員函數apply讀出下一個可用的圖形緩沖區,並且將該圖形緩沖區封裝成一個Open GL紋理,以便後面可以對它進行渲染。

DeferredLayerUpdater類的成員函數apply的實現如下所示:

 

bool DeferredLayerUpdater::apply() {
    bool success = true;
    ......

    if (mSurfaceTexture.get()) {
        ......
        if (mUpdateTexImage) {
            mUpdateTexImage = false;
            doUpdateTexImage();
        }
        ......
    }
    return success;
}
這個函數定義在文件frameworks/base/libs/hwui/DeferredLayerUpdater.cpp中。

 

DeferredLayerUpdater類的成員變量mSurfaceTexture指向的一個是GLConsumer對象。這個GLConsumer對象用來描述與當前正在處理的DeferredLayerUpdater對象關聯的TextureView對象所使用的一個SurfaceTexture對象的讀端。也就是說,通過這個GLConsumer對象可以將關聯的TextureView對象的下一個可用的圖形緩沖區讀取出來。

從前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文可以知道,當一個TextureView有可用的圖形緩沖區時,與它關聯的DeferredLayerUpdater對象的成員變量mUpdateTexImage值會被設置為true。這時候如果當前正在處理的DeferredLayerUpdater對象的成員變量mSurfaceTexture指向了一個GLConsumer對象,那麼現在就是時候去讀取可用的圖形緩沖區了。這是通過調用DeferredLayerUpdater類的成員函數doUpdateTexImage來實現的。

DeferredLayerUpdater類的成員函數doUpdateTexImage的實現如下所示:

 

void DeferredLayerUpdater::doUpdateTexImage() {
    if (mSurfaceTexture->updateTexImage() == NO_ERROR) {
        ......

        GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget();

        LayerRenderer::updateTextureLayer(mLayer, mWidth, mHeight,
                !mBlend, forceFilter, renderTarget, transform);
    }
}
這個函數定義在文件frameworks/base/libs/hwui/DeferredLayerUpdater.cpp中。

 

DeferredLayerUpdater類的成員函數doUpdateTexImage調用成員變量mSurfaceTexture指向的一個GLConsumer對象的成員函數updateTexImage讀出可用的圖形緩沖區,並且將該圖形緩沖區封裝成一個Open GL紋理。這個Open GL紋理可以通過調用上述的GLConsumer對象的成員函數getCurrentTextureTarget獲得了。

接下來DeferredLayerUpdater類的成員函數doUpdateTexImage調用LayerRenderer類的靜態成員函數updateTextureLayer將獲得的Open GL紋理關聯給成員變量mLayer描述的一個Layer對象。

LayerRenderer類的靜態成員函數updateTextureLayer的實現如下所示:

 

void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
        bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform) {
    if (layer) {
        ......

        if (renderTarget != layer->getRenderTarget()) {
            layer->setRenderTarget(renderTarget);
            ......
        }
    }
}
這個函數定義在文件frameworks/base/libs/hwui/LayerRenderer.cpp中。

 

LayerRenderer類的靜態成員函數updateTextureLayer主要就是將參數renderTarget描述的Open GL紋理設置給參數layer描述的Layer對象。這是通過調用Layer類的成員函數setRenderTarget實現的。一個Layer對象關聯了Open GL紋理之後,以後就可以進行渲染了。

這一步執行完成之後,如果應用程序窗口存在需要更新的TextureView,那麼這些TextureView就更新完畢,也就是這些TextureView下一個可用的圖形緩沖區已經被讀出,並且封裝成了Open GL紋理。回到前面分析的DrawFrameTask類的成員函數syncFrameState中,接下來要做的事情是將Main Thread維護的Display List等信息同步到Render Thread中。這是通過調用DrawFrameTask類的成員變量mContext指向的一個CanvasContext對象的成員函數prepareTree實現的。

CanvasContext對象的成員函數prepareTree執行完畢之後,會通過參數info描述的一個TreeInfo對象返回一些同步結果:

1. 當這個TreeInfo對象的成員變量out指向的一個Out對象的成員變量hasAnimations等於true時,表示應用程序窗口存在未完成的動畫。如果這些未完成的動畫至少存在一個是非異步動畫時,上述Out對象的成員變量requiresUiRedraw的值就會被設置為true。這時候DrawFrameTask類的成員變量mSyncResult的kSync_UIRedrawRequired位就會被設置為1。所謂非異步動畫,就是那些在執行過程可以停止的動畫。這個停止執行的邏輯是由Main Thread執行的,例如,Main Thread可以響應用戶輸入停止執行一個非異步動畫。從前面分析可以知道,DrawFrameTask類的成員變量mSyncResult的值最後將會返回給Java層的ThreadedRenderer類的成員函數draw。ThreadedRenderer類的成員函數draw一旦發現該值的kSync_UIRedrawRequired位被設置為1,那麼就會向Main Thread的消息隊列發送一個INVALIDATE消息,以便在處理這個INVALIDATE消息的時候,可以響應停止執行非異步動畫的請求。

2. 當這個TreeInfo對象的成員變量prepareTextures的值等於true時,表示應用程序窗口的Display List引用到的Bitmap均已作為Open GL紋理上傳到了GPU。這意味著應用程序窗口的Display List引用到的Bitmap已全部同步完成。在這種情況下,Render Thread在渲染下一幀之前,就可以喚醒Main Thread。另一方面,如果上述TreeInfo對象的成員變量prepareTextures的值等於false,就意味著應用程序窗口的Display List引用到的某些Bitmap不能成功地作為Open GL紋理上傳到GPU,這時候Render Thread在渲染下一幀之後,才可以喚醒Main Thread,防止這些未能作為Open GL紋理上傳到GPU的Bitmap一邊被Render Thread渲染,一邊又被Main Thread修改。那麼什麼時候應用程序窗口的Display List引用到的Bitmap會不能成功地作為Open GL紋理上傳到GPU呢?一個應用程序進程可以創建的Open GL紋理是有大小限制的,如果超出這個限制,那麼就會導至某些Bitmap不能作為Open GL紋理上傳到GPU。

接下來,我們就繼續分析CanvasContext類的成員函數prepareTree的實現,以便可以了解應用程序窗口的Display List等信息的同步過程,如下所示:

 

void CanvasContext::prepareTree(TreeInfo& info) {
    ......

    info.renderer = mCanvas;
    ......

    mRootRenderNode->prepareTree(info);

    ......

    int runningBehind = 0;
    // TODO: This query is moderately expensive, investigate adding some sort
    // of fast-path based off when we last called eglSwapBuffers() as well as
    // last vsync time. Or something.
    mNativeWindow->query(mNativeWindow.get(),
            NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
    info.out.canDrawThisFrame = !runningBehind;

    if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
        if (!info.out.requiresUiRedraw) {
            // If animationsNeedsRedraw is set don't bother posting for an RT anim
            // as we will just end up fighting the UI thread.
            mRenderThread.postFrameCallback(this);
        }
    }
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

 

CanvasContext類的成員變量mRootRenderNode指向的一個RenderNode對象描述的是應用程序窗口的Root Render Node,這裡通過調用它的成員函數prepareTree開始對應用程序窗口的各個View的Display List進行同步。

在這個同步的過程中,如果某些View設置了動畫,並且這些動還未執行完成,那麼參數info指向的TreeInfo對象的成員變量hasAnimations的值就會等於true。這時候如果應用程序窗口的下一幀不可以渲染,即上述TreeInfo對象的成員變量canDrawThisFrame的值等於false,並且所有View設置的動畫都是非異步的,即上述TreeInfo對象的成員變量requiresUiRedraw的值等於false,那麼就需要解決一個問題,那些未執行完成的動畫如何繼續執行下去?因為等到當應用程序窗口的下一幀可以渲染時,這些未完成的動畫還是需要繼續執行的。

我們知道,當TreeInfo對象的成員變量requiresUiRedraw的值等於true時,Main Thread會自動發起渲染應用程序窗口的Display List的命令。在這個命令的執行過程中,未完成的動畫是可以繼續執行的。但是當TreeInfo對象的成員變量requiresUiRedraw的值等於false時,Main Thread不會自動發起渲染應用程序窗口的Display List的命令,這時候就需要向Render Thread注冊一個IFrameCallback接口,這是通過調用CanvasContext類的成員變量mRenderThread指向的一個RenderThread對象的成員函數postFrameCallback實現的

從前面Android應用程序UI硬件加速渲染環境初始化過程分析一文可以知道,注冊到Render Thread的IFrameCallback接口在下一個Vsync信號到來時,它的成員函數doFrame會被調用,這時候就可以執行渲染應用程序窗口的下一幀了。在渲染的過程中,就可以繼續執行那些未完成的動畫了。

CanvasContext類的成員變量mNativeWindow描述的就是當前綁定的應用程序窗口,通過調用它的成員函數query,並且將第二個參數設置為NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND,可以查詢到它提交了多少個圖形緩沖區還未被處理。如果這些已提交了但是還沒有被處理的圖形緩沖區大於等於2,輸出參數runningBehind就會等於true,這表明Surface Flinger太忙了,這時候應用程序窗口就應該丟棄當前幀,因此就將參數info指向的TreeInfo對象的成員變量canDrawThisFrame的值設置為false。

接下來我們繼續分析RenderNode類的成員函數prepareTree的實現,以便可以了解對應用程序窗口的各個View的Display List的同步過程,如下所示:

 

void RenderNode::prepareTree(TreeInfo& info) {
    ......

    prepareTreeImpl(info);
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

 

RenderNode類的成員函數prepareTree調用另外一個成員函數prepareTreeImpl來同步當前正在處理的Render Node的Display List等信息,後者的實現如下所示:

 

void RenderNode::prepareTreeImpl(TreeInfo& info) {
    ......

    if (info.mode == TreeInfo::MODE_FULL) {
        pushStagingPropertiesChanges(info);
    }
    uint32_t animatorDirtyMask = 0;
    if (CC_LIKELY(info.runAnimations)) {
        animatorDirtyMask = mAnimatorManager.animate(info);
    }
    ......
    if (info.mode == TreeInfo::MODE_FULL) {
        pushStagingDisplayListChanges(info);
    }
    prepareSubTree(info, mDisplayListData);
    pushLayerUpdate(info);

    ......
}

這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

從前面分析的DrawFrameTask類的成員函數run可以知道,參數info指向的TreeInfo對象的成員變量mode的值等於TreeInfo::MODE_FULL,這意味著RenderNode類的成員函數prepareTreeImpl執行在同步模式中,這時候它將會執行以下五個操作:

1. 調用成員函數pushStagingPropertiesChanges同步當前正在處理的Render Node的Property。

2. 在參數info指向的TreeInfo對象的成員變量runAnitmations的值等於true的前提下,調用成員變量mAnimatorManager指向的一個AnimatorManager對象的成員函數animate執行動畫相關的操作。

3. 調用成員函數pushStagingDisplayListChanges同步當前正在處理的Render Node的Display List。

4. 調用成員函數prepareSubTree同步當前正在處理的Render Node的Display List引用的Bitmap,以及當前正在處理的Render Node的子Render Node的Display List等信息。

5. 調用成員函數pushLayerUpdate檢查當前正在處理的Render Node是否設置了Layer。如果設置了的話,就對這些Layer進行處理。

其中,第2個操作是與動畫顯示相關的,我們在接下來的一篇文章再詳細分析。

與第1個操作相關的函數是RenderNode類的成員函數pushStagingPropertiesChanges,它的實現如下所示:

 

void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
    ......

    if (mDirtyPropertyFields) {
        mDirtyPropertyFields = 0;
        ......
        mProperties = mStagingProperties;
        ......
    }
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

 

前面提到,當RenderNode類的成員變量mDirtyPropertyFields的值不等於0時,就表明Main Thread維護的Render Node的Property發生了變化,因此就需要將它同步到Render Thread去,也就是將成員變量mStagingProperties描述的RenderProperties對象轉移到成員變量mProperties去。

與第3個操作相關的函數是RenderNode類的成員函數pushStagingDisplayListChanges,它的實現如下所示:

 

void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
    if (mNeedsDisplayListDataSync) {
        mNeedsDisplayListDataSync = false;
        ......
        deleteDisplayListData();
        mDisplayListData = mStagingDisplayListData;
        mStagingDisplayListData = NULL;
        if (mDisplayListData) {
            for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
                (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, NULL);
            }
        }
        ......
    }
}

 

這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

前面提到,當RenderNode類的成員變量mNeedsDisplayListDataSync的值等於true時,就表明Main Thread維護的Render Node的Display List發生了變化,因此就需要將它同步到Render Thread去,也就是將成員變量mStagingDisplayListData描述的DisplayListData對象轉移到成員變量mDisplayListData去。

在將成員變量mStagingDisplayListData描述的DisplayListData對象轉移到成員變量mDisplayListData去之前,首先會調用成員函數deleteDisplayListData刪除成員變量mDisplayListData原先描述的DisplayListData對象。

記錄在Display List Data的繪制命令除了是一些普通的DrawOp之外,還可能是一些函數指針,這些函數指針保存在Display List Data的成員變量functors描述的一個Vector中。這些函數指針是做什麼用的呢?有些繪制命令很復雜,是不能通過一個簡單的DrawOp來描述的,例如它是由一系列簡單的繪制命令以復雜方式組合在一起形成的。對於這些復雜的繪制命令,我們就可以通過一個函數指針來描述。當Render Thread渲染應用程序窗口的Display List遇到這些函數指針時,就會調用這些函數指針指向的函數,這樣這些函數就可以在其內部實現復雜的繪制命令,或者說是完成自定義的繪制命令。

這些函數指針在應用程序窗口的Display List的渲染過程中,會被調用兩次。第一次調用時,第一個參數指定為DrawGlInfo::kModeSync,表示這時候它可以執行一些同步相關的操作。第二次調用時,第二個參數指定為DrawGlInfo::kModeDraw,表示這時候可以執行一些與渲染相關的操作。

此外,我們還可以通過Java層的ThreadedRenderer類的靜態成員函數invokeFunctor將一個函數指定在Render Thread執行。例如,我們希望在應用程序進程中執行一些Open GL相關的操作時,就可以將這些操作封裝在一個函數中,並且將該函數的地址封裝成一個Task發送到Render Thread的Task Queue中。當這個Task被Render Thread處理的時候,封裝在這個Task裡面的函數就會被執行。這時候傳遞給這些函數的第一個參數就為DrawGlInfo::kModeProcess或者DrawGlInfo::kModeProcessNoContext。其中,DrawGlInfo::kModeProcess表示Render Thread已經初好了Open GL環境,而DrawGlInfo::kModeProcessNoContext表示Render Thread還沒有初始化Open GL環境。

將函數指針記錄Display List中交給Main Thread和Render Thread執行以及將函數指針封裝成Task交給Render Thread執行的設計主要是為了實現WebView功能的。Android系統從4.4開始,通過Chromium來實現WebView的功能。Chromium有一套非常復雜的網頁渲染機制,當它通過WebView嵌入在應用程序進程執行時,就會需要利用Render Thread可以執行Open GL操作的能力來完成它自己的功能。由這些網頁渲染操作很復雜,因此就最好是通過函數來描述,這樣就產生了能夠將函數指定在Render Thread執行的需求。以後如果有機會分析WebView然Chromium版實現,我們就會看到這一套機制是如何運行的。

回到RenderNode類的成員函數prepareTree中,與第4個操作相關的函數是RenderNode類的成員函數prepareSubTree,它的實現如下所示:

 

void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
    if (subtree) {
        TextureCache& cache = Caches::getInstance().textureCache;
        ......
        if (subtree->ownedBitmapResources.size()) {
            info.prepareTextures = false;
        }
        for (size_t i = 0; info.prepareTextures && i < subtree->bitmapResources.size(); i++) {
            info.prepareTextures = cache.prefetchAndMarkInUse(subtree->bitmapResources[i]);
        }
        for (size_t i = 0; i < subtree->children().size(); i++) {
            DrawRenderNodeOp* op = subtree->children()[i];
            RenderNode* childNode = op->mRenderNode;
            ......
            childNode->prepareTreeImpl(info);
            .....
        }
    }
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

前面提到,Display List引用的Bitmap保存在它的成員變量ownedBitmapResources和bitmapResources的兩個Vector中。其中,保存在Display List的成員變量ownedBitmapResources中的Bitmap的底層儲存是由應用程序提供和管理的。這意味著很難維護該底層儲存在Main Thread和Render Thread的一致性。例如,有可能應用程序自行修改了該底層儲存的內容,但是又沒有通知Render Thread進行同步。因此,當存在這樣的Bitmap時,就不允許Render Thread在渲染完成應用程序窗口的一幀之前喚醒Main Thread,就是為了防止Main Thread會修改上述Bitmap的底層儲存。為了達到這個目的,這時候就需要將參數info指向的一個TreeInfo對象的成員變量prepareTextures的值設置為false。

另一方面,保存在Display List的成員變量bitmapResources中的Bitmap的底層儲存不是由應用程序提供和管理的,因此就能夠保證它不會被隨意修改而又不通知Render Thread進行同步。對於這些Bitmap,就可以將它們作為Open GL紋理上傳到GPU去。這就相當於是將Bitmap從Main Thread同步到Render Thread中,因為Render Thread就通過已經上傳到GPU的Open GL紋理來使用這些Bitmap。能夠執行這樣的同步操作的前提是Display List的成員變量ownedBitmapResources描述的Vector為空。因為當Display List的成員變量ownedBitmapResources描述的Vector不為空時,Main Thread和Render Thread在渲染應用程序窗口的一幀時是完全同步的,因此就沒有必要將Bitmap從Main Thread同步到Render Thread去。

此外,對於保存在Display List的成員變量bitmapResources中的Bitmap,由於內存大小的限制,因此就不是所有的這些Bitmap都是能夠作為Open GL紋理上傳到GPU去的。一旦某一個Bitmap不能作為Open GL紋理上傳到GPU去,那麼也是需要完全同步Main Thread和Render Thread渲染應用程序窗口的一幀的。這時候就需要將參數info指向的一個TreeInfo對象的成員變量prepareTextures的值設置為false。

同步完成當前正在處理的Render Node的Display List引用的Bitmap之後,接下來RenderNode類的成員函數prepareSubTree就調用前面分析過的成員函數prepareTreeImpl來同步當前在處理的Render Node的子Render Node的Display List、Property和Display List引用的Bitmap等信息。這個過程是一直歸遞執行下去,直到應用程序窗口視圖結構中的每一個Render Node的isplay List、Property和Display List引用的Bitmap等信息都從Main Thread同步到Render Thread為止。

上面提到的將Bitmap封裝成Open GL紋理上傳到GPU是通過調用TextureCache類的成員函數prefetchAndMarkInUse來實現的,如下所示:

 

bool TextureCache::prefetchAndMarkInUse(const SkBitmap* bitmap) {
    Texture* texture = getCachedTexture(bitmap);
    if (texture) {
        texture->isInUse = true;
    }
    return texture;
}
這個函數定義在文件frameworks/base/libs/hwui/TextureCache.cpp中。

 

TextureCache類的成員函數prefetchAndMarkInUse調用另外一個成員函數getCachedTexture將參數bitmap描述的Bitmap封裝成Open Gl紋理上傳到GPU中。如果能夠上傳成功,那麼就可以獲得一個Texture對象。TextureCache類的成員函數prefetchAndMarkInUse在將這個Texture對象返回給調用者之前,需要將它的成員變量isInUse設置為true,表示該Texture對象正在使用當中。

TextureCache類的成員函數getCachedTexture的實現如下所示:

 

Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) {
    Texture* texture = mCache.get(bitmap->pixelRef());

    if (!texture) {
        if (!canMakeTextureFromBitmap(bitmap)) {
            return NULL;
        }

        const uint32_t size = bitmap->rowBytes() * bitmap->height();
        bool canCache = size < mMaxSize;
        // Don't even try to cache a bitmap that's bigger than the cache
        while (canCache && mSize + size > mMaxSize) {
            Texture* oldest = mCache.peekOldestValue();
            if (oldest && !oldest->isInUse) {
                mCache.removeOldest();
            } else {
                canCache = false;
            }
        }

        if (canCache) {
            texture = new Texture();
            texture->bitmapSize = size;
            generateTexture(bitmap, texture, false);

            mSize += size;
            ......
            mCache.put(bitmap->pixelRef(), texture);
        }
    } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
        // Texture was in the cache but is dirty, re-upload
        // TODO: Re-adjust the cache size if the bitmap's dimensions have changed
        generateTexture(bitmap, texture, true);
    }

    return texture;
}
這個函數定義在文件frameworks/base/libs/hwui/TextureCache.cpp中。

 

每一個Bitmap作為Open GL紋理上傳到GPU後,都會為其創建一個Texture對象。這些Texture對象就保存在TextureCache類通過成員變量mCache指向的一個LruCache中。因此,當不能夠在該LruCache中找到參數bitmap描述的Bitmap對應的Texture對象時,就說明該Bitmap還未作為Open GL紋理上傳到過GPU中,因此接下來就需要將它作為Open GL紋理上傳到GPU去。

但是參數bitmap描述的Bitmap卻不一定能夠成功作為Open GL紋理上傳到GPU去,有兩個原因:

1. Bitmap太大,超出預先設定的最大Open GL紋理的大小。這種情況通過調用TextureCache類的成員函數canMakeTextureFromBitmap進行判斷。

2. 已經作為Open GL紋理上傳到GPU的Bitmap太多,超出預先設定的最多可以上傳到GPU的大小。

在第2種情況下,這時候TextureCache類的成員函數getCachedTexture會嘗試刪掉那些最早上傳到GPU的現在還不處於使用狀態的Open GL紋理,直到能滿足將參數bitmap描述的Bitmap作為Open GL紋理上傳到GPU為止。

一旦確定能夠將參數bitmap描述的Bitmap作為Open GL紋理上傳到GPU,那麼就會調用TextureCache類的成員函數generateTexture執行具體的操作,並且創建為其創建一個Texture對象保存在成員變量mCache指向的一個LruCache中。

另一方面,如果參數bitmap描述的Bitmap之前已經作為Open GL紋理上傳到過GPU中,由於現在它的內容可能已經發生了變化,因此也需要調用TextureCache類的成員函數generateTexture執行重新上傳的操作。

回到RenderNode類的成員函數prepareTree中,與第5個操作相關的函數是RenderNode類的成員函數pushLayerUpdate,它的實現如下所示:

 

void RenderNode::pushLayerUpdate(TreeInfo& info) {
    LayerType layerType = properties().layerProperties().type();
    ......
    if (CC_LIKELY(layerType != kLayerTypeRenderLayer) || CC_UNLIKELY(!isRenderable())) {
        if (CC_UNLIKELY(mLayer)) {
            LayerRenderer::destroyLayer(mLayer);
            mLayer = NULL;
        }
        return;
    }

    ......

    if (!mLayer) {
        mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight());
        ......
    } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
        if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
            ......
        }
        ......
    }

    SkRect dirty;
    info.damageAccumulator->peekAtDirty(&dirty);

    ......

    if (dirty.intersect(0, 0, getWidth(), getHeight())) {
        ......
        mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
    }

    ......

    if (info.renderer && mLayer->deferredUpdateScheduled) {
        info.renderer->pushLayerUpdate(mLayer);
    }

    ......
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

在分析RenderNode類的成員函數pushLayerUpdate的實現之前,我們首先要理解什麼情況下一個Render Node會被設置為一個Layer。

當一個View的類型被設置為LAYER_TYPE_HARDWARE時,如果它的成員函數buildLayer被調用,那麼與它關聯的Render Node就會被設置為一個Layer。這意味著該View將會作為一個FBO(Frame Buffer Object)進行渲染。這樣做主要是為了更流暢地顯示一個View的動畫。這一點我們在前面Android應用程序UI硬件加速渲染技術簡要介紹和學習計劃一文中曾經提到。

有了這個背景知識之後,我們就可以分析RenderNode類的成員函數pushLayerUpdate的實現了。

RenderNode類的成員函數pushLayerUpdate首先是判斷當前正在處理的Render Node的Layer Type是否為kLayerTypeRenderLayer,也就是判斷與它關聯的View的類型是否設置為LAYER_TYPE_HARDWARE。如果不是,那麼就不用往下執行了,因為這種情況當前正在處理的Render Node不可能設置為一個Layer。

另一方面,如果當前正在處理的Render Node的Display List還沒有創建或者是空的,那麼RenderNode類的成員函數pushLayerUpdate也不用往下執行了,因為這種情況當前正在處理的Render Node是無需要渲染的。判斷當前正在處理的Render Node的Display List有沒有創建或者是不是空的,可以通過調用RenderNode類的成員函數isRenderable來實現。

在上述兩種情況下,RenderNode類的成員函數pushLayerUpdate在返回之前,會判斷一下之前是否已經為當前正在處理的Render Node創建過Layer。如果創建過,那麼就會調用LayerRenderer類的靜態成員函數destroyLayer來銷毀該Layer。

接下來就是當前正在處理的Render Node需要設置為一個Layer的情況了。如果當前正在處理的Render Node還沒有設置過Layer,也就是它的成員變量mLayer的值等於NULL,那麼就調用LayerRenderer類的靜態成員函數createRenderLayer為它設置一個,也就是創建一個Layer對象,並且保存在它的成員變量mLayer中。

另一方面,如果當前正在處理的Render Node之前已經設置過Layer,但是該Layer的大小與當前正在處理的Render Node的大小不一致,那麼就需要調用LayerRenderer類的靜態成員函數resizeLayer調整廖Layer的大小。

再接下來是計算當前正在處理的Render Node是否在應用程序窗口當前要更新的髒區域中。如果在的話,那麼就需要調用與它關聯的Layer對象的成員函數updateDeferred來標記與它關聯的Layer對象是需要進行更新處理的。

Layer類的成員函數updateDeferred的實現如下所示:

 

void Layer::updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom) {
    requireRenderer();
    this->renderNode = renderNode;
    const Rect r(left, top, right, bottom);
    dirtyRect.unionWith(r);
    deferredUpdateScheduled = true;
}
這個函數定義在文件frameworks/base/libs/hwui/Layer.cpp中。

 

Layer類的成員函數updateDeferred首先是調用另外一個成員函數requireRenderer檢查當前正在處理的Layer對象是否已經創建有一個LayerRenderer對象了。這個LayerRenderer對象就是負責渲染當前正在處理的Layer對象的。如果還沒有創建,那麼就需要創建。如下 所示:

 

void Layer::requireRenderer() {
    if (!renderer) {
        renderer = new LayerRenderer(renderState, this);
        ......
    }
}
這個函數定義在文件frameworks/base/libs/hwui/Layer.cpp中。

 

Layer類的成員函數updateDeferred接下來再記錄當前正在處理的Layer對象關聯的Render Node,並且更新它的髒區載,最後將成員變量deferredUpdateScheduled設置為true,表示當前正在處理的Layer對象後面還需要執行真正的更新操作,而這裡只是記錄了相關的更新狀態信息而已。

這一步執行完成後,回到RenderNode類的成員函數pushLayerUpdate中,這時候成員變量mLayer指向的Layer對象的成員變量deferredUpdateScheduled的值是等於true的,並且參數info指向的一個TreeInfo對象的成員變量renderer的值不為空,它指向了一個OpenGLRenderer對象,因此接下來就會調用該OpenGLRenderer對象的成員函數pushLayerUpdate來將成員變量mLayer指向的Layer對象記錄在內部的一個待更新的Layer列表中,如下所示:

 

void OpenGLRenderer::pushLayerUpdate(Layer* layer) {
    if (layer) {
        ......
        for (int i = mLayerUpdates.size() - 1; i >= 0; i--) {
            if (mLayerUpdates.itemAt(i) == layer) {
                return;
            }
        }
        mLayerUpdates.push_back(layer);
        ......
    }
}
這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

 

OpenGLRenderer類將需要進行更新處理的Layer對象保存在成員變量mLayerUpdates描述的一個Vector中,保存在這個Vector中的Layer對象在渲染應用程序窗口的Display List的時候,就是需要進行更新處理的。

這一步執行完成之後,應用程序窗口的Display List等信息就從Main Thread同步到Render Thread了,回到DrawFrameTask類的成員函數run中,接下來就可以調用CanvasContext類的成員函數draw渲染應用程序窗口的Display List了。

CanvasContext類的成員函數draw的實現如下所示:

 

void CanvasContext::draw() {
    ......

    SkRect dirty;
    mDamageAccumulator.finish(&dirty);

    ......

    status_t status;
    if (!dirty.isEmpty()) {
        status = mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
                dirty.fRight, dirty.fBottom, mOpaque);
    } else {
        status = mCanvas->prepare(mOpaque);
    }

    Rect outBounds;
    status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);

    ......

    mCanvas->finish();

    ......

    if (status & DrawGlInfo::kStatusDrew) {
        swapBuffers();
    }

    ......
}

這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

CanvasContext類的成員函數draw的執行過程如下所示:

1. 獲得應用程序窗口要更新的髒區域之後,調用成員變量mCanvas指向的一個OpenGLRenderer對象的成員函數prepareDirty或者prepare執行一些初始化工作,取決於髒區域是不是空的。

2, 調用成員變量mCanvas指向的一個OpenGLRenderer對象的成員函數drawRenderNode渲染成員變量mRootRenderNode描述的應用程序窗口的Root Render Node的Display List。

3. 調用成員變量mCanvas指向的一個OpenGLRenderer對象的成員函數finish執行一些清理工作。在這一步中,如果開啟了OverDraw,那麼還會在應用程序窗口的上面繪制一些描述OverDraw的顏色塊。

4. 調用另外一個成員函數swapBuffers將前面已經繪制好的圖形緩沖區提交給Surface Flinger合成和顯示。

在上述四個步驟中,最重要的是第1步和第2步,因此接下來我們就分別對它們進行分析。

我們假設第1步得到的應用程序窗口要更新的髒區域不為空,因此這一步執行的就是OpenGLRenderer類的成員函數prepareDirty,它的實現如下所示:

 

status_t OpenGLRenderer::prepareDirty(float left, float top,
        float right, float bottom, bool opaque) {

    setupFrameState(left, top, right, bottom, opaque);

    ......
    if (currentSnapshot()->fbo == 0) {
        ......
        updateLayers();
    } else {
        return startFrame();
    }

    return DrawGlInfo::kStatusDone;
}
這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

 

OpenGLRenderer類的成員函數prepareDirty首先是調用另外一個成員函數setupFrameState設置幀狀態,它的實現如下所示:

 

void OpenGLRenderer::setupFrameState(float left, float top,
        float right, float bottom, bool opaque) {
    ......
    initializeSaveStack(left, top, right, bottom, mLightCenter);
    ......
}
這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

 

OpenGLRenderer類的成員函數setupFrameState最主要的操作是調用另外一個成員函數initializeSaveStack初始化一個Save Stack。

OpenGLRenderer類的成員函數initializeSaveStack是從父類StatefulBaseRenderer繼承下來的,它的實現如下所示:

 

void StatefulBaseRenderer::initializeSaveStack(float clipLeft, float clipTop,
        float clipRight, float clipBottom, const Vector3& lightCenter) {
    mSnapshot = new Snapshot(mFirstSnapshot,
            SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
    mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom);
    mSnapshot->fbo = getTargetFbo();
    mSnapshot->setRelativeLightCenter(lightCenter);
    mSaveCount = 1;
}
這個函數定義在文件frameworks/base/libs/hwui/StatefulBaseRenderer.cpp中。

 

StatefulBaseRenderer類內部維護有一個Save Stack。這個Save Stack由一系列的Snapshot組成,其中最頂端的Snapshot,也就是當前使用的Snapshot,保存成員變量mSnapshot中。每一個Snapshot都是用來描述當前的一個渲染狀態,例如偏移位置、裁剪區間、燈光位置等。

Snapshot有一個重要的成員變量fbo。當它的值大於0的時候,就表示要將UI渲染在一個FBO上。涉及到渲染UI的Renderer有兩個,一個是LayerRenderer,另外一個是OpenGLRenderer。從前面的分析可以知道,LayerRenderer主要負責用來渲染類型為LAYER_TYPE_HARDWARE的View。這些View將會渲染在一個FBO上。OpenGLRenderer負責渲染應用程序窗口的Display List。這個Display List是直接渲染在Frame Buffer上的,也就是直接渲染在從Surface Flinger請求回來的圖形緩沖區上。

LayerRenderer類繼承於OpenGLRenderer類,OpenGLRenderer類又繼承於StatefulBaseRenderer類。StatefulBaseRenderer類的成員函數getTargetFbo是一個虛函數,LayerRenderer類和OpenGLRenderer類都重寫了它。

其中,OpenGLRenderer類的成員函數getTargetFbo的實現如下所示:

 

class OpenGLRenderer : public StatefulBaseRenderer {
    ......

    virtual GLuint getTargetFbo() const {
        return 0;
    }

    ......
}
這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.h。

 

從這裡就可以看到,OpenGLRenderer類的成員函數getTargetFbo的返回值總是0,也就是說,OpenGLRenderer類總是直接將UI渲染在Frame Buffer上。

LayerRenderer類的成員函數getTargetFbo的實現如下所示:

 

GLuint LayerRenderer::getTargetFbo() const {
    return mLayer->getFbo();
}
這個函數定義在文件frameworks/base/libs/hwui/LayerRenderer.cpp。

 

LayerRenderer類的成員變量mLayer描述的是一個Layer對象。這個Layer對象關聯有一個FBO對象,可以通過調用它的成員函數getFbo獲得。獲得FBO被LayerRenderer類的成員函數getTargetFbo返回給調用者。

回到前面分析的StatefulBaseRenderer類的成員函數initializeSaveStack中,從前面的調用過程可以知道,當前正在處理的是一個OpenGLRenderer對象,因此,成員變量mSnapshot指向的一個Snapshot對象的成員變量fbo的值等於0。

StatefulBaseRenderer類的成員函數initializeSaveStack執行完成後,回到OpenGLRenderer類的成員函數prepareDirty中,它調用另外一個成員函數currentSnapshot獲得的就是父類StatefulBaseRenderer的成員變量mSnapshot描述的Snapshot對象。這個Snapshot對象的成員變量fbo的值是等於0的,因此接下來就會繼續調用OpenGLRenderer類的成員函數updateLayers更新那些待更析的Layer對象。

另一方面,如果當前正在處理的是一個LayerRenderer對象,那麼OpenGLRenderer類的成員函數prepareDirty調用的是另外一個成員函數startFrame執行一些Open GL初始化工作,例如設置View Port等基本操作。

由於當前正在處理的是一個OpenGLRenderer對象,因此我們接下來繼續分析OpenGLRenderer類的成員函數updateLayers的實現,如下所示:

 

void OpenGLRenderer::updateLayers() {
    ......
    int count = mLayerUpdates.size();
    if (count > 0) {
        ......

        for (int i = 0; i < count; i++) {
            Layer* layer = mLayerUpdates.itemAt(i);
            updateLayer(layer, false);
            ......
        }

        ......
    }
}
這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

 

前面提到,OpenGLRenderer類的成員變量mLayerUpdates描述的一個Vector保存的都是那些需要更新的Layer對象。每一個Layer對象的更新是通過調用OpenGLRenderer類的另外一個成員函數updateLayer實現的。

OpenGLRenderer類的成員函數updateLayer的實現如下所示:

 

bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
    if (layer->deferredUpdateScheduled && layer->renderer
            && layer->renderNode.get() && layer->renderNode->isRenderable()) {
        ......

        if (CC_UNLIKELY(inFrame || mCaches.drawDeferDisabled)) {
            layer->render(*this);
        } else {
            layer->defer(*this);
        }

        ......

        return true;
    }

    return false;
}
這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。

 

從前面的分析可以知道,保存在OpenGLRenderer類的成員變量mLayerUpdates描述的一個Vector中的Layer對象,它的成員變量deferredUpdateScheduled的值是等於true的。當這些Layer對象設置有自己的Renderer,以及關聯有Render Node,並且這個Render Node是可渲染的時候,就會調用它們的成員函數render進行直接更新,或者調用它們的成員函數defer進行延遲更新。

當參數inFrame的值等於true,或者OpenGLRenderer類的成員變量mCaches指向的一個Caches對象的成員變量drawDeferDisabled的值等於true時,就會調用Layer類的成員函數render進行直接更新。其中,Caches類的成員變量drawDeferDisabled用來描述是否要對Open GL操作進行合並。當它的值等於true時,就表示不要合並;否則就表示需要合並。關於Open GL操作的合並,我們在前面Android應用程序UI硬件加速渲染的預加載資源地圖集服務(Asset Atlas Service)分析一文中有提到。

我們假設Open GL操作需要進行合並,即OpenGLRenderer類的成員變量mCaches指向的一個Caches對象的成員變量drawDeferDisabled等於false。從前面的調用過程可以知道,參數inFrame的值也是等於false,因此接下來OpenGLRenderer類的成員函數updateLayer就會調用Layer類的成員函數defer對參數layer描述的一個Layer對象進行更新。

Layer類的成員函數defer的實現如下所示:

 

void Layer::defer(const OpenGLRenderer& rootRenderer) {
    ......

    delete deferredList;
    deferredList = new DeferredDisplayList(dirtyRect);

    DeferStateStruct deferredState(*deferredList, *renderer,
            RenderNode::kReplayFlag_ClipChildren);

    ......

    renderNode->computeOrdering();
    renderNode->defer(deferredState, 0);

    deferredUpdateScheduled = false;
}
這個函數定義在文件frameworks/base/libs/hwui/Layer.cpp中。

 

Layer類的成員函數defer的主要工作是創建一個DeferredDisplayList對象,保存在成員變量deferredList中,然後再將該DeferredDisplayList對象封裝成一個DeferStateStruct對象中。同時被封裝成這個DeferStateStruct對象還有Layer類的成員變量renderer描述的一個LayerRenderer對象。

Layer類的成員變量renderNode描述的是當前正在處理的Layer對象所關聯的Render Node。Layer類的成員函數defer接下來就分別調用這個Render Node的成員函數computeOrdering和defer。其中,調用Render Node的成員函數defer的時候,會將前面創建的DeferStateStruct對象作為參數傳遞進去。

調用一個Render Node的成員函數computeOrdering,是為了找出那些需要投影到它的Background進行渲染的子Render Node。這些子Render Node稱為Projected Node,如下所示:

/

圖3 Projection Nodes

Projection Node的解釋可以參考前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文,RenderNode類的成員函數computeOrdering的實現我們也留給讀者自己去分析。最終如果一個Rende Node具有Projected Node,那麼這些Projected Node就會保存在它的成員變量mProjectedNodes中。

回到前面分析的Layer類的成員函數defer中,接下來它要調用的是RenderNode類的成員函數defer,它的實現如下所示:

 

void RenderNode::defer(DeferStateStruct& deferStruct, const int level) {
    DeferOperationHandler handler(deferStruct, level);
    issueOperations(deferStruct.mRenderer, handler);
}

這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

RenderNode類的成員函數defer調用另外一個成員函數issueOperations對當前正在處理的Render Node的Display List的繪制命令進行處理,具體的處理是由第二個參數指定的一個DeferOperationHandler對象的操作符重載函數()執行的,如下所示:

 

class DeferOperationHandler {
public:
    DeferOperationHandler(DeferStateStruct& deferStruct, int level)
        : mDeferStruct(deferStruct), mLevel(level) {}
    inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
        operation->defer(mDeferStruct, saveCount, mLevel, clipToBounds);
    }
    ......
private:
    DeferStateStruct& mDeferStruct;
    const int mLevel;
};
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

 

參數operation指向的就是當前正在處理的Render Node的Display List的一個繪制命令,這裡調用它的成員函數defer執行我們在前面Android應用程序UI硬件加速渲染的預加載資源地圖集服務(Asset Atlas Service)分析一文提到的繪制命令合並操作。

接下來,我們首先分析RenderNode類的成員函數issueOperations,然後再分析一個典型的DisplayListOp的成員函數defer的實現。

RenderNode類的成員函數issueOperations的實現如下所示:

 

template 
void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
    ......

    const bool drawLayer = (mLayer && (&renderer != mLayer->renderer));
    ......

    bool quickRejected = properties().getClipToBounds()
            && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
    if (!quickRejected) {
        ......

        if (drawLayer) {
            handler(new (alloc) DrawLayerOp(mLayer, 0, 0),
                    renderer.getSaveCount() - 1, properties().getClipToBounds());
        } else {
            ......
            for (size_t chunkIndex = 0; chunkIndex < mDisplayListData->getChunks().size(); chunkIndex++) {
                const DisplayListData::Chunk& chunk = mDisplayListData->getChunks()[chunkIndex];

                Vector zTranslatedNodes;
                buildZSortedChildList(chunk, zTranslatedNodes);

                issueOperationsOf3dChildren(kNegativeZChildren,
                        initialTransform, zTranslatedNodes, renderer, handler);


                for (int opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
                    DisplayListOp *op = mDisplayListData->displayListOps[opIndex];
                    ......
                    handler(op, saveCountOffset, properties().getClipToBounds());

                    if (CC_UNLIKELY(!mProjectedNodes.isEmpty() && opIndex == projectionReceiveIndex)) {
                        issueOperationsOfProjectedChildren(renderer, handler);
                    }
                }

                issueOperationsOf3dChildren(kPositiveZChildren,
                        initialTransform, zTranslatedNodes, renderer, handler);
            }
        }
    }

    ......
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

 

RenderNode類的成員函數issueOperations執行的操作就是對當前正在處理的Render Node的Display List的繪制命令進行重排。為什麼需要重排呢?在前面Android應用程序UI硬件加速渲染的Display List構建過程分析一文中,我們分析Display List的結構,如圖4所示:

/

 

圖4 Display List Display List的繪制命令以Chunk為單位進行保存。每一個Chunk通過begin op index和end op index描述的一系列Display List Op對應的就是一個Render Node包含繪制命令。此外,每一個Chunk還通過begin child index和end child index描述的一系列Draw Render Node Op對應的就是一個Render Node的子Render Node相關的繪制命令。這些子Render Node的Z軸位置相對父Render Node有可能是負的,也有可能是正的。對於Z軸位置為負的子Render Node的繪制命令,它們應該先於父Render Node的繪制命令執行。而對於Z軸位置為正的子Render Node的繪制命令,它們應該後於父Render Node的繪制命令執行。因此,每一個Chunk描述的繪制命令的排列順序就如下所示:

 

1. Z軸位置為負的子Render Node的繪制命令。

2. 父Render Node的繪制命令。

3. Z軸位置為正的子Render Node的繪制命令。

此外,如果一個Render Node的的某一個Display List Op恰好是一個圖3所示的Projection Receiver,那麼還需要Render Node的所有Projected Node的繪制命令排列在該Projection Receiver的後面。

如果一個Render Node設置了Layer,那麼就意味著這個Render Node的所有繪制命令都是作為一個整體進行執行的。也就是說,對於設置了Layer的Render Node,我們首先需要將它的Display List的所有繪制命令合成一個整體的繪制命令,目的就是為了得到一個FBO,然後渲染這個FBO就可以得一個Render Node的UI。

對於設置了Layer的Render Node來說,它的成員函數defer會被調用兩次。第一次調用的時候,就是為了將它的Display List的所有繪制命令合成一個FBO。第二次調用的時候,就是為了將合成後的FBO渲染到應用程序窗口的UI上。

這時候RenderNode類的成員函數defer屬於第一次執行。那麼RenderNode類的成員函數issueOperations是如何區分它是被第一次調用的成員函數defer調用,還是第二次調用的成員函數defer調用呢?主要是通過比較參數renderer描述的OpenGLRender對象和成員變量mLayer指向的一個Layer對象的成員變量renderer描述折一個OpenGLRender對象來區分。如果這兩個OpenGLRenderer對象是同一個,就意味著是被第一次調用的成員函數defer調用;否則的話,就是被第二次調用的成員函數defer調用。

當RenderNode類的成員函數issueOperations是被第二次調用的成員函數defer調用的時候,該Render Node的Display List的所有繪制命令已經被合成在一個FBO裡面,並且這個FBO是由它所關聯的Layer對象維護的,因此這時候只需要將該Layer對象封裝成一個DrawLayerOp交給參數handler描述的一個DeferOperationHandler對象處理即可。

我們再確認一下現在RenderNode類的成員函數issueOperations是被第一次調用的成員函數defer調用。它的參數renderer指向的一個OpenGLRenderer對象是從Layer類的成員函數defer傳遞進行的,而Layer類的成員函數defer傳遞進行的這個OpenGLRenderer對象就正好是與Render Node關聯的Layer對象的成員變量renderer描述折一個OpenGLRender對象,因此它們就是相同的。從前面的分析可以知道,這個OpenGLRenderer對象的實際類型是LayerRenderer。

後面我們會看到,當Render Node的成員函數issueOperations是被第二次調用的成員函數defer調用的時候,它的參數renderer指向的一個OpenGLRenderer對象的實際類型就是OpenGLRenderer,它與當前正在處理的Render Node關聯的Layer對象的成員變量描述折一個OpenGLRender對象不可能是相同的,因為後者的實際類型是LayerRenderer。

接下來我們就繼續分析RenderNode類的成員函數issueOperations是被第一次調用的成員函數defer調用時的執行情況,這時候得到的本地變量drawLayer的值為false。

RenderNode類的成員函數issueOperations首先是判斷當前正在處理的Render Node的占據的屏幕位置在應用程序窗口的當前幀中是否是可見的。如果不可見,那麼得到的本地變量quickRejected的值就等於true。在這種情況下就不用做任何事情。

當本地變量quickRejected的值就等於false,並且本地變量drawLayer的值也等於false的時候,RenderNode類的成員函數issueOperations就對當前正在處理的Render Node的Display List的所有繪制命令按照我們上面描述的規則進行排序。

RenderNode類的成員函數issueOperations通過一個for循環對當前正在處理的Render Node的Display List的繪制命令按Chunk進行處理。對於每一個Chunk:

1. 調用成員函數buildZSortedChildList對其子Render Node相關的Draw Render Node Op按照Z軸位置從小到大的順序排列在本地變量zTranslatedNodes描述的一個Vector中。

2. 調用成員函數issueOperationsOf3dChildren將Z軸位置為負的子Render Node相關的Draw Render Node Op交給參數handler描述的一個DeferOperationHandler對象處理。

3. 通過一個for循環依次將當前正在處理的Render Node相關的Display List Op交給參數handler描述的一個DeferOperationHandler對象處理。如果其中的某一個Display List Op是一個Projection Receiver,那麼就繼續調用成員函數issueOperationsOfProjectedChildren將當前正在處理的Render Node的Projected Node交給參數handler描述的一個DeferOperationHandler對象處理。

4. 調用成員函數issueOperationsOf3dChildren將Z軸位置為正的子Render Node相關的Draw Render Node Op交給參數handler描述的一個DeferOperationHandler對象處理。

接下來我們繼續分析RenderNode類的成員函數issueOperationsOf3dChildren和issueOperationsOfProjectedChildren的實現。

RenderNode類的成員函數issueOperationsOf3dChildren的實現如下所示:

 

template 
void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
        const Matrix4& initialTransform, const Vector& zTranslatedNodes,
        OpenGLRenderer& renderer, T& handler) {
    const int size = zTranslatedNodes.size();
    ......

    const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
    size_t drawIndex, shadowIndex, endIndex;
    if (mode == kNegativeZChildren) {
        drawIndex = 0;
        endIndex = nonNegativeIndex;
        shadowIndex = endIndex; // draw no shadows
    } else {
        drawIndex = nonNegativeIndex;
        endIndex = size;
        shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
    }

    ......

    while (shadowIndex < endIndex || drawIndex < endIndex) {
       ......

        DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
        ......
        childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
        handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
        childOp->mSkipInOrderDraw = true;
        ......
        drawIndex++;
    }
    
    ......
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

RenderNode類的成員函數issueOperationsOf3dChildren既用來處理Z軸位置為負的子Render Node相關的Draw Render Node Op,也用來處理Z軸位置為正的子Render Node相關的Draw Render Node Op,因此它就需要根據參數mode以及參數zTranslatedNodes描述的一個Vector中Z軸位置為非負的子Render Node相關的Draw Render Node Op的索引nonNegativeIndex來確定當前需要處理的子Render Node相關的Draw Render Node Op。

由於參數zTranslatedNodes描述的一個Vector中的Draw Render Node Op是按照它們對應的子Render Node的Z軸位置由小到大的順序排列的,因此如果參數mode的值等於kNegativeZChildren,那麼當前需要處理的Draw Render Node Op在參數zTranslatedNodes描述的一個Vector中的索引范圍就為[0, nonNegativeIndex)。另一方面,如果參數mode的值等於kPositiveZChildren,,那麼當前需要處理的Draw Render Node Op在參數zTranslatedNodes描述的一個Vector中的索引范圍就為[nonNegativeIndex, size),其中,size為參數zTranslatedNodes描述的一個Vector的大小。

確定了要處理的Draw Render Node Op在參數zTranslatedNodes描述的一個Vector的范圍之後,就可以通過一個while循環對它們進行處理了,處理的方式就將它們交給參數handler描述的一個DeferOperationHandler對象。

在將要處理的Draw Render Node Op交給參數handler描述的一個DeferOperationHandler對象處理之前,有一個小Hack,這些Draw Render Node Op的成員變量mSkipInOrderDraw的值設置為false,處理完成之後再恢復為true。這樣做的目的是為了當前正在處理的Render Node以相同的方式遞歸處理其子Render Node的Display List的繪制命令。我們在後面將會看到這一點。

我們再來看RenderNode類的成員函數issueOperationsOfProjectedChildren的實現,如下所示:

 

template 
void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& handler) {
    ......

    // draw projected nodes
    for (size_t i = 0; i < mProjectedNodes.size(); i++) {
        DrawRenderNodeOp* childOp = mProjectedNodes[i];
        ......
        childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
        handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
        childOp->mSkipInOrderDraw = true; 
        ......
    }

    ......
}
這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。

 

RenderNode類的成員函數issueOperationsOfProjectedChildren主要就是將成員變量mProjectedNodes描述的一個Vector中的所有Draw Render Node Op都交給參數handler描述的一個DeferOperationHandler對象處理。其中,RenderNode類的成員變量mProjectedNodes描述的一個Vector應該包含哪些Projected Node就是在Layer類的成員函數defer中調用當前正在處理的Render Node的成員函數computeOrdering來計算得到的。

同樣,在將要處理的Draw Render Node Op交給參數handler描述的一個DeferOperationHandler對象處理之前,這些Draw Render Node Op的成員變量mSkipInOrderDraw的值設置為false,處理完成之後再恢復為true。這樣做的目的是為了當前正在處理的Render Node以相同的方式遞歸處理它的Projected Node的Display List的繪制命令。我們在後面將會看到這一點。

這一步執行完成之後,回到RenderNode類的成員函數issueOperations中,現在當前正在處理的Render Node的Display List的所有繪制命令都按照我們前面描述的順序交給參數handler描述的一個DeferOperationHandler對象處理了,也就是調用該DeferOperationHandler對象的操作符重載函數()進行處理。以一個類型為DrawOp的Display List Op為例,DeferOperationHandler對象的操作符重載函數()會調用它的成員函數defer進行處理。

DrawOp類的成員函數defer的實現如下所示:

 

class DrawOp : public DisplayListOp {
    ......

    virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
            bool useQuickReject) {
        ......

        deferStruct.mDeferredList.addDrawOp(deferStruct.mRenderer, this);
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DisplayListOp.h中。

 

DrawOp類的成員函數defer調用了參數deferStruct描述的一個DeferStateStruct對象的成員變量mDeferredList指向的一個DeferredDisplayList對象的成員函數addDrawOp檢查當前正在處理的一個DrawOp是否可以與其它DrawOp進行合並,它的實現如下所示:

 

void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
    /* 1: op calculates local bounds */
    DeferredDisplayState* const state = createState();
    if (op->getLocalBounds(state->mBounds)) {
        if (state->mBounds.isEmpty()) {
            .......
            return;
        }
    } else {
        state->mBounds.setEmpty();
    }

    /* 2: renderer calculates global bounds + stores state */
    if (renderer.storeDisplayState(*state, getDrawOpDeferFlags())) {
        ......
        return; // quick rejected
    }

    /* 3: ask op for defer info, given renderer state */
    DeferInfo deferInfo;
    op->onDefer(renderer, deferInfo, *state);

    // complex clip has a complex set of expectations on the renderer state - for now, avoid taking
    // the merge path in those cases
    deferInfo.mergeable &= !recordingComplexClip();
    deferInfo.opaqueOverBounds &= !recordingComplexClip() && mSaveStack.isEmpty();

    if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() &&
            state->mClipSideFlags != kClipSide_ConservativeFull &&
            deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) {
        // avoid overdraw by resetting drawing state + discarding drawing ops
        discardDrawingBatches(mBatches.size() - 1);
        ......
    }

    if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) {
        // TODO: elegant way to reuse batches?
        DrawBatch* b = new DrawBatch(deferInfo);
        b->add(op, state, deferInfo.opaqueOverBounds);
        mBatches.add(b);
        return;
    }

    // find the latest batch of the new op's type, and try to merge the new op into it
    DrawBatch* targetBatch = NULL;

    // insertion point of a new batch, will hopefully be immediately after similar batch
    // (eventually, should be similar shader)
    int insertBatchIndex = mBatches.size();
    if (!mBatches.isEmpty()) {
        if (state->mBounds.isEmpty()) {
            // don't know the bounds for op, so add to last batch and start from scratch on next op
            DrawBatch* b = new DrawBatch(deferInfo);
            b->add(op, state, deferInfo.opaqueOverBounds);
            mBatches.add(b);
            ......
            return;
        }

        if (deferInfo.mergeable) {
            // Try to merge with any existing batch with same mergeId.
            if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) {
                if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) {
                    targetBatch = NULL;
                }
            }
        } else {
            // join with similar, non-merging batch
            targetBatch = (DrawBatch*)mBatchLookup[deferInfo.batchId];
        }

        if (targetBatch || deferInfo.mergeable) {
            // iterate back toward target to see if anything drawn since should overlap the new op
            // if no target, merging ops still interate to find similar batch to insert after
            for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
                DrawBatch* overBatch = (DrawBatch*)mBatches[i];

                if (overBatch == targetBatch) break;

                // TODO: also consider shader shared between batch types
                if (deferInfo.batchId == overBatch->getBatchId()) {
                    insertBatchIndex = i + 1;
                    if (!targetBatch) break; // found insert position, quit
                }

                if (overBatch->intersects(state->mBounds)) {
                    // NOTE: it may be possible to optimize for special cases where two operations
                    // of the same batch/paint could swap order, such as with a non-mergeable
                    // (clipped) and a mergeable text operation
                    targetBatch = NULL;
                    ......
                    break;
                }
            }
        }
    }

    if (!targetBatch) {
        if (deferInfo.mergeable) {
            targetBatch = new MergingDrawBatch(deferInfo,
                    renderer.getViewportWidth(), renderer.getViewportHeight());
            mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch);
        } else {
            targetBatch = new DrawBatch(deferInfo);
            mBatchLookup[deferInfo.batchId] = targetBatch;
        }

        ......
        mBatches.insertAt(targetBatch, insertBatchIndex);
    }

    targetBatch->add(op, state, deferInfo.opaqueOverBounds);
}

 

這個函數定義在文件frameworks/base/libs/hwui/DeferredDisplayList.cpp中。

 

在分析DeferredDisplayList類的成員函數addDrawOp的實現之前,我們首先要了解它的三個成員變量mBatches、mBatchLookup和mMergingBatches,如下所示:

 

class DeferredDisplayList {
    friend class DeferStateStruct; // used to give access to allocator
public:
    ......

    enum OpBatchId {
        kOpBatch_None = 0, // Don't batch
        kOpBatch_Bitmap,
        kOpBatch_Patch,
        kOpBatch_AlphaVertices,
        kOpBatch_Vertices,
        kOpBatch_AlphaMaskTexture,
        kOpBatch_Text,
        kOpBatch_ColorText,

        kOpBatch_Count, // Add other batch ids before this
    };

    ......
private:
    ......

    Vector mBatches;

    // Maps batch ids to the most recent *non-merging* batch of that id
    Batch* mBatchLookup[kOpBatch_Count];
    ......

    /**
     * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
     * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
     * collide, which avoids the need to resolve mergeid collisions.
     */
    TinyHashMap mMergingBatches[kOpBatch_Count];
  
    ......
};

這三個成員變量定義在文件frameworks/base/libs/hwui/DeferredDisplayList.h中。

可以批量進行處理的繪制命令,也就是DrawOp,放在同一個Batch中,這些Batch按照繪制先後順序保存在DeferredDisplayList類的成員變量mBatches描述的一個Vector中。注意,這裡說的批量處理,有兩種含義。第一種含義是在同一個Batch中的每一個DrawOp都是單獨執行的,不過它們是按順序執行的。第二種含義是在在同一個Batch中的所有DrawOp都是一次性執行的。其中,第二種含義才稱為合並執行。

兩個DrawOp可以合並執行的必要條件是它們具有相同的Batch ID和Merge ID。注意,這不是充分條件。也就是說,具有相同Batch ID和Merge ID的兩個Draw Op不一定能夠合並執行。例如,當它們重疊,或者在它們之間存在另外的DrawOp與它們重疊。這些都會造成兩個具有相同Batch ID和Merge ID的Draw Op不能合並執行。

對於具有相同Batch ID但是不同的Merge ID的兩個Draw Op,我們希望它們將放在相鄰的位置,因為Batch ID描述的是一種繪制類型。這些繪制類型由枚舉類型OpBatchId定義。這樣GPU在執行這些Draw Op時,在內部就不需要進行狀態切換,這樣可以提高效率。當然,也並不是所有具有相同Batch ID的DrawOp都能夠放在相鄰的位置,因為它們之間可能存在其它的Draw Op與它們重疊。

基於以上的分析,當給出一個DrawOp時,我們希望:

1. 在DeferredDisplayList類的成員變量mBatches描述的一個Vector中快速找可以與它進行合並執行的DrawOp所在的Batch。這時候就需要用到DeferredDisplayList類的成員變量mMergingBatches描述的是一個TinyHashMap數組了。這個數組的大小為kOpBatch_Count,這意味著每一個Batch ID在這個數組中都有一個TinyHashMap。因此,給出一個DrawOp,我們根據它的Batch ID就可以快速得到一個TinyHashMap。有了這個TinyHashMap,我們再以給出的Draw Op的Merge ID作為鍵值,快速找到一個Batch。接著再根據其它條件判斷給出的DrawOp與在找到的Batch中已經存在的DrawOp是否能夠合並。如果能夠合並,就將給出的DrawOp添加到找到的Batch去就行了。

2. 如果不能在DeferredDisplayList類的成員變量mBatches描述的一個Vector中可以讓它合並的Batch時,我們希望可以快速找到另外一個Batch,這個Batch的所有DrawOp都是依次地單獨執行。這時候就需要用到DeferredDisplayList類的成員變量mBatchLookup描述的一個Batch數組了。這個數組的大小同樣為kOpBatch_Count,這也意味著每一個Batch ID在這個數組中都有一個Batch。因此,給出一個DrawOp,我們根據它的Batch ID就可以快速得到一個Batch。接著再根據其它條件判斷給出的DrawOp與在找到的Batch中已經存在的DrawOp是否能夠合並。如果能夠合並,就將給出的DrawOp添加到找到的Batch去就行了。

3. 如果通過上面的兩個方法還是不能找到一個Batch,那麼就需要創建一個新的Batch來存放給出的Draw Op。但是我們希望可以將這個新創建的Batch放在與它具有相同Batch ID的Batch相鄰的位置上。

了解了DeferredDisplayList類的三個成員變量mBatches、mBatchLookup和mMergingBatches的作用之後,我們再來看另外一個結構體DeferInfo,如下所示:

 

struct DeferInfo {
public:
    DeferInfo() :
            batchId(DeferredDisplayList::kOpBatch_None),
            mergeId((mergeid_t) -1),
            mergeable(false),
            opaqueOverBounds(false) {
    };

    int batchId;
    mergeid_t mergeId;
    bool mergeable;
    bool opaqueOverBounds; // opaque over bounds in DeferredDisplayState - can skip ops below
};

這個結構體定義在文件frameworks/base/libs/hwui/DeferredDisplayList.h中。

結構體DeferInfo有四個成員變量,分別是:

1. batchId:描述一個DrawOp的Batch ID。

2. mergeId:描述一個DrawOp的Merge ID。

3. mergeable:描述一個DrawOp是否具有與其它DrawOp進行合並的條件,最終能不能合並還要取決於其它條件。

4. opaqueOverBounds:描述的一個DrawOp是不是不透明繪制。如果是的話,就會可能覆蓋在它前面的DrawOp,但是最終能不能覆蓋同樣還要取決於其它條件。

每一個DrawOp都定義有一個成員函數onDefer,用來設置一個DeferInfo結構體的各個成員變量,以便調用者可以知道它的Batch ID和Merge ID,以及它的合並和覆蓋繪制信息。具體的例子可以參考前面Android應用程序UI硬件加速渲染的預加載資源地圖集服務(Asset Atlas Service)分析一文。

有了上面這些知識之後,我們就開始分析上面列出的DrawOp類的成員函數defer的代碼。為了描述分便,我們分段來閱讀:

 

    /* 1: op calculates local bounds */
    DeferredDisplayState* const state = createState();
    if (op->getLocalBounds(state->mBounds)) {
        if (state->mBounds.isEmpty()) {
            .......
            return;
        }
    } else {
        state->mBounds.setEmpty();
    }
這段代碼是獲得參數op描述的DrawOp的繪制區域,保存在本地變量state指向的一個DeferDisplayState結構體的成員變量mBounds中。通過調用這個DrawOp的成員函數getLocalBounds可以獲得它的繪制區域。

 

如果這個DrawOp設置了一個空區域,那麼就不會對它進行處理了。另一方面,如果這個DrawOp沒有設置繪制區載,調用它的成員函數getLocalBounds得到的返回值為false,這時候會將本地變量const_state指向的一個DeferDisplayState結構體的成員變量mBounds描述的區域設置為空,但是其實想表達的意思是未設置繪制區域。

 

    /* 2: renderer calculates global bounds + stores state */
    if (renderer.storeDisplayState(*state, getDrawOpDeferFlags())) {
        ......
        return; // quick rejected
    }

這段代碼調用參數renderer描述的一個OpenGLRender對象的成員函數storeDisplayState設置參數op描述的DrawOp的裁剪區域。如果參數op描述的DrawOp描述的繪制區域與當前的裁剪區域沒有交集,那麼就說明該DrawOp是不可見的,因此就不用對它進行繪制了,於是就不用往下處理了。

    /* 3: ask op for defer info, given renderer state */
    DeferInfo deferInfo;
    op->onDefer(renderer, deferInfo, *state);

    // complex clip has a complex set of expectations on the renderer state - for now, avoid taking
    // the merge path in those cases
    deferInfo.mergeable &= !recordingComplexClip();
    deferInfo.opaqueOverBounds &= !recordingComplexClip() && mSaveStack.isEmpty();

這段代碼調用參數op描述的DrawOp獲得一個初始好的DeferInfo結構體,也就是獲得參數op描述的DrawOp的Batch ID和Merge ID,以及合並和覆蓋繪制信息。

如果參數op描述的DrawOp表明自己可以與其它具有相同Batch ID和Merge ID的DrawOp合並,但是如果當前的裁剪區域是一個復雜的裁剪區域,也就是由一系列正則的矩形組合形成的復雜區域,那麼就會禁止op描述的DrawOp與其它具有相同Batch ID和Merge ID的DrawOp合並。

同樣,如果參數op描述的DrawOp表明自己的繪制會覆蓋前面的DrawOp,但是如果當前的裁剪區域是一個復雜的裁剪區域,或者當前是繪制在一個Layer上,那麼就會禁止op描述的DrawOp覆蓋前面的DrawOp。

復雜的裁剪區域會導致具有相同Batch ID和Merge ID的DrawOp不能正確地合並,同時也會導致不透明的DrawOp不能正確地前面的DrawOp。另外,如果參數op描述的DrawOp是繪制在一個Layer之上,也就是在它之前有一個saveLayer操作,該操作會創建一個Layer,那麼後面會有一個對應的restore/restoreToCount操作。當執行restore/restoreToCount操作的時候,前面繪制出來的Layer會被合並在前一個Layer或者Frame Buffer之上。這個合並的操作導致參數op描述的DrawOp不能直接就覆蓋前面的DrawOp,也就是丟棄前面的DrawOp。

 

    if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() &&
            state->mClipSideFlags != kClipSide_ConservativeFull &&
            deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) {
        // avoid overdraw by resetting drawing state + discarding drawing ops
        discardDrawingBatches(mBatches.size() - 1);
        ......
    }

 

這段代碼綜合判斷參數op描述的DrawOp是否能夠覆蓋排在前面的DrawOp。如果以下條件都能滿足,那麼參數op描述的DrawOp是否能夠覆蓋排在前面的DrawOp:

1. 當前設置了禁止過度繪制,即DeferredDisplayList類的成員變量mAvoidOverdraw的值等於true。在啟用過度繪制的情況下,即使是被覆蓋的區域,也要進行繪制。這樣才能將看到過度繪制。

2. 在參數op描述的DrawOp之前,已經存在其它的DrawOp,也就是DeferredDisplayList類的成員變量mBatches描述的一個Vector不為空,這樣才有DrawOp被覆蓋。

3. 參數op描述的DrawOp明確設置有繪制區域。如果參數op描述的DrawOp沒有設置繪制區域,那麼本地變量state指向的一個state指向的一個DeferDisplayState結構體的成員變量mClipSideFlags的值會被設置為kClipSide_ConservativeFull。未設置繪制區域的DrawOp,我們就不能明確地知道它會不會覆蓋之前的DrawOp。

4. 參數op描述的DrawOp表明自己是不透明繪制,即本地變量deferInfo描述的一個DeferInfo結構體的成員變量opaqueOverBounds的值等於ture。

5. 參數op描述的DrawOp的繪制區域包含了之前的DrawOp合並起來的繪制區域。

這些排在前面的DrawOp就保存在DeferredDisplayList類的成員變量mBatches描述的一個Vector中。如果能夠覆蓋,那麼就可以丟棄它們,實際上就是調用DeferredDisplayList類的成員函數discardDrawingBatches清空上述Vector。

 

    if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) {
        // TODO: elegant way to reuse batches?
        DrawBatch* b = new DrawBatch(deferInfo);
        b->add(op, state, deferInfo.opaqueOverBounds);
        mBatches.add(b);
        return;
    }
如果參數renderer描述的一個OpenGLRenderer表明自己禁止重新排序它的DrawOp,也就是禁止執行DrawOp的合並操作,這時候就會直接為參數op描述的DrawOp創建一個Batch,並且保存在DeferredDisplayList類的成員變量mBatches描述的一個Vector中。這意味著每一個DrawOp都會有獨立保存一個Batch中,這樣就可以避免出現合並操作。

 

 

    // find the latest batch of the new op's type, and try to merge the new op into it
    DrawBatch* targetBatch = NULL;

    // insertion point of a new batch, will hopefully be immediately after similar batch
    // (eventually, should be similar shader)
    int insertBatchIndex = mBatches.size();
    if (!mBatches.isEmpty()) {
        if (state->mBounds.isEmpty()) {
            // don't know the bounds for op, so add to last batch and start from scratch on next op
            DrawBatch* b = new DrawBatch(deferInfo);
            b->add(op, state, deferInfo.opaqueOverBounds);
            mBatches.add(b);
            ......
            return;
        }

 

這段代碼判斷在參籹op描述的DrawOp之前,是否已經存在其它的DrawOp。如果存在,但是參籹op描述的DrawOp又沒有設置繪制區域,那麼即使前面的DrawOp能夠與它進行合並,那麼也是禁止的。這時候就單獨為它創建一個Batch,並且保存在DeferredDisplayList類的成員變量mBatches描述的一個Vector中。

 

        if (deferInfo.mergeable) {
            // Try to merge with any existing batch with same mergeId.
            if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) {
                if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) {
                    targetBatch = NULL;
                }
            }
        } else {
            // join with similar, non-merging batch
            targetBatch = (DrawBatch*)mBatchLookup[deferInfo.batchId];
        }
如果參數op描述的DrawOp表明自己可以與其它具有相同Batch ID和Merge ID的DrawOp進行合並,那麼這段代碼就按照我們前面描述的,通過DeferredDisplayList類的成員變量mMergingBatches描述的一個TinyHashMap數組,快速找到一個具有相同Batch ID和Merge ID的Batch。如果能找到這樣的Batch,還需要調用這個Batch的成員函數canMergeWith判斷已經存在該Batch的DrawOp是否能夠真的與參數op描述的DrawOp進行合並。例如,對於Batch ID等於kOpBatch_Text的兩個文字繪制DrawOp,如果文字的顏色不一樣,那麼這兩個DrawOp合並。

 

如果參數op描述的DrawOp表明自己不可以與其它DrawOp進行合並,那麼這段代碼也是按照我們前面描述的,通過DeferredDisplayList類的成員變量mBatchLookup描述的一個Batch數組,找到一個與它具有相同的Batch ID的Batch,以便將參數op描述的DrawOp加入到這個Batch去進行依次的獨立繪制。

 

        if (targetBatch || deferInfo.mergeable) {
            // iterate back toward target to see if anything drawn since should overlap the new op
            // if no target, merging ops still interate to find similar batch to insert after
            for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
                DrawBatch* overBatch = (DrawBatch*)mBatches[i];

                if (overBatch == targetBatch) break;

                // TODO: also consider shader shared between batch types
                if (deferInfo.batchId == overBatch->getBatchId()) {
                    insertBatchIndex = i + 1;
                    if (!targetBatch) break; // found insert position, quit
                }

                if (overBatch->intersects(state->mBounds)) {
                    // NOTE: it may be possible to optimize for special cases where two operations
                    // of the same batch/paint could swap order, such as with a non-mergeable
                    // (clipped) and a mergeable text operation
                    targetBatch = NULL;
                    ......
                    break;
                }
            }
        }
    }
這段代碼判斷參數op描述的DrawOp是否真的能加入到前面找到的Batch去,主要就是判斷參數op描述的DrawOp與找到的Batch裡面的DrawOp之間,是否存在其它的DrawOp與它存在重疊。如果存在,那麼就不能夠將參數op描述的DrawOp是否真的能加入到前面找到的Batch去了。這意味著要為參數op描述的DrawOp創建一個獨立的Batch。這個Batch也是按照我們前面描述的,盡可能放在前面與它具有相同Batch ID的Batch的相鄰位置。這個位置就通過設置本地變量insertBatchIndex的值得到。

 

    if (!targetBatch) {
        if (deferInfo.mergeable) {
            targetBatch = new MergingDrawBatch(deferInfo,
                    renderer.getViewportWidth(), renderer.getViewportHeight());
            mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch);
        } else {
            targetBatch = new DrawBatch(deferInfo);
            mBatchLookup[deferInfo.batchId] = targetBatch;
        }

        ......
        mBatches.insertAt(targetBatch, insertBatchIndex);
    }

    targetBatch->add(op, state, deferInfo.opaqueOverBounds);
這段代碼判斷本地變量targetBatch的值。如果等於NULL,那麼就表明前面不能在DeferredDisplayList類的成員變量mBatches描述的一個Vector中找到一個能夠用來保存參數op描述的DrawOp的Batch。這時候就需要為參數op描述的DrawOp創建一個Batch了。這個Batch的具體類型要麼是MergingDrawBatch,要麼是DrawBatch,取決於參數op描述的DrawOp是否表明自己是可合並的,即本地變量deferInfo描述的一個DeferInfo結構體的成員變量mergeable的值是否為true。

 

如果參數op描述的DrawOp表明自己是可合並的,那麼就為它創建一個MergingDrawBatch,並且保存在DeferredDisplayList類的成員變量MMergingDrawBatch描述的一個TinyHashMap數組中,使得它後面的與它具有相同Batch ID和Merge ID的DrawOp能夠快速找到它。

如果參數op描述的DrawOp表明自己是不可以合並的,那麼就為它創建一個DrawBatch,並且保存在DeferredDisplayList類的成員變量mBatchLookup描述的一個Batch數組中,以便它後面的與它具有相同Batch ID的DrawOp能夠快速找到它。

這意味著保存在同一個MergingDrawBatch的DrawOp,在渲染的時候是可以進合並繪制的,而保存在同一個rawBatch的DrawOp,在渲染的時候是可以連續地進行獨立繪制的。

最後,新創建的Batch就根據前面得到的本地變量insertBatchIndex的值保存在DeferredDisplayList類的成員變量mBatches描述的一個Vector中,使得該Batch盡可能地與它具有同的Batch ID的Batch放在一起。

另一方面,如果本地變量targetBatch的值不等於NULL,那麼就表明前面找到了一個Batch,這個Batch可以用來保存參數op描述的DrawOp。

這樣,當DeferredDisplayList類的成員addDrawOp執行完成之後,當前正在處理的所有DrawOp都經過合並等處理了,並且處理後得到的DrawOp以Batch為單位保存在DeferredDisplayList類的成員變量mBatches描述的一個Vector中。

上面描述的是一個普通的DrawOp的成員函數defer被調用時所執行的繪制命令重排和合並操作。還有另外一種特殊的Display List Op,即DrawRenderNodeOp。從前面的分析可以知道,當一個Render Node包含有子Render Node時,它的Display List包含有一個對應的DrawRenderNodeOp。此外,當一個Render Node具有Projected Node時,每一個Projected Node都有一個對應的DrawRenderNodeOp保存該Render Node的成員變量mProjectedNodes描述的一個Vector。所有的這些DrawRenderNodeOp也像DrawOp一樣,會被DeferOperationHandler類的操作符重載函數()調用它們的成員函數defer。

DrawRenderNodeOp類的成員函數defer的實現如下所示:

 

class DrawRenderNodeOp : public DrawBoundedOp {
    ......

    virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
            bool useQuickReject) {
        if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
            mRenderNode->defer(deferStruct, level + 1);
        }
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DisplayListOp.h中。

 

DrawRenderNodeOp類的成員變量mRenderNode描述的是當前正在處理的DrawRenderNodeOp所關聯的一個Render Node。當這個Render Node的Display List不為空時,就表示這個Render Node的Display List的繪制命令需要執行重排和合並操作。

此外,DrawRenderNodeOp類還有另外一個成員變量mSkipInOrderDraw。當它的值等於true時,就表示當前正在處理的DrawRenderNodeOp所關聯的Render Node要跳過順序繪制。這是什麼意思呢?其實這是針對我們前面提到的Ripple Drawable的。我們知道,Ripple Drawable有可能不是按照它們在視圖結構的順序繪制的,因為它們有可能會被投影到最近一個父Render Node的Backround去繪制。這樣當它們對應的Render Node在順序繪制中就應該跳過處理。

在我們這個情景中,這裡的DrawRenderNodeOp類的成員函數defer並不是在順序繪制過程中被調用的,而是在重排和合並一個Render Node的Display List的繪制命令的過程中調用的,也就是在前面分析的RenderNode類的成員函數issueOperationsOf3dChildren和issueOperationsOfProjectedChildren中調用的。這兩個成員函數需要強制DrawRenderNodeOp類的成員函數defer重排和合並當前正在處理的DrawRenderNodeOp所關聯的一個Render Node的Display List的繪制命令,因此就會強制當前正在處理的DrawRenderNodeOp的成員變量mSkipInOrderDraw設置為false。

這樣,當一個DrawRenderNodeOp的成員變量mSkipInOrderDraw的值為false,並且它關聯的Render Node的Display List不為空,這個Render Node的成員函數defer就會被調用。這意味著通過DrawRenderNodeOp類的成員函數defer,一個Render Node及其所有的子Render Node和Projected Node的Display List的繪制命令都會得到歸遞重排和合並處理。

這一步執行完成之後,回到CanvasContext類的成員函數draw中,這時候所有設置了Layer的Render Node的Display List包含的Display List Op都已經得到了重排和合並等處理,接下來要做的事情就是調用OpenGLRenderer類的成員函數drawRenderNode渲染應用程序窗口的Root Render Node的Display List。

OpenGLRenderer類的成員函數drawRenderNode的實現如下所示:

 

status_t OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
    status_t status;
    // All the usual checks and setup operations (quickReject, setupDraw, etc.)
    // will be performed by the display list itself
    if (renderNode && renderNode->isRenderable()) {
        // compute 3d ordering
        renderNode->computeOrdering();
        if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
            status = startFrame();
            ReplayStateStruct replayStruct(*this, dirty, replayFlags);
            renderNode->replay(replayStruct, 0);
            return status | replayStruct.mDrawGlStatus;
        }

        bool avoidOverdraw = !mCaches.debugOverdraw && !mCountOverdraw; // shh, don't tell devs!
        DeferredDisplayList deferredList(*currentClipRect(), avoidOverdraw);
        DeferStateStruct deferStruct(deferredList, *this, replayFlags);
        renderNode->defer(deferStruct, 0);

        flushLayers();
        status = startFrame();

        return deferredList.flush(*this, dirty) | status;
    }

    // Even if there is no drawing command(Ex: invisible),
    // it still needs startFrame to clear buffer and start tiling.
    return startFrame();
}

這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp。

參數renderNode描述的是應用程序窗口的Root Render Node,如果它的值不等於NULL,並且它是可渲染的,即調用它的成員函數isRenderable的返回值為true,那麼接下來就開始渲染的它的Display List。

在渲染應用程序窗口的Root Render Node之前,OpenGLRenderer類的成員函數drawRenderNode首先調用它的成員函數computeOrdering計算它的Projected Node。這一步與前面LayerRenderer類渲染設置了Layer的Render Node的Display List的過程是一樣的,都是為重排那些Projected Node,使得它們的渲染順序位於要投影到的Render Node的後面。

OpenGLRenderer類的成員函數drawRenderNode接下來判斷當前是否禁止重排應用程序窗口的Root Render Node的Display List的繪制命令,也就是不允許對這些繪制命令進行合並。如果是禁止的話,那麼OpenGLRenderer類的成員變量mCaches指向的一個Caches對象的成員變量drawDeferDisabled的值就會等於true。在這種情況下,就會跳過應用程序窗口的Root Render Node的Display List的繪制命令的重排階段,而直接對它們進行執行。這是通過調用RenderNode類的成員函數replay實現的。

如果當前不禁止重排應用程序窗口的Root Render Node的Display List的繪制命令,那麼OpenGLRenderer類的成員函數drawRenderNode接下來做的事情就是調用前面分析過的RenderNode類的成員函數defer對應用程序窗口的Root Render Node及其子Render Node和Projected Node的的Display List的繪制命令進行合並操作。合並後得到的繪制命令,也就是DrawOp,就以Batch為單位保存在本地變量deferredList描述的一個DeferredDisplayList對象的成員變量mBatches描述的一個Vector中。

這裡有有一點需要注意的是,在調用RenderNode類的成員函數defer合並應用程序窗口的Root Render Node的Display List的繪制命令的時候,傳遞進去的DeferStateStruct結構體封裝的Renderer是一個OpenGLRenderer。這意味著如果應用程序窗口的Root Render Node包含了一個設置了Layer的子RenderNode,那麼當調用到RenderNode類的成員函數issueOperations遞歸處理該子RenderNode時候,這個子RenderNode就直接以一個DrawLayerOp進行繪制。這是由於這時候這個子RenderNode的成員變量renderer指向的OpenGLRenderer對象的實際類型是LayerRenderer,而參數renderer指向OpenGLRenderer對象的實際類型就是OpenGLRenderer。這兩個OpenGLRenderer對象的不相等,就使得本地變量drawLayer的值等於true,於是該子RenderNode的繪制命令就被封裝為一個DrawLayerOp。這樣做是合理的,因為這個子RenderNode的Display List的繪制命令之前已經被重排和合並過了。

重排和合並完成應用程序窗口的Root Render Node及其子Render Node和Projected Node的Display List的繪制命令之後,本來就可以執行它們了。但是在執行它們之前,還有一件事情需要做,就是先執行那些設置了Layer的子Render Node的繪制命令,以便得到一個對應的FBO。這些FBO就代表了那些設置了Layer的子Render Node的UI。這一步是通過調用OpenGLRenderer類的成員函數flush來完成的。

OpenGLRenderer類的成員函數flush的實現如下所示:

 

void OpenGLRenderer::flushLayers() {
    int count = mLayerUpdates.size();
    if (count > 0) {
        ......

        // Note: it is very important to update the layers in order
        for (int i = 0; i < count; i++) {
            ......
            Layer* layer = mLayerUpdates.itemAt(i);
            layer->flush();
            ......
        }

        ......

        mRenderState.bindFramebuffer(getTargetFbo());
        
        ......
    }
}
這個函數定義在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp。

 

從前面的分析可以知道,OpenGLRenderer類的成員變量mLayerUpdates描述的一個Vector裡面存放的都是設置了Layer的Render Node關聯的Layer,並且這些Render Noder的Display List的繪制命令都是已經經過了重排和合並等操作的。

對於保存在上述Vector中的每一個Layer,OpenGLRenderer類的成員函數flushLayers都會調用它的成員函數flush,目的就是執行這些Layer關聯的Render Node的Display List經過重排和合並後的繪制命令。

Layer類的成員函數flush的實現如下所示:

 

void Layer::flush() {
    // renderer is checked as layer may be destroyed/put in layer cache with flush scheduled
    if (deferredList && renderer) {
        ......
        renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom,
                !isBlend());

        deferredList->flush(*renderer, dirtyRect);

        ......
    }
}
這個函數定義在文件frameworks/base/libs/hwui/Layer.cpp中。

 

從前面的分析可以知道,這時候正在處理的Layer對象的成員變量renderer和deferredList的值均不等於NULL,它們分別指向了一個LayerRenderer對象和一個DeferredDisplayList對象,因此Layer類的成員函數flush接下來就分別調用了這兩個對象的成員函數prepareDirty和flush。

LayerRenderer類的成員函數prepareDirty的實現如下所示:

 

status_t LayerRenderer::prepareDirty(float left, float top, float right, float bottom,
        bool opaque) {
    ......

    renderState().bindFramebuffer(mLayer->getFbo());

    ......

    return OpenGLRenderer::prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, opaque);
}
這個函數定義在文件frameworks/base/libs/hwui/LayerRenderer.cpp中。

 

LayerRenderer類的成員函數prepareDirty做了一件很重要的事情,就是在從成員變量mLayer指向的一個Layer對象獲得一個FBO,並且將該FBO設置當前Open GL環境的渲染對象,這意味著後續的Open GL繪制命令都是將UI渲染在該FBO上。

LayerRenderer類的成員函數prepareDirty最後還調用了父類OpenGLRenderer的成員函數prepareDirty。前面我們在分析OpenGLRenderer類的成員函數prepareDirty的時候提到,如果當前正在處理的一個LayerRenderer對象,那麼它所做的事情是調用OpenGLRenderer類的另外一個成員函數startFrame。OpenGLRenderer類的成員函數startFrame僅僅是負責執行一些諸如清理顏色繪沖區等基本操作。當然,這裡清理的是從成員變量mLayer指向的一個Layer對象獲得一個FBO的顏色繪沖區。

這一步執行完成之後,回到Layer類的成員函數flush中,它接下來調用DeferredDisplayList類的成員函數flush,目的是為了將當前正在處理的Layer關聯的Render Node的Display List渲染在上述的FBO上。

DeferredDisplayList類的成員函數flush的實現如下所示:

 

status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
    ......

    status |= replayBatchList(mBatches, renderer, dirty);

    ......

    return status;
}
這個函數定義在文件frameworks/base/libs/hwui/DeferredDisplayList.cpp中。

 

前面提到,DeferredDisplayList類的成員變量mBatches描述的一個Vector存放的就是一個設置了Layer的Render Node的Display List經過重排和合並後的繪制命令,這些繪制命令通過DeferredDisplayList類的另外一個成員函數replayBatchList執行。

DeferredDisplayList類的成員函數replayBatchList的實現如下所示:

 

static status_t replayBatchList(const Vector& batchList,
        OpenGLRenderer& renderer, Rect& dirty) {
    status_t status = DrawGlInfo::kStatusDone;

    for (unsigned int i = 0; i < batchList.size(); i++) {
        if (batchList[i]) {
            status |= batchList[i]->replay(renderer, dirty, i);
        }
    }
    ......
    return status;
}
這個函數定義在文件frameworks/base/libs/hwui/DeferredDisplayList.cpp中。

 

DeferredDisplayList類的成員函數replayBatchList依次調用參數batchList描述的一個Vector中的每一個Batch對象的成員函數replay。從前面分析的DeferredDisplayList類的成員函數addDrawOp可以知道,參數batchList描述的一個Vector中的每一個Batch對象的實際類型要麼是DrawBatch,要麼是MergingDrawBatch,因此我們接下來就繼續分析DrawBatch類和MergingDrawBatch類的成員函數replay的實現。

DrawBatch類的成員函數replay的實現如下所示:

 

class DrawBatch : public Batch {
public:
    ......

    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) {
        ......

        status_t status = DrawGlInfo::kStatusDone;
        ......
        for (unsigned int i = 0; i < mOps.size(); i++) {
            DrawOp* op = mOps[i].op;
            ......

            status |= op->applyDraw(renderer, dirty);

            .....
        }
        return status;
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DeferredDisplayList.cpp中。

 

DrawBatch類的成員函數replay依次調用存放在成員變量mOps描述的一個Vector中的每一個DrawOp的成員函數applyDraw,以便這些DrawOp可以轉化為Open GL繪制命令進行執行。

以一個具體的DrawRectOp為例,它的成員函數applyDraw的實現如下所示:

 

class DrawRectOp : public DrawStrokableOp {
public:
    ......

    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
        return renderer.drawRect(mLocalBounds.left, mLocalBounds.top,
                mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer));
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DisplayListOp.h中。

 

DrawRectOp類的成員函數applyDraw調用了參數renderer描述的一個OpenGLRenderer對象的成員函數drawRect來渲染當前正在處理的一個DrawRectOp。參數renderer描述的一個OpenGLRenderer對象的實際類型為LayerRenderer,不過LayerRenderer類的成員函數drawRect是從父類OpenGLRenderer繼承下來的。因此,當前正在處理的一個DrawRectOp最終是通過OpenGLRenderer類的成員函數drawRect轉化Open GL繪制命令進行執行的。這一點我們就留給讀者自己去分析了。

還有一種特殊的DrawOp,即DrawRenderNodeOp,當它們的成員函數applyDraw被調用時,它所做的工作實際上遞歸地將它的子Render Node或者Projected Node的Display List包含的DrawOp轉化為Open GL命令來執行,它的實現如下所示:

 

class DrawRenderNodeOp : public DrawBoundedOp {
    ......

    virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
            bool useQuickReject) {
        if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
            mRenderNode->replay(replayStruct, level + 1);
        }
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DisplayListOp.h中。

 

這一點與前面我們分析的DrawRenderNodeOp類的成員函數applyDraw的邏輯是類似的,因此我們就不再詳述。

接下來我們再來看MergingDrawBatch類的成員函數replay的實現,如下所示:

 

class MergingDrawBatch : public DrawBatch {
public:
    ......

    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) {
        ......

        DrawOp* op = mOps[0].op;
        ......

        status_t status = op->multiDraw(renderer, dirty, mOps, mBounds);
        ......
        
        return status;
    }

    ......
};
這個函數定義在文件frameworks/base/libs/hwui/DeferredDisplayList.cpp中。

 

MergingDrawBatch類的成員函數replay只調用了保存在成員變量mOps描述的一個Vector中的第一個DrawOp的成員函數multiDraw,但是針將其余的DrawOp作為參數傳遞給它。

以一個具體的DrawPatchOp為例,它的成員函數multiDraw的實現如下所示:

 

class DrawPatchOp : public DrawBoundedOp {  
public:
    ......  
  
    virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,  
            const Vector& ops, const Rect& bounds) {  
        const DeferredDisplayState& firstState = *(ops[0].state);  
        renderer.restoreDisplayState(firstState, true); // restore all but the clip  
  
        // Batches will usually contain a small number of items so it's  
        // worth performing a first iteration to count the exact number  
        // of vertices we need in the new mesh  
        uint32_t totalVertices = 0;  
        for (unsigned int i = 0; i < ops.size(); i++) {  
            totalVertices += ((DrawPatchOp*) ops[i].op)->getMesh(renderer)->verticesCount;  
        }  
  
        const bool hasLayer = renderer.hasLayer();  
  
        uint32_t indexCount = 0;  
  
        TextureVertex vertices[totalVertices];  
        TextureVertex* vertex = &vertices[0];  
  
        // Create a mesh that contains the transformed vertices for all the  
        // 9-patch objects that are part of the batch. Note that onDefer()  
        // enforces ops drawn by this function to have a pure translate or  
        // identity matrix  
        for (unsigned int i = 0; i < ops.size(); i++) {  
            DrawPatchOp* patchOp = (DrawPatchOp*) ops[i].op;  
            const DeferredDisplayState* state = ops[i].state;  
            const Patch* opMesh = patchOp->getMesh(renderer);  
            uint32_t vertexCount = opMesh->verticesCount;  
            if (vertexCount == 0) continue;  
  
            // We use the bounds to know where to translate our vertices  
            // Using patchOp->state.mBounds wouldn't work because these  
            // bounds are clipped  
            const float tx = (int) floorf(state->mMatrix.getTranslateX() +  
                    patchOp->mLocalBounds.left + 0.5f);  
            const float ty = (int) floorf(state->mMatrix.getTranslateY() +  
                    patchOp->mLocalBounds.top + 0.5f);  
  
            // Copy & transform all the vertices for the current operation  
            TextureVertex* opVertices = opMesh->vertices;  
            for (uint32_t j = 0; j < vertexCount; j++, opVertices++) {  
                TextureVertex::set(vertex++,  
                        opVertices->x + tx, opVertices->y + ty,  
                        opVertices->u, opVertices->v);  
            }  
  
            // Dirty the current layer if possible. When the 9-patch does not  
            // contain empty quads we can take a shortcut and simply set the  
            // dirty rect to the object's bounds.  
            if (hasLayer) {  
                if (!opMesh->hasEmptyQuads) {  
                    renderer.dirtyLayer(tx, ty,  
                            tx + patchOp->mLocalBounds.getWidth(),  
                            ty + patchOp->mLocalBounds.getHeight());  
                } else {  
                    const size_t count = opMesh->quads.size();  
                    for (size_t i = 0; i < count; i++) {  
                        const Rect& quadBounds = opMesh->quads[i];  
                        const float x = tx + quadBounds.left;  
                        const float y = ty + quadBounds.top;  
                        renderer.dirtyLayer(x, y,  
                                x + quadBounds.getWidth(), y + quadBounds.getHeight());  
                    }  
                }  
            }  
  
            indexCount += opMesh->indexCount;  
        }  
  
        return renderer.drawPatches(mBitmap, getAtlasEntry(),  
                &vertices[0], indexCount, getPaint(renderer));  
    }  
  
    ......  
};  
這個函數定義在文件frameworks/base/libs/hwui/DisplayListOp.h中。

 

在前面Android應用程序UI硬件加速渲染的預加載資源地圖集服務(Asset Atlas Service)分析一文中,我們有分析過DrawPatchOp類的成員函數multiDraw的實現,它所做的事情就是首先計算出當前正在處理的DrawPatchOp和參數ops描述的DrawPatchOp的紋理坐標,並且將這些紋理坐標保存一個數組中傳遞給參數renderer描述的一個OpenGLRenderer對象的成員函數drawPatches,使得後者可以一次性地將N個DrawPatchOp合並在一起轉化為Open GL繪制命令執行。這之所以是可行的,是因為這些DrawPatchOp是以紋理方式進行渲染的,這些它們使用的是同一個紋理。

這一步執行完成之後,回到OpenGLRenderer類的成員函數flushLayers中,這時候所有設置了Layer的Render Noder及其子Render Node和Projected Node的Display List均已渲染到了自己的FBO之上,接下來就要將這些FBO以及其它沒有設置Layer的Render Node的Display List渲染在Frame Buffer上,也就是渲染在從Surface Flinger請求回來的一個圖形緩沖區之上。由於前面每調用一個Layer對象的成員函數flush的時候,都會將一個FBO設置為當前的渲染對象,而接下來的渲染對象是Frame Buffer,因此就需要調用成員變量mRenderState描述的一個RenderState對象的成員函數bindFramebuffer將Frame Buffer設置為當前的渲染對象。前面提到,OpenGLRenderer類的成員函數getTargetFbo的返回值等於0,當我們將一個值為0的FBO設置為當前的渲染對象時,起到的效果實際上解除前面設置的值為非0的FBO作為當前的渲染對象,並且將當前的渲染對象還原回Frame Buffer的效果。

OpenGLRenderer類的成員函數flushLayers執先完成後,回到OpenGLRenderer類的成員函數drawRenderNode中,這時候可以渲染應用程序窗口的Root Render Node的Display List了。在渲染之前,同樣是先調用OpenGLRenderer類的成員函數startFrame執行一些諸如清理顏色繪沖區等基本操作。注意,這裡清理的是Frame Buffer的顏色繪沖區。這時候應用程序窗口的Root Render Node及其子Render Node和Projected Node的Display List經過重排和合並後的繪制命令就存放在本地變量deferredList描述的一個DeferredDisplayList的成員變量mBatches描述的一個Vector中,因此OpenGLRenderer類的成員函數drawRenderNode就可以調用前面已經分析過的DeferredDisplayList類的成員函數flush來執行它們。這裡同樣是需要注意,這些繪制命令的執行是作用在Frame Buffer之上的。

至此,應用程序窗口的Display List的渲染過程就分析完成了。整個過程比較復雜,但是總結來說,核心邏輯就是:

1. 將Main Thread維護的Display List同步到Render Thread維護的Display List去。這個同步過程由Render Thread執行,但是Main Thread會被阻塞住。

2. 如果能夠完全地將Main Thread維護的Display List同步到Render Thread維護的Display List去,那麼Main Thread就會被喚醒,此後Main Thread和Render Thread就互不干擾,各自操作各自內部維護的Display List;否則的話,Main Thread就會繼續阻塞,直到Render Thread完成應用程序窗口當前幀的渲染為止。

3. Render Thread在渲染應用程序窗口的Root Render Node的Display List之前,首先將那些設置了Layer的子Render Node的Display List渲染在各自的一個FBO上,接下來再將一起將這些FBO以及那些沒有設置Layer的子Render Node的Display List一起渲染在Frame Buffer之上,也就是渲染在從Surface Flinger請求回來的一個圖形緩沖區上。這個圖形緩沖區最終會被提交給Surface Flinger合並以及顯示在屏幕上。

第2步能夠完全將Main Thread維護的Display List同步到Render Thread維護的Display List去很關鍵,它使得Main Thread和Render Thread可以並發執行,這意味著Render Thread在渲染應用程序窗口當前幀的Display List的同時,Main Thread可以去准備應用程序窗口下一幀的Display List,這樣就使得應用程序窗口的UI更流暢。

Android 5.0引入Render Thread的作用除了可以獲得上面描述的效果之外,還可以使得應用程序窗口動畫顯示更加流暢。在接下來的一篇文章中,我們就繼續分析在硬件加速渲染的環境下,應用程序窗口的動畫顯示框架,敬請關注!更多的信息也可以關注老羅的新浪微博:http://weibo.com/shengyangluo。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved