Android畫面顯示流程分析(3)

努比亞技術(shù)團隊原創(chuàng)內(nèi)容爽待,轉(zhuǎn)載請務(wù)必注明出處融痛。

Android畫面顯示流程分析(1)
Android畫面顯示流程分析(2)
Android畫面顯示流程分析(3)
Android畫面顯示流程分析(4)
Android畫面顯示流程分析(5)

5. BufferQueue

BufferQueue要解決的是生產(chǎn)者和消費者的同步問題菩混,應(yīng)用程序生產(chǎn)畫面鸯两,SurfaceFlinger消費畫面宣吱;SurfaceFlinger生產(chǎn)畫面而HWC Service消費畫面哼转。用來存儲這些畫面的存儲區(qū)我們稱其為幀緩沖區(qū)buffer, 下面我們以應(yīng)用程序作為生產(chǎn)者明未,SurfaceFlinger作為消費者為例來了解一下BufferQueue的內(nèi)部設(shè)計。

5.1. Buffer State的切換

在BufferQueue的設(shè)計中壹蔓,一個buffer的狀態(tài)有以下幾種:

FREE :表示該buffer可以給到應(yīng)用程序趟妥,由應(yīng)用程序來繪畫
DEQUEUED:表示該buffer的控制權(quán)已經(jīng)給到應(yīng)用程序側(cè),這個狀態(tài)下應(yīng)用程序可以在上面繪畫了
QUEUED: 表示該buffer已經(jīng)由應(yīng)用程序繪畫完成佣蓉,buffer的控制權(quán)已經(jīng)回到SurfaceFlinger手上了
ACQUIRED:表示該buffer已經(jīng)交由HWC Service去合成了披摄,這時控制權(quán)已給到HWC Service了

Buffer的初始狀態(tài)為FREE, 當(dāng)生產(chǎn)者通過dequeueBuffer來申請buffer成功時亲雪,buffer狀態(tài)變?yōu)榱薉EQUEUED狀態(tài), 應(yīng)用畫圖完成后通過queueBuffer把buffer狀態(tài)改到QUEUED狀態(tài)行疏, 當(dāng)SurfaceFlinger通過acquireBuffer操作把buffer拿去給HWC Service合成匆光, 這時buffer狀態(tài)變?yōu)锳CQUIRED狀態(tài),合成完成后通過releaseBuffer把buffer狀態(tài)重新改為FREE狀態(tài)酿联。狀態(tài)切換如下圖所示:

image-20210904122710556.png

從時間軸上來看一個buffer的狀態(tài)總是這樣循環(huán)變化:

FREE->DEQUEUED->QUEUED->ACQUIRED->FREE

應(yīng)用程序在DEQUEUED狀態(tài)下繪畫终息,而HWC Service在狀態(tài)為ACQUIRED狀態(tài)下做合成:

image-20210904122806448.png

5.2. BufferSlot

每一個應(yīng)用程序的圖層在SurfaceFlinger里稱為一個Layer, 而每個Layer都擁有一個獨立的BufferQueue, 每個BufferQueue都有多個Buffer,Android 系統(tǒng)上目前支持每個Layer最多64個buffer, 這個最大值被定義在frameworks/native/gui/BufferQueueDefs.h贞让, 每個buffer用一個結(jié)構(gòu)體BufferSlot來代表周崭。

每個BufferSlot里主要有如下重要成員:

struct BufferSlot{
    ......
    BufferState mBufferState;//代表當(dāng)前Buffer的狀態(tài) FREE/DEQUEUED/QUEUED/ACQUIRED
    ....
    sp<GraphicBuffer> mGraphicBuffer;//代表了真正的buffer的存儲空間
    ......
    uint64_t mFrameNumber;//表示這個slot被queued的編號,在應(yīng)用調(diào)dequeueBuffer申請slot時會參考該值
    ......
    sp<Fence> mFence;//在Fence一章再來看它的作用
    .....
}

64個BufferSlot可以分成兩個部分喳张,used Slots和Unused Slots, 這個比較好理解续镇,就是使用中的和未被使用的,而Used Slots又可以分為Active Slots和UnActive Slots, 處在DEQUEUED, QUEUED, ACQUIRED狀態(tài)的被稱為Active Slots, 剩下FREE狀態(tài)的稱為UnActive Slots, 所以所有Active Slots都是正在有人使用中的slot, 使用者可能是生產(chǎn)者也可能是消費者销部。而FREE狀態(tài)的Slot根據(jù)是否已經(jīng)為其分配過內(nèi)存來分成兩個部分摸航, 一是已經(jīng)分配過內(nèi)存的,在Android源碼中稱為mFreeBuffers, 沒有分配過內(nèi)存的稱為mFreeSlots, 所以如果我們在代碼中看到是從mFreeSlots里拿出一個BufferSlot那說明這個BufferSlot是還沒有配置GraphicBuffer的, 這個slot可能是第一次被使用到舅桩。其分類如下圖所示:

image-20210904122822581.png

我們來看一下酱虎,應(yīng)用上幀時SurfaceFlinger是如何管理分配這些Slot的。

應(yīng)用側(cè)對圖層buffer的操作接口是如下文件:

frameworks/native/libs/gui/Surface.cpp

應(yīng)用第一次dequeueBuffer前會通過connect接口和SurfaceFlinger建立“連接”:

int Surface::connect(int api, const sp<IProducerListener>&listener, bool reportBufferRemoval){
    ATRACE_CALL();//應(yīng)用第一次上幀前可以在trace 中看到這個
    ......
    int err = mGraphicBufferProducer->connect(listener, api, mProducerControlledByApp, &output);//這里通過binder調(diào)用和SurfaceFlinger建立聯(lián)系
    ......
}

應(yīng)用在第一次dequeueBuffer時會先調(diào)用requestBuffer:

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ATRACE_CALL();//這里可以在systrace中看到
    ......
    //這里嘗試去dequeueBuffer,因為這時SurfaceFlinger對應(yīng)Layer的slot還沒有分配buffer,這時SurfaceFlinger會回復(fù)的flag會有BUFFER_NEEDS_REALLOCATION
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight, 
                                       reqFormat, reqUsage, &mBufferAge, 
                                        enableFrameTimestamps?&frameTimestamps:nullptr);
    ......
    if((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == nullptr) {
        ......
        //這里檢查到dequeueBuffer返回的結(jié)果里帶有BUFFER_NEEDS_REALLOCATION標(biāo)志就會發(fā)出一次requestBuffer
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
        ......
    }
    ......
}

在SurfaceFlinger這端擂涛,第一次收到dequeueBuffer時發(fā)現(xiàn)分配出來的slot沒有GraphicBuffer读串, 這時會去申請對應(yīng)的buffer:

status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
                                            uint32_t width, uint32_t height, PixelFormat format,
                                            uint64_t usage, uint64_t* outBufferAge,
                                            FrameEventHistoryDelta* outTimestamps) {
    if ((buffer == NULL) ||
        buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))//檢查是否已分配了GraphicBuffer
    {
        ......
        returnFlags |= BUFFER_NEEDS_REALLOCATION;//發(fā)現(xiàn)需要分配buffer,置個標(biāo)記
    }
    ......
    if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
        ......
        //新創(chuàng)建一個新的GraphicBuffer給到對應(yīng)的slot
        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
               width, height, format, BQ_LAYER_COUNT, usage,
               {mConsumerName.string(), mConsumerName.size()});
        ......
               mSlots[*outSlot].mGraphicBuffer = graphicBuffer;//把GraphicBuffer給到對應(yīng)的slot
        ......
    }
    ......
    return returnFlags;//注意在應(yīng)用第一次請求buffer, dequeueBuffer返回時對應(yīng)的GraphicBuffer已經(jīng)創(chuàng)建完成并給到了對應(yīng)的slot上,但返回給應(yīng)用的flags里還是帶有BUFFER_NEEDS_REALLOCATION標(biāo)記的
}

應(yīng)用側(cè)收到帶有BUFFER_NEEDS_REALLOCATION標(biāo)記的返回結(jié)果后就會調(diào)requestBuffer來獲取對應(yīng)buffer的信息:

status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
    ATRACE_CALL();
    ......
    mSlots[slot].mRequestBufferCalled = true;
    *buf = mSlots[slot].mGraphicBuffer;
    return NO_ERROR;
}

從上面可以看出requestBuffer的主要作用就是把GraphicBuffer傳遞到應(yīng)用側(cè)撒妈,這里思考一個問題恢暖,既然SurfaceFlinger在響應(yīng)dequeueBuffer時就已經(jīng)為slot新創(chuàng)建了GraphicBuffer, 為什么還需要應(yīng)用側(cè)再次調(diào)用requestBuffer時再把GraphicBuffer傳給應(yīng)用呢? 為什么dequeueBuffer不直接返回呢狰右?這不是多花費一次跨進程通信的時間嗎杰捂? 為什么設(shè)計成了這個樣子呢?

我們再來看一下應(yīng)用側(cè)接口dequeueBuffer的函數(shù)設(shè)計:

frameworks/native/libs/gui/IGraphicBufferProducer.h

virtual status_t dequeueBuffer(int* buf, sp<Fence>* fence, uint32_t width, uint32_t height,
                                   PixelFormat format, uint64_t usage, uint64_t* outBufferAge,
                                   FrameEventHistoryDelta* outTimestamps);

注意第一個參數(shù)只是返回一個int值棋蚌,它表示的是64個slot里的哪一個slot琼娘, 其他參數(shù)里也不會返回這個slot所對應(yīng)的GraphicBuffer的信息,但這個slot拿到應(yīng)用側(cè)后附鸽,應(yīng)用是要拿到確確實實的GraphicBuffer才能把共享內(nèi)存mmap到自已進程空間脱拼,才能在上面繪畫。而顯然這個接口的設(shè)計并不會帶來GraphicBuffer的信息坷备,那設(shè)計之初為什么不把這個信息放進來呢熄浓? 因為這個接口調(diào)用太頻繁了,比如在90FPS的設(shè)備上,一秒鐘該接口要執(zhí)行90次赌蔑,太頻繁了俯在,而且這個信息只需要傳遞一次就可以了,如果每次這個接口都要帶上GraphicBuffer的信息娃惯,傳輸了很多冗余數(shù)據(jù)跷乐,所以不如加入一個新的api(requestBuffer)來完成GraphicBuffer傳遞的事情.

應(yīng)用側(cè)在requestBuffer后會拿到GraphicBuffer的信息,然后會通過importBuffer在本進程內(nèi)通過binder傳過來的parcel包把GraphicBuffer重建出來:

frameworks/native/libs/ui/GraphicBuffer.cpp

status_t GraphicBuffer::unflatten(
        void const*& buffer, size_t& size, int const*& fds, size_t& count) {
        ......
        if (handle != 0) {
            buffer_handle_t importedHandle;
            //獲取從SurfaceFlinger傳過來的buffer
            status_t err = mBufferMapper.importBuffer(handle, uint32_t(width), uint32_t(height),
                    uint32_t(layerCount), format, usage, uint32_t(stride), &importedHandle);
            ......
        }
        ......
}

如下圖所示趾浅,從App側(cè)看愕提,前三幀都會有requestBuffer, 都會有importBuffer,在第4幀時就沒有requestBuffer/importBuffer了皿哨,因為我們當(dāng)前系統(tǒng)一共使用了三個buffer,從systrace上可以看到這個區(qū)別:

image-20210917144349942.png

當(dāng)一個surface被創(chuàng)建出來開始上幀時其流程如下圖所示浅侨,應(yīng)用所使用的畫布是在前三幀被分配出來的,從第四幀開始進入穩(wěn)定上幀期证膨,這時會重復(fù)循環(huán)利用前三次分配的buffer如输。

image-20210904122836162.png

思考一個問題,在三個buffer的系統(tǒng)中一定是前三幀中觸發(fā)分配GraphicBuffer嗎央勒? 如果某個應(yīng)用有一個SurfaceView自已決定上幀的幀率不见,而這個幀率非常低,如低到一秒一幀崔步,那前三秒會把三個Buffer分配出來嗎稳吮?我們需要了解一下多buffer下SurfaceFlinger的管理策略是什么。

5.3. Buffer管理

前文提到了每個圖層Layer都有最多64個BufferSlot, 如下圖所示,每個BufferSlot都會記錄有自身的狀態(tài)(BufferState),以及自已的GraphicBuffer指針mGraphicBuffer.

image-20210904122905043.png

但不是每個Layer都能使用到那么多刷晋,每個Layer最多可使用多少個Layer是在這里設(shè)置的:

frameworks/native/services/surfaceflinger/BufferQueueLayer.cpp

void BufferQueueLayer::onFirstRef() {
    ......
    // BufferQueueCore::mMaxDequeuedBufferCount is default to 1
    if (!mFlinger->isLayerTripleBufferingDisabled()) {
        mProducer->setMaxDequeuedBufferCount(2);//3 buffer時這里設(shè)為2盖高, 是因為在BufferQueueCore那里會+1
    }
    ......
}

我們重新回憶下BufferSlot的幾個狀態(tài)慎陵,F(xiàn)REE 眼虱,代表該buffer可以給到應(yīng)用程序,由應(yīng)用程序來繪畫席纽, 這樣的Slot SurfaceFlinger會根據(jù)是否有給它分配有GraphicBuffer分到兩個隊列里捏悬, 有GraphicBuffer的分配到mFreeBuffers里, 沒有GraphicBuffer的分配到mFreeSlots里润梯; 當(dāng)應(yīng)用申請走一個Slot時过牙,該Slot狀態(tài)會切換到DEQUEUED狀態(tài),該Slot會被放入mActiveBuffers隊列里纺铭; 當(dāng)應(yīng)用繪畫完成后Slot狀態(tài)會切到QUEUED狀態(tài)寇钉,所有QUEUED狀態(tài)的Slot會被放入mQueue隊列里; 當(dāng)一個Slot被HWC Service拿去合成后狀態(tài)會變?yōu)锳CQUIRED舶赔, 這個Slot會被從mQueue隊列中取出放入mActiveBuffers隊列里扫倡;

我們先來看一個BufferSlot管理的場景:

image-20210917162537755.png

Time1: 在上圖中,初始狀態(tài)下竟纳,有0撵溃, 1疚鲤, 2這三個BufferSlot, 由于它們都沒有分配過GraphicBuffer, 所以它們都位于mFreeSlots隊列里,當(dāng)應(yīng)用來dequeueBuffer時缘挑,SurfaceFlinger會先檢查在mFreeBuffers隊列中是否有Slot集歇, 如果有則直接分配該Slot給應(yīng)用。顯然此時mFreeBuffers里是空的语淘,這時Surfaceflinger會去mFreeSlots里去找出第一個Slot, 這時就找到了0號Slot, dequeueBuffer結(jié)束時應(yīng)用就拿到了0號Slot的使用權(quán)诲宇,于此同時SurfaceFlinger也會為0號Slot分配GraphicBuffer, 之后應(yīng)用將通過requestBuffer和importBuffer來獲取到該Slot的實際內(nèi)存空間。

應(yīng)用dequeueBuffer之后0號Slot切換到DEQUEUED狀態(tài)亏娜,并被放入mActiveBuffers列表焕窝。

Time2:應(yīng)用完成繪制后通過queueBuffer來提交繪制好的畫面,完成后0號Slot狀態(tài)變?yōu)镼UEUED狀態(tài)维贺,放入mQueue隊列它掂,此時1,2號Slot還停留在mFreeSlots隊列中溯泣。

Time3: 上面這個狀態(tài)會持續(xù)到下一個Vsync-sf信號到來虐秋,當(dāng)Vsync-sf信號到來時,SurfaceFlinger主線程會檢查mQueue隊列中是否有Slot, 有就意味著有應(yīng)用上幀垃沦,這時它會把該Slot從mQueue中取出放入mActiveBuffers隊列客给,并將Slot的狀態(tài)切換到ACQUIRED, 代表這個Slot已被拿去做畫面合成。那么這之后0號Slot被從mQueue隊列拿出放入mActivieBuffers里肢簿。

Time4:接下來應(yīng)用繼續(xù)調(diào)用dequeueBuffer申請buffer, 此時0號Slot在mActiveBuffers里靶剑,1,2號在mFreeSlots里池充,SurfaceFlinger仍然是先檢查mFreeBuffers里有沒有Slot, 發(fā)現(xiàn)還是沒有桩引,再檢查mFreeSlots里是否有,于是取出了1號Slot給到應(yīng)用側(cè)收夸,同時1號Slot狀態(tài)切換到DEQUEUED狀態(tài)坑匠, 放入mActiveBuffers里,

Time5:1號Slot應(yīng)用繪畫完畢卧惜,通過queueBuffer提交上來厘灼,這時1號Slot狀態(tài)由DEQUEUED狀態(tài)切換到了QUEUED狀態(tài),進入mQueue隊列咽瓷,之后將維持該狀態(tài)直到下一個Vsync-sf信號到來设凹。

Time6: 此時Vsync-sf信號到來,發(fā)現(xiàn)mQueue中有個Slot 1, 這時SurfaceFlinger主線程會把它取出茅姜,把狀態(tài)切換到ACQUIRED闪朱, 并放入mActiveBuffers里。

Time7:這時0號Slot HWC Service使用完畢,通過releaseBuffer還了回來监透,0號Slot的狀態(tài)將從ACQUIRED切換回FREE, Surfaceflinger會把它從mActivieBuffers里拿出來放入mFreeBuffers里桶错。注意這時放入的是mFreeBuffers里而不是mFreeSlots里,因為此時0號Slot是有GraphicBuffer的胀蛮。

在上述過程中SurfaceFlinger收到應(yīng)用dequeueBuffer請求時處在FREE狀態(tài)的Slot都還沒有分配過GraphicBuffer, 由之前的討論我們知道這通常發(fā)生在一個Surface的前幾幀時間內(nèi)院刁。如3 buffer下的前三幀。

我們再來看一下申請buffer時mFreeBuffers里有Slot時的情況:

image-20210917170805329.png

Time11:當(dāng)下的狀態(tài)是0粪狼,1兩個Slot都在mFreeBuffers里退腥,2號Slot在mActiveBuffers里,這時應(yīng)用來dequeueBuffer

Time12: SurfaceFlinger仍然會先查看mFreeBuffers列表看是否有可用的Slot, 發(fā)現(xiàn)0號可用再榄,于是0號Slot狀態(tài)由FREE切換到DEQUEUED狀態(tài)狡刘,并被放入mActiveBuffers里

Time13:應(yīng)用對0號Slot的繪圖完成后提交上來,這時狀態(tài)從DEQUEUED切換到QUEUED狀態(tài)困鸥,0號Slot被放入mQueue隊列嗅蔬,之后會維持該狀態(tài)直到下一下Vsync-sf信號到來

Time14:這時Vsync-sf信號到來,SurfaceFlinger主線程中檢查mQueue隊列中是否有Slot, 發(fā)現(xiàn)0號Slot疾就, 于是通過 aquireBuffer操作把0號Slot狀態(tài)切換到ACQUIRED

這個過程中應(yīng)用申請buffer時已經(jīng)有處于FREE狀態(tài)的Slot是分配過GraphicBuffer的澜术,這種情況多發(fā)生在Surface的穩(wěn)定上幀期。

再來關(guān)注一下acquireBuffer和releaseBuffer的過程:

image-20210917175233306.png

Time 23: 當(dāng)前狀態(tài)mQueue里有兩個buffer

Time 24:Vsync-sf信號到達猬腰,從mQueue隊列里取走了0號Slot,

Time 25: 再一次Vsync-sf到來鸟废,這時SurfaceFlinger會先查看mQueue隊列是否有buffer,發(fā)現(xiàn)有2號Slot姑荷, 會先取走2號Slot

Time 26: 此時0號Slot已經(jīng)被HWC Service使用完畢盒延,需要把Slot還回來,0號Slot在此刻進入mFreeBuffers隊列鼠冕。

這里需要注意的是兩個時序:

  1. 每次Vsync-sf信號到來時總是先查看mQueue隊列看是否有Layer上幀添寺,然后才會走到releaseBuffer把HWC Service使用的Slot回收回來
  2. 本次Vsync-sf被aquireBuffer取走的Slot總是會在下一個Vsync-sf時才會被release回來

由上述過程不難看出,如果應(yīng)用上幀速度較慢供鸠,比如其上幀周期時長大于兩倍屏幕刷新周期時畦贸,每次應(yīng)用來dequeueBuffer時前一次queueBuffer的BufferSlot都已經(jīng)被release回來了陨闹,這時總會在mFreeBuffers里找到可用的楞捂,那么就不需要三個Slot都分配出GraphicBuffer.

在應(yīng)用上幀過程中所涉及到的BufferSlot我們可以通過systrace來觀察:

image-20210917211102193.png

這兩個圖中顯示可以從systrace中看到每次dequeueBuffer和acquireBuffer所操作到的Slot是哪個,當(dāng)然releaseBuffer也可以在systrace上找到:

image-20210922175331118.png

從trace里我們還應(yīng)注意到趋厉,releaseBuffer是在postComposition里調(diào)用到的寨闹,這段代碼如下:

frameworks/native/services/surfaceflinger/surfaceflinger.cpp

void SurfaceFlinger::postComposition(){
    ATRACE_CALL();
    ......
    for(auto& layer:mLayersWithQueuedFrames){//這里只要主線程執(zhí)行到這個postComposition函數(shù)就一定會讓集合中的layer去執(zhí)行releasePendingBuffer, 而這個releasePendingBuffer里就會調(diào)用到releaseBuffer
        layer->releasePendingBuffer(dequeueReadyTime);
    }
    ......
}

mLayersWithQueuedFrames里的Layer是在這里被加入進來的:

bool SurfaceFlinger::handlePageFlip(){
    ......
    mDrawingState.traverse([&](Layer* layer){
        .......
        if(layer != nullptr && layer->hasReadyFrame()){//這里是判斷這個Layer是否有buffer更新,也就是mLayersWithQueuedFrames里放的是有上幀的layer
            ......
            mLayersWithQueuedFrames.push_back(layer);
            ......
        }
        .......
    });
    ......
}

在Layer的releasePendingBuffer里會把對應(yīng)的Slot的狀態(tài)切到FREE狀態(tài)君账,切換到FREE狀態(tài)后繁堡,是很可能被應(yīng)用dequeueBuffer獲取到的,那么怎么能確定buffer已經(jīng)被HWC Service使用完了呢?如果HWC Service還沒有使用完成椭蹄,而應(yīng)用申請到了這個buffer闻牡,buffer中的數(shù)據(jù)會出錯,怎么解決這個問題呢绳矩,這就要靠我們下一章要討論的Fence來解決罩润。

我們再從幀數(shù)據(jù)更新的流程上來看下bufferSlot的管理,從systrace(屏幕刷新率為90HZ)上可以觀察到的應(yīng)用上幀的全景圖:

image-20210917212902570.png

首先應(yīng)用(這里是以一個SurfaceView上幀為例)通過dequeueBuffer拿到了BufferSlot 0, 開始第1步繪圖翼馆,繪圖完成后通過queueBuffer將Slot 0提交到SurfaceFlinger, 下一個Vsync-sf信號到達后割以,開始第2步圖層處理,這時SurfaceFlinger通過aquireBuffer把Slot 0拿去給到HWC Service应媚,與此同時進入第3步HWC Service開始把多個圖層做合成严沥,合成完成后通過libdrm提供的接口通知DRM模塊通過DSI傳輸給DDIC, Panel 通過Disp Scan Gram把圖像顯示到屏幕。

5.4. 代碼接口

以應(yīng)用為生產(chǎn)者SurfaceFlinger為消費者為例中姜,BufferQueue的Slot管理核心代碼如BufferQueueCore消玄、BufferQueueProducer、BufferQueueConsumer組成丢胚, 生產(chǎn)者這邊還有一個Surface它是應(yīng)用側(cè)操作BufferQueue的接口:

相關(guān)代碼路徑如下:

Surface.cpp (frameworks\native\libs\gui)
BufferQueueCore.cpp (frameworks\native\libs\gui)
BufferQueueProducer.cpp (frameworks\native\libs\gui)
BufferQueueConsumer.cpp (frameworks\native\libs\gui)
IGraphicBufferProducer.cpp (frameworks\native\libs\gui)
IGraphicBufferConsumer.cpp (frameworks\native\libs\gui)
IConsumerListener.h (frameworks\native\libs\gui\include\gui)

由于Android規(guī)定莱找,BufferQueue的buffer必須是在Consumer側(cè)來分配,所以BufferQueue的核心Slot管理代碼是在SurfaceFlinger進程空間內(nèi)執(zhí)行的嗜桌,它們關(guān)系可以用如下圖來表示:

image-20210920103858070.png

相關(guān)代碼路徑:

IGraphicBufferProducer用來規(guī)定了BufferQueue向生產(chǎn)者提供的接口有哪些奥溺,比如請求buffer用到的dequeueBuffer, 提交buffer用到的queueBuffer等等:

class IGraphicBufferProducer : public RefBase {
    ......
    virtual status_t connect(const sp<IProducerListener>& listener,
            int api, bool producerControlledByApp, QueueBufferOutput* output) = 0;
    virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) = 0;
    virtual status_t dequeueBuffer(int* slot, sp<Fence>* fence, uint32_t w, uint32_t h,
                                   PixelFormat format, uint64_t usage, uint64_t* outBufferAge,
                                   FrameEventHistoryDelta* outTimestamps) = 0;
    virtual status_t queueBuffer(int slot, const QueueBufferInput& input,
            QueueBufferOutput* output) = 0;
    virtual status_t disconnect(int api, DisconnectMode mode = DisconnectMode::Api) = 0;
    ......
}

connect接口是在開始時上幀前調(diào)用一次,主要用來讓生產(chǎn)者和消費者溝通一些參數(shù)骨宠,比如api 版本浮定,buffer的尺寸,個數(shù)等层亿; disconnect用于在生產(chǎn)者不再生產(chǎn)斷開連接桦卒,用以通知消費端清理一些資源。

IGraphicBufferConsumer則規(guī)定了消費者和BufferQueueCore的接口有哪些匿又,比如查詢從mQueue隊列中取出buffer方灾,和還buffer到BufferQueue:

class IGraphicBufferConsumer : public RefBase {
    ......
    virtual status_t acquireBuffer(BufferItem* buffer, nsecs_t presentWhen,
                                   uint64_t maxFrameNumber = 0) = 0;
    virtual status_t releaseBuffer(int buf, uint64_t frameNumber, EGLDisplay display,
                                   EGLSyncKHR fence, const sp<Fence>& releaseFence) = 0;    
    ......    
}

5.5. 本章小結(jié)

讓我們用一張圖來總結(jié)說明一下在Triple Buffer下應(yīng)用連續(xù)上幀過程中三個buffer的使用情況,以及在此過程中應(yīng)用碌更, SurfaceFlinger是如何配合的:

image-20210917115726177.png

應(yīng)用在每個Vsync信號到來后都會通過dequeueBuffer/queueBuffer來申請buffer和提交繪圖數(shù)據(jù)裕偿,Surfaceflinger都會在下一個vsync信號到來時取走buffer去做合成和顯示, 并在下一下個vsync時將buffer還回來痛单,再次循環(huán)嘿棘。

6. Fence

Fence這個英文單詞通常代表柵欄,籬笆,圍墻旭绒,代表了此處是否可以通行鸟妙。它是內(nèi)核提供的不同硬件間同步機制焦人,在userspace層我們可以將它視為是一把鎖,它代表了某個硬件對共享資源的占用情況重父。

6.1. 為什么要有Fence

一般凡是共享的資源都要建立一個同步機制來管理花椭,比如在多線程編程中對臨界資源的通過加鎖實現(xiàn)互斥訪問,再比如BufferQueue中Surfaceflinger和應(yīng)用對共享內(nèi)存(幀緩沖)的訪問中有bufferstate來標(biāo)識共享內(nèi)存控制權(quán)的方法來做同步房午。沒有同步機制的無序訪問極可能造成數(shù)據(jù)混亂个从。

image-20210904123139567.png

上面圖中的BufferState的方式只是解決了在CPU管理之下,當(dāng)下共享內(nèi)存的控制權(quán)歸屬問題歪沃,但當(dāng)共享資源是在兩個硬件之中時嗦锐,情況就不同了,比如當(dāng)一個幀緩沖區(qū)共享內(nèi)存給到GPU時沪曙,GPU并不清楚CPU還有沒有在使用它奕污,同樣地,當(dāng)GPU在使用共享內(nèi)存時液走,CPU也不清楚GPU是否已使用完畢碳默,如下面這個例子:

image-20210904123110113.png

CPU調(diào)用OpenGL函數(shù)繪圖過程的一個簡化版流程如上圖所示,首先CPU側(cè)調(diào)用glClear清空畫布缘眶,再調(diào)用glXXX()來畫各種各樣的畫面嘱根,對于CPU來講在glXXX()執(zhí)行完畢后,它的繪圖工作已經(jīng)完成了巷懈。但其實glXXX()的具體工作是由GPU來完成的该抒,CPU側(cè)的glXXX()只是在向GPU傳達任務(wù)而已,任務(wù)傳達完并不意味著任務(wù)已經(jīng)完成了顶燕。真正任務(wù)做完是在GPU把glXXX()所對應(yīng)的工作做完才是真正的任務(wù)完成了凑保。從CPU下達完任務(wù)到GPU完成任務(wù)間存在時差,而且這個時差受GPU工作頻率影響并不是一個定值涌攻。在OpenGL的語境中CPU可以通過glFilish()來等待GPU完成所有工作欧引,但這顯然浪費了CPU本可以并行工作的時間,這段時間CPU沒有用來做別的事情恳谎。

在上面的例子中CPU下達了要在畫布上繪畫的指令給GPU, 而GPU什么時候畫完時間是不確定的芝此,這里的畫布就是共享資源,CPU和GPU的工作完全是異步的因痛。Fence提供了一種方式來處理不同硬件對共享資源的訪問控制婚苹。

image-20210904123150698.png

我們可以這樣來理解Fence的工作原理: Fence是一個內(nèi)核driver, 對一個Fence對象有兩種操作, signal和wait, 當(dāng)生產(chǎn)者(App)向GPU下達了很多繪圖指令(drawCall)后GPU開始工作婚肆,這里CPU就認為繪圖工作已經(jīng)完成了租副,之后把創(chuàng)建的Fence對象通過binder通知給消費者(SurfaceFlinger),SurfaceFlinger收到通知后坐慰,此時SurfaceFlinger并不知道GPU是否已經(jīng)繪圖完畢较性,即GPU是否已對共享資源訪問完畢用僧,消費者先通過Fence對象的wait方法等待,如果GPU繪圖完成會調(diào)用Fence的signal赞咙, 這時消費者就會從Fence對象的wait方法中跳出责循。即wait方法結(jié)束時就是GPU工作完成時。這個signal由kernel driver來完成攀操。有了Fence的情況下院仿,CPU在完成自已的工作后就可以繼續(xù)做別的事情,到了真正要使用共享資源時再通過Fence wait來和GPU同步速和,盡最大可能做到了讓不同硬件并行工作歹垫。

image-20210904123206565.png

6.2. 與BufferQueue協(xié)作方式

我們以App(productor)和SurfaceFlinger(Consumer)間的交互來看下Fence在其中的作用:

image-20210918212154468.png

首先App通過dequeueBuffer獲得某一Slot的使用權(quán),這時Slot的狀態(tài)切換到DEQUEUED狀態(tài)颠放,隨著dequeueBuffer函數(shù)返回的還有一個releaseFence對象排惨,這時因為releaseFence還沒有signaled, 這意味著雖然在CPU這邊已經(jīng)拿到了buffer的使用權(quán)碰凶,但別的硬件還在使用這個buffer, 這時的GPU還不能直接在上面繪畫暮芭,它要等releaseFence signaled后才能繪畫。 接下來我們先假設(shè)GPU的工作花費的時間較長欲低,在它完成之前CPU側(cè)APP已經(jīng)完成了queueBuffer動作辕宏,這時Slot的狀態(tài)已切換為QUEUED狀態(tài),或者vsync已經(jīng)到來狀態(tài)變?yōu)锳CQUIRED狀態(tài)砾莱, 這在CPU側(cè)代表該buffer給HWC去合成了趁仙,但這時HWC的硬件MDP還不能去讀里面的數(shù)據(jù),它還需要等待acauireFence的signaled信號蕴坪,只有等到了acquireFence的signaled信號才代表GPU的繪畫工作真正做完了隘马,GPU已經(jīng)完成了對幀緩沖區(qū)的訪問,這時HWC 的硬件才能去讀幀緩沖區(qū)的數(shù)據(jù)扫步,完成圖層合成的工作魔策。

同樣地,當(dāng)SurfaceFlinger執(zhí)行到releaseBuffer時河胎,并不能代表HWC 已經(jīng)完全完成合成工作了闯袒,很有可能它還在讀取緩沖區(qū)的內(nèi)容做合成, 但不妨礙releaseBuffer的流程執(zhí)行游岳,雖然HWC還在使用緩沖區(qū)做合成政敢,但幀緩沖區(qū)的Slot有可能被應(yīng)用申請走變成DEQUEUED狀態(tài),雖然Slot是DEQUEUED狀態(tài)這時GPU并不能直接存取它胚迫,它要等代表著HWC使用完畢的releaseFence的signaled信號喷户。

應(yīng)用側(cè)申請buffer的同時會獲取到一個fence對象(releaseFence):

frameworks/native/libs/gui/Surface.cpp

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ATRACE_CALL();
    .....
    sp<Fence> fence;
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
                                                            reqFormat, reqUsage, &mBufferAge,
                                                            enableFrameTimestamps ? &frameTimestamps
                                                                                  : nullptr);
    .....    
}

對應(yīng)SurfaceFlinger側(cè):

frameworks/native/libs/gui/BufferQueueProducer.cpp

status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
                                            uint32_t width, uint32_t height, PixelFormat format,
                                            uint64_t usage, uint64_t* outBufferAge,
                                            FrameEventHistoryDelta* outTimestamps) {
       ATRACE_CALL();
       .......
       *outFence = (mCore->mSharedBufferMode &&
                mCore->mSharedBufferSlot == found) ?
                Fence::NO_FENCE : mSlots[found].mFence;//把Slot里記錄的mFence對象返回出去,就是應(yīng)用側(cè)拿到的releaseFence
        mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
        mSlots[found].mFence = Fence::NO_FENCE;//不妨思考下這里為什么可以清成NO_FENCE访锻?
       .......                                          
 }

應(yīng)用側(cè)上幀時要創(chuàng)建一個fence來代表GPU的功能還在進行中褪尝,提交buffer的同時把fence對象傳給SurfaceFlinger:

frameworks/native/libs/gui/Surface.cpp

int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
    ATRACE_CALL();
    ......
    sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE);//創(chuàng)建一個fence, 這個就是SurfaceFlinger側(cè)的acquireFence
    ......
    IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp,//將fence放入input參數(shù)
            static_cast<android_dataspace>(mDataSpace), crop, mScalingMode,
            mTransform ^ mStickyTransform, fence, mStickyTransform,
            mEnableFrameTimestamps);
    ......
    status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);//把這個fence傳給surfaceflinger
    ......
}

對應(yīng)的SurfaceFlinger側(cè)從binder里獲取到應(yīng)用側(cè)傳來的fence對象(這個稱為acquireFence):

frameworks/native/libs/gui/BufferQueueProducer.cpp

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
    ATRACE_CALL();
    ......
    sp<Fence> acquireFence;
    ......
    input.deflate(&requestedPresentTimestamp, &isAutoTimestamp, &dataSpace,
            &crop, &scalingMode, &transform, &acquireFence, &stickyTransform,
            &getFrameTimestamps);
    ......
    mSlots[slot].mFence = acquireFence;//queueBuffer完成時Slot的mFence放的是acquireFence
    ......
}

我們來通過systrace觀察一個因GPU工作時間太長闹获,從而讓DRM工作線程卡在等Fence的情況:

image-20210920155058950.png

如上圖所示,complete_commit函數(shù)(從上面4.3章我們了解過這個函數(shù)是執(zhí)行SOC準備傳輸數(shù)據(jù)到DDIC的過程)執(zhí)行時前面有一段時間是陷于等待狀態(tài)了河哑,那么它在等誰呢避诽,從圖中所示我們可以看出它在等下73026號fence的signal信號。這種情況說明drm內(nèi)部的dma要去讀這miHoYo.yuanshen這個應(yīng)用的buffer時發(fā)現(xiàn)應(yīng)用的GPU還沒有把畫面畫完璃谨,它不得不等待它畫完才能開始讀取沙庐,但既然都已經(jīng)送到crtc_commit了,至少在CPU這側(cè)佳吞,該Slot的BufferState已經(jīng)是ACQUIRED狀態(tài)拱雏。

6.3 本章小結(jié)

在本章節(jié)中我們了解了不同硬件間同步工作的一種方法,了解了Fence在App畫面更新過程中的使用情況底扳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末古涧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子花盐,更是在濱河造成了極大的恐慌羡滑,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件算芯,死亡現(xiàn)場離奇詭異柒昏,居然都是意外死亡,警方通過查閱死者的電腦和手機熙揍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門职祷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人届囚,你說我怎么就攤上這事有梆。” “怎么了意系?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵泥耀,是天一觀的道長。 經(jīng)常有香客問我蛔添,道長痰催,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任迎瞧,我火速辦了婚禮夸溶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凶硅。我一直安慰自己缝裁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布足绅。 她就那樣靜靜地躺著捷绑,像睡著了一般韩脑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胎食,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天扰才,我揣著相機與錄音允懂,去河邊找鬼厕怜。 笑死,一個胖子當(dāng)著我的面吹牛蕾总,可吹牛的內(nèi)容都是我干的粥航。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼生百,長吁一口氣:“原來是場噩夢啊……” “哼递雀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚀浆,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缀程,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后市俊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杨凑,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年摆昧,在試婚紗的時候發(fā)現(xiàn)自己被綠了撩满。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡绅你,死狀恐怖伺帘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情忌锯,我是刑警寧澤伪嫁,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站偶垮,受9級特大地震影響礼殊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜针史,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一晶伦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧啄枕,春花似錦婚陪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脆淹。三九已至,卻和暖如春沽一,著一層夾襖步出監(jiān)牢的瞬間盖溺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工铣缠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烘嘱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓蝗蛙,卻偏偏與公主長得像蝇庭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捡硅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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