理解Android硬件加速原理的小白文

硬件加速,直觀上說(shuō)就是依賴GPU實(shí)現(xiàn)圖形繪制加速,軟硬件加速的區(qū)別主要是圖形的繪制究竟是GPU來(lái)處理還是CPU,如果是GPU,就認(rèn)為是硬件加速繪制,反之,軟件繪制。在Android中也是如此,不過(guò)相對(duì)于普通的軟件繪制,硬件加速還做了其他方面優(yōu)化,不僅僅限定在繪制方面,繪制之前,在如何構(gòu)建繪制區(qū)域上,硬件加速也做出了很大優(yōu)化,因此硬件加速特性可以從下面兩部分來(lái)分析:

  • 1、前期策略:如何構(gòu)建需要繪制的區(qū)域
  • 2、后期繪制:單獨(dú)渲染線程,依賴GPU進(jìn)行繪制

無(wú)論是軟件繪制還是硬件加速,繪制內(nèi)存的分配都是類似的,都是需要請(qǐng)求SurfaceFlinger服務(wù)分配一塊內(nèi)存,只不過(guò)硬件加速有可能從FrameBuffer硬件緩沖區(qū)直接分配內(nèi)存(SurfaceFlinger一直這么干的),兩者的繪制都是在APP端,繪制完成之后同樣需要通知SurfaceFlinger進(jìn)行合成,在這個(gè)流程上沒(méi)有任何區(qū)別,真正的區(qū)別在于在APP端如何完成UI數(shù)據(jù)繪制,本文就直觀的了解下兩者的區(qū)別,會(huì)涉及部分源碼,但不求甚解。

軟硬件加速的分歧點(diǎn)

大概從Android 4.+開(kāi)始,默認(rèn)情況下都是支持跟開(kāi)啟了硬件加速的,也存在手機(jī)支持硬件加速,但是部分API不支持硬件加速的情況,如果使用了這些API,就需要主關(guān)閉硬件加速,或者在View層,或者在Activity層,比如Canvas的clipPath等。但是,View的繪制是軟件加速實(shí)現(xiàn)的還是硬件加速實(shí)現(xiàn)的,一般在開(kāi)發(fā)的時(shí)候并不可見(jiàn),那圖形繪制的時(shí)候,軟硬件的分歧點(diǎn)究竟在哪呢?舉個(gè)例子,有個(gè)View需要重繪,一般會(huì)調(diào)用View的invalidate,觸發(fā)重繪,跟著這條線走,去查一下分歧點(diǎn)。

視圖重繪

從上面的調(diào)用流程可以看出,視圖重繪最后會(huì)進(jìn)入ViewRootImpl的draw,這里有個(gè)判斷點(diǎn)是軟硬件加速的分歧點(diǎn),簡(jiǎn)化后如下

ViewRootImpl.java

private void draw(boolean fullRedrawNeeded) {
    ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        <!--關(guān)鍵點(diǎn)1 是否開(kāi)啟硬件加速-->
        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
             ...
            dirty.setEmpty();
            mBlockResizeBuffer = false;
            <!--關(guān)鍵點(diǎn)2 硬件加速繪制-->
            mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
        } else {
          ...
           <!--關(guān)鍵點(diǎn)3 軟件繪制-->
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        ...

關(guān)鍵點(diǎn)1是啟用硬件加速的條件,必須支持硬件并且開(kāi)啟了硬件加速才可以,滿足,就利用HardwareRenderer.draw,否則drawSoftware(軟件繪制)。簡(jiǎn)答看一下這個(gè)條件,默認(rèn)情況下,該條件是成立的,因?yàn)?.+之后的手機(jī)一般都支持硬件加速,而且在添加窗口的時(shí)候,ViewRootImpl會(huì)enableHardwareAcceleration開(kāi)啟硬件加速,new HardwareRenderer,并初始化硬件加速環(huán)境。

private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {

    <!--根據(jù)配置,獲取硬件加速的開(kāi)關(guān)-->
    // Try to enable hardware acceleration if requested
    final boolean hardwareAccelerated =
            (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
   if (hardwareAccelerated) {
        ...
            <!--新建硬件加速圖形渲染器-->
            mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
            if (mAttachInfo.mHardwareRenderer != null) {
                mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
                mAttachInfo.mHardwareAccelerated =
                        mAttachInfo.mHardwareAccelerationRequested = true;
            }
        ...

其實(shí)到這里軟件繪制跟硬件加速的分歧點(diǎn)已經(jīng)找到了,就是ViewRootImpl在draw的時(shí)候,如果需要硬件加速就利用 HardwareRenderer進(jìn)行draw,否則走軟件繪制流程,drawSoftware其實(shí)很簡(jiǎn)單,利用Surface.lockCanvas,向SurfaceFlinger申請(qǐng)一塊匿名共享內(nèi)存內(nèi)存分配,同時(shí)獲取一個(gè)普通的SkiaCanvas,用于調(diào)用Skia庫(kù),進(jìn)行圖形繪制,

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        final Canvas canvas;
        try {
            <!--關(guān)鍵點(diǎn)1 -->
            canvas = mSurface.lockCanvas(dirty);
            ..
            <!--關(guān)鍵點(diǎn)2 繪制-->
                 mView.draw(canvas);
             ..
             關(guān)鍵點(diǎn)3 通知SurfaceFlinger進(jìn)行圖層合成
                surface.unlockCanvasAndPost(canvas);
            }   ...         
           return true;  }

默認(rèn)情況下Skia的繪制沒(méi)有采用GPU渲染的方式(雖然Skia也能用GPU渲染),也就說(shuō)默認(rèn)drawSoftware工作完全由CPU來(lái)完成,不會(huì)牽扯到GPU的操作,但是8.0之后,Google逐漸加重了Skia,開(kāi)始讓Skia接手OpenGL,間接統(tǒng)一調(diào)用,將來(lái)還可能是Skia同Vulkan的結(jié)合,不過(guò)這里不是重點(diǎn)。重點(diǎn)看下HardwareRenderer所進(jìn)行的硬件加速繪制。

HardwareRenderer硬件加速繪制模型

開(kāi)頭說(shuō)過(guò),硬件加速繪制包括兩個(gè)階段:構(gòu)建階段+繪制階段,所謂構(gòu)建就是遞歸遍歷所有視圖,將需要的操作緩存下來(lái),之后再交給單獨(dú)的Render線程利用OpenGL渲染。在Android硬件加速框架中,View視圖被抽象成RenderNode節(jié)點(diǎn),View中的繪制都會(huì)被抽象成一個(gè)個(gè)DrawOp(DisplayListOp),比如View中drawLine,構(gòu)建中就會(huì)被抽象成一個(gè)DrawLintOp,drawBitmap操作會(huì)被抽象成DrawBitmapOp,每個(gè)子View的繪制被抽象成DrawRenderNodeOp,每個(gè)DrawOp有對(duì)應(yīng)的OpenGL繪制命令,同時(shí)內(nèi)部也握著繪圖所需要的數(shù)據(jù)。如下所示:

繪圖Op抽象

如此以來(lái),每個(gè)View不僅僅握有自己DrawOp List,同時(shí)還拿著子View的繪制入口,如此遞歸,便能夠統(tǒng)計(jì)到所有的繪制Op,很多分析都稱為Display List,源碼中也是這么來(lái)命名類的,不過(guò)這里其實(shí)更像是一個(gè)樹(shù),而不僅僅是List,示意如下:

硬件加速.jpg

構(gòu)建完成后,就可以將這個(gè)繪圖Op樹(shù)交給Render線程進(jìn)行繪制,這里是同軟件繪制很不同的地方,軟件繪制時(shí),View一般都在主線程中完成繪制,而硬件加速,除非特殊要求,一般都是在單獨(dú)線程中完成繪制,如此以來(lái)就分擔(dān)了主線程很多壓力,提高了UI線程的響應(yīng)速度。

硬件加速模型.jpg

知道整個(gè)模型后,就代碼來(lái)簡(jiǎn)單了解下實(shí)現(xiàn)流程,先看下遞歸構(gòu)建RenderNode樹(shù)及DrawOp集。

利用HardwareRenderer構(gòu)建DrawOp集

HardwareRenderer是整個(gè)硬件加速繪制的入口,實(shí)現(xiàn)是一個(gè)ThreadedRenderer對(duì)象,從名字能看出,ThreadedRenderer應(yīng)該跟一個(gè)Render線程息息相關(guān),不過(guò)ThreadedRenderer是在UI線程中創(chuàng)建的,那么與UI線程也必定相關(guān),其主要作用:

  • 1、在UI線程中完成DrawOp集構(gòu)建
  • 2、負(fù)責(zé)跟渲染線程通信

可見(jiàn)ThreadedRenderer的作用是很重要的,簡(jiǎn)單看一下實(shí)現(xiàn):

ThreadedRenderer(Context context, boolean translucent) {
    ...
    <!--新建native node-->
    long rootNodePtr = nCreateRootRenderNode();
    mRootNode = RenderNode.adopt(rootNodePtr);
    mRootNode.setClipToBounds(false);
    <!--新建NativeProxy-->
    mNativeProxy = nCreateProxy(translucent, rootNodePtr);
    ProcessInitializer.sInstance.init(context, mNativeProxy);
    loadSystemProperties();
}

從上面代碼看出,ThreadedRenderer中有一個(gè)RootNode用來(lái)標(biāo)識(shí)整個(gè)DrawOp樹(shù)的根節(jié)點(diǎn),有個(gè)這個(gè)根節(jié)點(diǎn)就可以訪問(wèn)所有的繪制Op,同時(shí)還有個(gè)RenderProxy對(duì)象,這個(gè)對(duì)象就是用來(lái)跟渲染線程進(jìn)行通信的句柄,看一下其構(gòu)造函數(shù):

RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
        : mRenderThread(RenderThread::getInstance())
        , mContext(nullptr) {
    SETUP_TASK(createContext);
    args->translucent = translucent;
    args->rootRenderNode = rootRenderNode;
    args->thread = &mRenderThread;
    args->contextFactory = contextFactory;
    mContext = (CanvasContext*) postAndWait(task);
    mDrawFrameTask.setContext(&mRenderThread, mContext);  
   }

從RenderThread::getInstance()可以看出,RenderThread是一個(gè)單例線程,也就是說(shuō),每個(gè)進(jìn)程最多只有一個(gè)硬件渲染線程,這樣就不會(huì)存在多線程并發(fā)訪問(wèn)沖突問(wèn)題,到這里其實(shí)環(huán)境硬件渲染環(huán)境已經(jīng)搭建好好了。下面就接著看ThreadedRenderer的draw函數(shù),如何構(gòu)建渲染Op樹(shù):

@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
    attachInfo.mIgnoreDirtyState = true;

    final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
    choreographer.mFrameInfo.markDrawStart();
    <!--關(guān)鍵點(diǎn)1:構(gòu)建View的DrawOp樹(shù)-->
    updateRootDisplayList(view, callbacks);

    <!--關(guān)鍵點(diǎn)2:通知RenderThread線程繪制-->
    int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
    ...
}

只關(guān)心關(guān)鍵點(diǎn)1 updateRootDisplayList,構(gòu)建RootDisplayList,其實(shí)就是構(gòu)建View的DrawOp樹(shù),updateRootDisplayList會(huì)進(jìn)而調(diào)用根View的updateDisplayListIfDirty,讓其遞歸子View的updateDisplayListIfDirty,從而完成DrawOp樹(shù)的創(chuàng)建,簡(jiǎn)述一下流程:

private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
    <!--更新-->
    updateViewTreeDisplayList(view);
   if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
      <!--獲取DisplayListCanvas-->
        DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
        try {
        <!--利用canvas緩存Op-->
            final int saveCount = canvas.save();
            canvas.translate(mInsetLeft, mInsetTop);
            callbacks.onHardwarePreDraw(canvas);

            canvas.insertReorderBarrier();
            canvas.drawRenderNode(view.updateDisplayListIfDirty());
            canvas.insertInorderBarrier();

            callbacks.onHardwarePostDraw(canvas);
            canvas.restoreToCount(saveCount);
            mRootNodeNeedsUpdate = false;
        } finally {
        <!--將所有Op填充到RootRenderNode-->
            mRootNode.end(canvas);
        }
    }
}
  • 利用View的RenderNode獲取一個(gè)DisplayListCanvas
  • 利用DisplayListCanvas構(gòu)建并緩存所有的DrawOp
  • 將DisplayListCanvas緩存的DrawOp填充到RenderNode
  • 將根View的緩存DrawOp設(shè)置到RootRenderNode中,完成構(gòu)建
繪制流程

簡(jiǎn)單看一下View遞歸構(gòu)建DrawOp,并將自己填充到

 @NonNull
    public RenderNode updateDisplayListIfDirty() {
        final RenderNode renderNode = mRenderNode;
        ...
            // start 獲取一個(gè) DisplayListCanvas 用于繪制 硬件加速 
            final DisplayListCanvas canvas = renderNode.start(width, height);
            try {
                // 是否是textureView
                final HardwareLayer layer = getHardwareLayer();
                if (layer != null && layer.isValid()) {
                    canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
                } else if (layerType == LAYER_TYPE_SOFTWARE) {
                    // 是否強(qiáng)制軟件繪制
                    buildDrawingCache(true);
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
                } else {
                      // 如果僅僅是ViewGroup,并且自身不用繪制,直接遞歸子View
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                    } else {
                        <!--調(diào)用自己draw,如果是ViewGroup會(huì)遞歸子View-->
                        draw(canvas);
                    }
                }
            } finally {
                  <!--緩存構(gòu)建Op-->
                renderNode.end(canvas);
                setDisplayListProperties(renderNode);
            }
        }  
        return renderNode;
    }

TextureView跟強(qiáng)制軟件繪制的View比較特殊,有額外的處理,這里不關(guān)心,直接看普通的draw,假如在View onDraw中,有個(gè)drawLine,這里就會(huì)調(diào)用DisplayListCanvas的drawLine函數(shù),DisplayListCanvas及RenderNode類圖大概如下

硬件加速類圖

DisplayListCanvas的drawLine函數(shù)最終會(huì)進(jìn)入DisplayListCanvas.cpp的drawLine,

void DisplayListCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
    points = refBuffer<float>(points, count);

    addDrawOp(new (alloc()) DrawLinesOp(points, count, refPaint(&paint)));
}

可以看到,這里構(gòu)建了一個(gè)DrawLinesOp,并添加到DisplayListCanvas的緩存列表中去,如此遞歸便可以完成DrawOp樹(shù)的構(gòu)建,在構(gòu)建后利用RenderNode的end函數(shù),將DisplayListCanvas中的數(shù)據(jù)緩存到RenderNode中去:

public void end(DisplayListCanvas canvas) {
    canvas.onPostDraw();
    long renderNodeData = canvas.finishRecording();
    <!--將DrawOp緩存到RenderNode中去-->
    nSetDisplayListData(mNativeRenderNode, renderNodeData);
    // canvas 回收掉]
    canvas.recycle();
    mValid = true;
}

如此,便完成了DrawOp樹(shù)的構(gòu)建,之后,利用RenderProxy向RenderThread發(fā)送消息,請(qǐng)求OpenGL線程進(jìn)行渲染。

RenderThread渲染UI到Graphic Buffer

DrawOp樹(shù)構(gòu)建完畢后,UI線程利用RenderProxy向RenderThread線程發(fā)送一個(gè)DrawFrameTask任務(wù)請(qǐng)求,RenderThread被喚醒,開(kāi)始渲染,大致流程如下:

  • 首先進(jìn)行DrawOp的合并
  • 接著繪制特殊的Layer
  • 最后繪制其余所有的DrawOpList
  • 調(diào)用swapBuffers將前面已經(jīng)繪制好的圖形緩沖區(qū)提交給Surface Flinger合成和顯示。

不過(guò)再這之前先復(fù)習(xí)一下繪制內(nèi)存的由來(lái),畢竟之前DrawOp樹(shù)的構(gòu)建只是在普通的用戶內(nèi)存中,而部分?jǐn)?shù)據(jù)對(duì)于SurfaceFlinger都是不可見(jiàn)的,之后又繪制到共享內(nèi)存中的數(shù)據(jù)才會(huì)被SurfaceFlinger合成,之前分析過(guò)軟件繪制的UI是來(lái)自匿名共享內(nèi)存,那么硬件加速的共享內(nèi)存來(lái)自何處呢?到這里可能要倒回去看看ViewRootImlp

private void performTraversals() {
        ...
        if (mAttachInfo.mHardwareRenderer != null) {
            try {
                hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
                        mSurface);
                if (hwInitialized && (host.mPrivateFlags
                        & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
                    mSurface.allocateBuffers();
                }
            } catch (OutOfResourcesException e) {
                handleOutOfResourcesException(e);
                return;
            }
        }
      ....
      
/**
 * Allocate buffers ahead of time to avoid allocation delays during rendering
 * @hide
 */
public void allocateBuffers() {
    synchronized (mLock) {
        checkNotReleasedLocked();
        nativeAllocateBuffers(mNativeObject);
    }
}

可以看出,對(duì)于硬件加速的場(chǎng)景,請(qǐng)求SurfaceFlinger內(nèi)存分配的時(shí)機(jī)會(huì)稍微提前,而不是像軟件繪制,由Surface的lockCanvas發(fā)起,主要目的是:預(yù)先分配slot位置,避免在渲染的時(shí)候再申請(qǐng),一是避免分配失敗,浪費(fèi)了CPU之前的準(zhǔn)備工作,二是也可以將渲染線程個(gè)工作簡(jiǎn)化,減少延時(shí)。不過(guò),還是會(huì)存在另一個(gè)問(wèn)題,一個(gè)APP進(jìn)程,同一時(shí)刻會(huì)有過(guò)個(gè)Surface繪圖界面,但是渲染線程只有一個(gè),那么究竟渲染那個(gè)呢?這個(gè)時(shí)候就需要將Surface與渲染線程(上下文)綁定。

static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jobject jsurface) {
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
    return proxy->initialize(window);
}

首先通過(guò)android_view_Surface_getNativeWindowSurface獲取Surface,在Native層,Surface對(duì)應(yīng)一個(gè)ANativeWindow,接著,通過(guò)RenderProxy類的成員函數(shù)initialize將前面獲得的ANativeWindow綁定到RenderThread

bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
    SETUP_TASK(initialize);
    args->context = mContext;
    args->window = window.get();
    return (bool) postAndWait(task);
}

仍舊是向渲染線程發(fā)送消息,讓其綁定當(dāng)前Window,其實(shí)就是調(diào)用CanvasContext的initialize函數(shù),讓繪圖上下文綁定繪圖內(nèi)存:

bool CanvasContext::initialize(ANativeWindow* window) {
    setSurface(window);
    if (mCanvas) return false;
    mCanvas = new OpenGLRenderer(mRenderThread.renderState());
    mCanvas->initProperties();
    return true;
}

CanvasContext通過(guò)setSurface將當(dāng)前要渲染的Surface綁定到到RenderThread中,大概流程是通過(guò)eglApi獲得一個(gè)EGLSurface,EGLSurface封裝了一個(gè)繪圖表面,進(jìn)而,通過(guò)eglApi將EGLSurface設(shè)定為當(dāng)前渲染窗口,并將繪圖內(nèi)存等信息進(jìn)行同步,之后通過(guò)RenderThread繪制的時(shí)候才能知道是在哪個(gè)窗口上進(jìn)行繪制。這里主要是跟OpenGL庫(kù)對(duì)接,所有的操作最終都會(huì)歸結(jié)到eglApi抽象接口中去。假如,這里不是Android,是普通的Java平臺(tái),同樣需要相似的操作,進(jìn)行封裝處理,并綁定當(dāng)前EGLSurface才能進(jìn)行渲染,因?yàn)镺penGL是一套規(guī)范,想要使用,就必須按照這套規(guī)范走。之后,再創(chuàng)建一個(gè)OpenGLRenderer對(duì)象,后面執(zhí)行OpenGL相關(guān)操作的時(shí)候,其實(shí)就是通過(guò)OpenGLRenderer來(lái)進(jìn)行的。

綁定流程

上面的流程走完,有序DrawOp樹(shù)已經(jīng)構(gòu)建好、內(nèi)存也已分配好、環(huán)境及場(chǎng)景也綁定成功,剩下的就是繪制了,不過(guò)之前說(shuō)過(guò),真正調(diào)用OpenGL繪制之前還有一些合并操作,這是Android硬件加速做的優(yōu)化,回過(guò)頭繼續(xù)走draw流程,其實(shí)就是走OpenGLRenderer的drawRenderNode進(jìn)行遞歸處理:

void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
       ... 
        <!--構(gòu)建deferredList-->
        DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
        DeferStateStruct deferStruct(deferredList, *this, replayFlags);
        <!--合并及分組-->
        renderNode->defer(deferStruct, 0);
        <!--繪制layer-->
        flushLayers();
        startFrame();
      <!--繪制 DrawOp樹(shù)-->
        deferredList.flush(*this, dirty);
        ...
    }
硬件加速渲染流程

先看下renderNode->defer(deferStruct, 0),合并操作,DrawOp樹(shù)并不是直接被繪制的,而是首先通過(guò)DeferredDisplayList進(jìn)行一個(gè)合并優(yōu)化,這個(gè)是Android硬件加速中采用的一種優(yōu)化手段,不僅可以減少不必要的繪制,還可以將相似的繪制集中處理,提高繪制速度。

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

RenderNode::defer其實(shí)內(nèi)含遞歸操作,比如,如果當(dāng)前RenderNode代表DecorView,它就會(huì)遞歸所有的子View進(jìn)行合并優(yōu)化處理,簡(jiǎn)述一下合并及優(yōu)化的流程及算法,其實(shí)主要就是根據(jù)DrawOp樹(shù)構(gòu)建DeferedDisplayList,defer本來(lái)就有延遲的意思,對(duì)于DrawOp的合并有兩個(gè)必要條件,

  • 1:兩個(gè)DrawOp的類型必須相同,這個(gè)類型在合并的時(shí)候被抽象為Batch ID,取值主要有以下幾種

      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  
      }; 
    
  • 2:DrawOp的Merge ID必須相同,Merge ID沒(méi)有太多限制,由每個(gè)DrawOp自定決定,不過(guò)好像只有DrawPatchOp、DrawBitmapOp、DrawTextOp比較特殊,其余的似乎不需要考慮合并問(wèn)題,即時(shí)是以上三種,合并的條件也很苛刻

在合并過(guò)程中,DrawOp被分為兩種:需要合的與不需要合并的,并分別緩存在不同的列表中,無(wú)法合并的按照類型分別存放在Batch* mBatchLookup[kOpBatch_Count]中,可以合并的按照類型及MergeID存儲(chǔ)到TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count]中,示意圖如下:

DrawOp合并操作.jpg

合并之后,DeferredDisplayList Vector<Batch * > mBatches 包含全部整合后的繪制命令,之后渲染即可,需要注意的是這里的合并并不是多個(gè)變一個(gè),只是做了一個(gè)集合,主要是方便使用各資源紋理等,比如繪制文字的時(shí)候,需要根據(jù)文字的紋理進(jìn)行渲染,而這個(gè)時(shí)候就需要查詢文字的紋理坐標(biāo)系,合并到一起方便統(tǒng)一處理,一次渲染,減少資源加載的浪費(fèi),當(dāng)然對(duì)于理解硬件加速的整體流程,這個(gè)合并操作可以完全無(wú)視,甚至可以直觀認(rèn)為,構(gòu)建完之后,就可以直接渲染,它的主要特點(diǎn)是在另一個(gè)Render線程使用OpenGL進(jìn)行繪制,這個(gè)是它最重要的特點(diǎn)。而mBatches中所有的DrawOp都會(huì)通過(guò)OpenGL被繪制到GraphicBuffer中,最后通過(guò)swapBuffers通知SurfaceFlinger合成。

總結(jié)

軟件繪制同硬件加速的區(qū)別主要是在繪制上,內(nèi)存分配、圖層合成等整體流程是一樣的,只不過(guò)硬件加速相比軟件繪制算法更加合理,同時(shí)采用單獨(dú)的渲染線程,減輕了主線程的負(fù)擔(dān)。

作者:看書(shū)的小蝸牛
理解Android硬件加速的小白文

僅供參考,歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,781評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,588評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,315評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,699評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,882評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,189評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,388評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,613評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,023評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,310評(píng)論 1 293
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,112評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,334評(píng)論 2 377

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