編輯:安卓省電與加速
在硬件加速渲染環境中,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這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。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); } } } ...... }
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這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。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++; } ...... }
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這個函數定義在文件frameworks/base/libs/hwui/RenderNode.cpp中。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; ...... } ...... }
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: ...... VectormBatches; // 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這個函數定義在文件frameworks/base/libs/hwui/DeferredDisplayList.cpp中。& 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; }
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這個函數定義在文件frameworks/base/libs/hwui/DisplayListOp.h中。& 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)); } ...... };
在前面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。
很多人機友苦惱自己手中的手機電量掉的很快,可是總是找不到手機用電在哪裡,明明什麼都沒開,本文將給大家介紹21種安卓智能手機的省電小技巧方法,希望大家在使用手機
有不少的朋友都反應三星S4耗電太快了,不用太擔心,這或許是你的設置有有誤才造成三星S4太過耗電,其實通過正確的省
本節引言:本節繼續來扣Android中的傳感器,本節帶來的是加速度傳感器(Accelerometer sensor)以及陀螺儀傳感器(Gyrosco
在學習小米手機技巧之如何省電以前,我們要知道要怎麼樣檢測耗電使用情況,我一般用自帶的電量監控與點心省電兩個軟件來查看我的耗電情況,兩個軟件交替看數據,基本大致情況就都可以