Surface,Layer,SurfaceFlinger 與BufferQueue簡(jiǎn)單記錄

根據(jù)官方文檔可知:
SurfaceFlingerWindowManager處接收bufferswindow 相關(guān)數(shù)據(jù)。
然后SurfaceFlingerbufferswindow相關(guān)數(shù)據(jù),合成一個(gè)Layer,發(fā)送給WindowManager,由WindowManager操控顯示在屏幕上。fling有扔,擲,拋的意思。flinger是指扔的人。SurfaceFlingerSurface扔給WindowManager。

SurfaceFlinger的作用就是合成Layer
LayerSurface的關(guān)系如下:
Layer = Surface + SurfaceControl
這里其實(shí)關(guān)于surface和layer的解釋并不直觀。
Android opengl es的資料遠(yuǎn)不如opengl多(特別是在我并不想看源碼的情況下)。可以參考opengl相關(guān)書籍。很多東西找不到詳細(xì)的資料解釋,原因是代碼很多地方并不是原創(chuàng)而是移植,因此只有使用類文檔,原理并不詳細(xì)。有時(shí)候看技術(shù)文檔,一層層地減少信息量,到了后來都是精簡(jiǎn)版的信息,會(huì)對(duì)理解造成阻礙,甚至有時(shí)候會(huì)造成錯(cuò)誤理解。
Layer的合成過程這個(gè)過程類似于《OpenGL超級(jí)寶典》中的將繪圖坐標(biāo)映射到窗口,而繪圖坐標(biāo),就類似于SurfaceSurfaceControl類似下面的映射的選擇,控制如何映射(例如存在2種不同的映射,具體如何映射是需要定義的,window 相關(guān)數(shù)據(jù)例如屏幕大小等在發(fā)送給SurfaceFlinger 后,還需要一個(gè)對(duì)象去控制映射),然后就是繪圖坐標(biāo)進(jìn)行映射,這個(gè)過程相當(dāng)于SurfaceFlinger 使用Surface+ SurfaceControl去合成 Layer,最終顯示在窗口需要的圖形就是 Layer

出處:OpenGL超級(jí)寶典第五版第一章 3D圖形和opengl簡(jiǎn)介


1.png

2.png

3.png

Surface contain BufferQueue

Surface 包含BufferQueue。

image.png

實(shí)際上Surface 就類似opengl中的幀緩沖區(qū)對(duì)象(FBO:FramebufferObject)的概念。
由于譯本可能會(huì)導(dǎo)致理解上存在一定誤差,建議中英文對(duì)照去看。網(wǎng)上均可下載。
例如當(dāng)時(shí)看這本書的時(shí)候,不太能理解緩沖區(qū)的概念,后面一看buffer這個(gè)名詞就比較好理解了。

OpenGL超級(jí)寶典第五版

FBO是包含了buffer的一個(gè)Object,并不占用存儲(chǔ)空間,真正占用存儲(chǔ)空間的是buffer,這個(gè)buffer存儲(chǔ)了可以渲染的數(shù)據(jù)(例如RGBYUV等數(shù)據(jù)),這個(gè)ObjectAndroid中定義為Surface類,Surface存儲(chǔ)的bufferGraphicBuffer,GraphicBuffer存儲(chǔ)在BufferQueue中。
如果想深入了解GraphicBuffer可以參考這篇:Android P 圖像顯示系統(tǒng)(二)GraphicBuffer和Gralloc分析
關(guān)于BufferQueue可以參考:深入淺出Android BufferQueue
BufferQueue分析:Buffer隊(duì)列

我并沒有怎么看懂,粗略看了下,C++看的我頭痛?;叵肫饋砀暗囊粋€(gè)做c++的同事聯(lián)調(diào)代碼的時(shí)候?qū)Ψ奖硎緅ava看得也很頭痛。語言有時(shí)候真的是很大障礙,至少對(duì)我來說是這樣。

出處:https://source.android.com/devices/graphics/arch-bq-gralloc
使用方創(chuàng)建并擁有 BufferQueue 數(shù)據(jù)結(jié)構(gòu),并且可存在于與其生產(chǎn)方不同的進(jìn)程中。當(dāng)生產(chǎn)方需要緩沖區(qū)時(shí),它會(huì)通過調(diào)用 dequeueBuffer() 從 BufferQueue 請(qǐng)求一個(gè)可用的緩沖區(qū),并指定緩沖區(qū)的寬度、高度、像素格式和使用標(biāo)記。然后,生產(chǎn)方填充緩沖區(qū)并通過調(diào)用 queueBuffer() 將緩沖區(qū)返回到隊(duì)列。接下來,使用方通過 acquireBuffer() 獲取該緩沖區(qū)并使用該緩沖區(qū)的內(nèi)容。當(dāng)使用方操作完成后,它會(huì)通過調(diào)用 releaseBuffer() 將該緩沖區(qū)返回到隊(duì)列。同步框架可控制緩沖區(qū)在 Android 圖形管道中移動(dòng)的方式。
BufferQueue 的一些特性(例如可以容納的最大緩沖區(qū)數(shù))由生產(chǎn)方和使用方聯(lián)合決定。但是,BufferQueue 會(huì)根據(jù)需要分配緩沖區(qū)。除非特性發(fā)生變化,否則將會(huì)保留緩沖區(qū);例如,如果生產(chǎn)方請(qǐng)求具有不同大小的緩沖區(qū),則系統(tǒng)會(huì)釋放舊的緩沖區(qū),并根據(jù)需要分配新的緩沖區(qū)。
BufferQueue 永遠(yuǎn)不會(huì)復(fù)制緩沖區(qū)內(nèi)容,因?yàn)橐苿?dòng)如此多的數(shù)據(jù)是非常低效的操作。相反,緩沖區(qū)始終通過句柄進(jìn)行傳遞。

出處:https://source.android.com/devices/graphics/arch-sh
用于顯示 Surface 的 BufferQueue 通常配置為三重緩沖。緩沖區(qū)是按需分配的,因此,如果生產(chǎn)方足夠緩慢地生成緩沖區(qū)(例如在 60 fps 的顯示屏上以 30 fps 的速度進(jìn)行緩沖),隊(duì)列中可能只有兩個(gè)分配的緩沖區(qū)。按需分配緩沖區(qū)有助于最大限度地減少內(nèi)存消耗。您可以看到與 dumpsys SurfaceFlinger 輸出中每個(gè)層級(jí)相關(guān)的緩沖區(qū)的摘要。

簡(jiǎn)單來說就是:Buffer隊(duì)列中存在一個(gè)或多個(gè)buffer(按需分配,通常是三重緩沖),所謂3重緩沖,就是使用3個(gè)buffer去存儲(chǔ)和處理數(shù)據(jù),2重緩沖就是使用2個(gè)buffer。BufferQueue通過handle進(jìn)行傳遞buffer中的內(nèi)容而不是copy(類似于handle+MessageQueue)。

下面來看看surface的雙緩沖。

//frameworks\native\libs\gui\Surface.cpp
status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
    if (mLockedBuffer != 0) {
        ALOGE("Surface::lock failed, already locked");
        return INVALID_OPERATION;
    }

    if (!mConnectedToCpu) {
        int err = Surface::connect(NATIVE_WINDOW_API_CPU);
        if (err) {
            return err;
        }
        // we're intending to do software rendering from this point
        setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
    }

    ANativeWindowBuffer* out;
    int fenceFd = -1;
    status_t err = dequeueBuffer(&out, &fenceFd);
    ALOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err));
    if (err == NO_ERROR) {
        sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
        const Rect bounds(backBuffer->width, backBuffer->height);

        Region newDirtyRegion;
        if (inOutDirtyBounds) {
            newDirtyRegion.set(static_cast<Rect const&>(*inOutDirtyBounds));
            newDirtyRegion.andSelf(bounds);
        } else {
            newDirtyRegion.set(bounds);
        }

        // figure out if we can copy the frontbuffer back
        const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
        const bool canCopyBack = (frontBuffer != 0 &&
                backBuffer->width  == frontBuffer->width &&
                backBuffer->height == frontBuffer->height &&
                backBuffer->format == frontBuffer->format);

        if (canCopyBack) {
            // copy the area that is invalid and not repainted this round
            const Region copyback(mDirtyRegion.subtract(newDirtyRegion));
            if (!copyback.isEmpty()) {
                copyBlt(backBuffer, frontBuffer, copyback, &fenceFd);
            }
        } else {
            // if we can't copy-back anything, modify the user's dirty
            // region to make sure they redraw the whole buffer
            newDirtyRegion.set(bounds);
            mDirtyRegion.clear();
            Mutex::Autolock lock(mMutex);
            for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
                mSlots[i].dirtyRegion.clear();
            }
        }


        { // scope for the lock
            Mutex::Autolock lock(mMutex);
            int backBufferSlot(getSlotFromBufferLocked(backBuffer.get()));
            if (backBufferSlot >= 0) {
                Region& dirtyRegion(mSlots[backBufferSlot].dirtyRegion);
                mDirtyRegion.subtract(dirtyRegion);
                dirtyRegion = newDirtyRegion;
            }
        }

        mDirtyRegion.orSelf(newDirtyRegion);
        if (inOutDirtyBounds) {
            *inOutDirtyBounds = newDirtyRegion.getBounds();
        }

        void* vaddr;
        status_t res = backBuffer->lockAsync(
                GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                newDirtyRegion.bounds(), &vaddr, fenceFd);

        ALOGW_IF(res, "failed locking buffer (handle = %p)",
                backBuffer->handle);

        if (res != 0) {
            err = INVALID_OPERATION;
        } else {
            mLockedBuffer = backBuffer;
            outBuffer->width  = backBuffer->width;
            outBuffer->height = backBuffer->height;
            outBuffer->stride = backBuffer->stride;
            outBuffer->format = backBuffer->format;
            outBuffer->bits   = vaddr;
        }
    }
    return err;
}

以下出處:顯示緩沖區(qū)的作用

status_t Surface::lock(SurfaceInfo* other, Region* dirtyIn, bool blocking) 
{
    ......
    android_native_buffer_t* out;
    // 分配新的內(nèi)存空間并將其加入緩沖隊(duì)列,返回給out
    status_t err = dequeueBuffer(&out);
    if (err == NO_ERROR) {
        // 從剛才得到的buffer創(chuàng)建GraphicBuffer對(duì)象,
        // 該對(duì)象是用來更新顯示的緩沖區(qū),叫做背景緩沖區(qū)。
        // 重畫動(dòng)作在背景緩沖區(qū)進(jìn)行。
        sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
        // 鎖定這片內(nèi)存
        err = lockBuffer(backBuffer.get());
        if (err == NO_ERROR) {
            const Rect bounds(backBuffer->width, backBuffer->height);
            const Region boundsRegion(bounds);
            Region scratch(boundsRegion);
            // newDirtyRegion是需要重畫的區(qū)域
            Region& newDirtyRegion(dirtyIn ? *dirtyIn : scratch);
            newDirtyRegion &= boundsRegion;
 
            // 已經(jīng)顯示出來的frontBuffer叫做前景緩沖區(qū)
            // 判斷是否需要拷貝frontBuffer到backBuffer
            const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
            const bool canCopyBack = (frontBuffer != 0 &&
                    backBuffer->width  == frontBuffer->width &&
                    backBuffer->height == frontBuffer->height &&
                    backBuffer->format == frontBuffer->format &&
                    !(mFlags & ISurfaceComposer::eDestroyBackbuffer));
 
            mDirtyRegion = newDirtyRegion;
 
            // 如果需要做拷貝動(dòng)作,則將frontBuffer中非newDirtyRegion區(qū)域
            // 拷貝到backBuffer中
            if (canCopyBack) {
                const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion));
                if (!copyback.isEmpty())
                    copyBlt(backBuffer, frontBuffer, copyback);
            } else {
                // 如果不需要拷貝,則重畫整個(gè)區(qū)域
                newDirtyRegion = boundsRegion;
            }
 
            mOldDirtyRegion = newDirtyRegion;
 
            // 鎖定將要畫圖的緩沖區(qū),并返回一個(gè)地址給調(diào)用者
            void* vaddr;
            status_t res = backBuffer->lock(
                    GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                    newDirtyRegion.bounds(), &vaddr);
             
            // 返回給SurfaceInfo參數(shù)other
            mLockedBuffer = backBuffer;
            other->w      = backBuffer->width;
            other->h      = backBuffer->height;
            other->s      = backBuffer->stride;
            other->usage  = backBuffer->usage;
            other->format = backBuffer->format;
            other->bits   = vaddr;
        }
    }
    mApiLock.unlock();
    return err;
}

從注釋中大致可以了解lock函數(shù)中做了些什么操作。surface的雙緩沖,關(guān)鍵在于Surface.cpp中的lock函數(shù)中的操作。
用文字表述流程可以參考下面,這就是雙緩沖的具體過程??梢詫?duì)照代碼多看幾遍。


https://wenku.baidu.com/view/1ff720b565ce050876321395.html

前景就是已經(jīng)顯示的,后景是未顯示的。如果一個(gè)buffer已顯示,那么它就是frontBuffer ,如果未顯示那就是backBuffer,frontBuffer 和backBuffer在使用過程中是會(huì)翻轉(zhuǎn)的。

出處:SurfaceView 的雙緩沖
系統(tǒng)先從 buffer 池中 dequeueBuffer 出來一個(gè)可用的 out,然后將 out 賦給 backBuffer。mPostedBuffer 為已經(jīng)顯示的 buffer,將 mPostedBuffer 的內(nèi)容賦給 frontBuffer

例如,存在一個(gè)地址為0-8的空間。

0.png

執(zhí)行lock函數(shù)過程中,假設(shè)此時(shí)顯示的buffer數(shù)據(jù)為0010,待顯示的數(shù)據(jù)為0001。如下所示。即backBuffer為0001,frontBuffer為0010。此時(shí)為0-3顯示front,4-7顯示back。這里的數(shù)據(jù)是隨便寫的。實(shí)際數(shù)據(jù)是使用Canvas、OpenGL ES 或 Vulkan等去生成。

出處:https://source.android.com/devices/graphics#image_stream_producers
應(yīng)用開發(fā)者可通過三種方式將圖像繪制到屏幕上:使用 Canvas、OpenGL ES 或 Vulkan。無論開發(fā)者使用什么渲染 API,一切內(nèi)容都會(huì)渲染到Surface。

一般使用Activity顯示View是通過Canvas去產(chǎn)生圖像數(shù)據(jù)。
可參考這篇:探究Android View 繪制流程,Canvas 的由來
View的繪制過程中會(huì)調(diào)用這句生成canvas。

final DisplayListCanvas canvas = renderNode.start(width, height);

然后會(huì)進(jìn)入c++的領(lǐng)域,最后應(yīng)該會(huì)進(jìn)入BufferQueue,供surface.cpp調(diào)用。大致流程就這樣。
關(guān)于渲染等概念,建議通讀OpenGL與計(jì)算機(jī)圖形學(xué)等相關(guān)書籍,簡(jiǎn)單了解計(jì)算機(jī)圖形是如何到屏幕上的。

1.png

當(dāng)backbuffer經(jīng)SurfaceFlinger合成Layer后,back和front就進(jìn)行了交換,0-3顯示front,4-7顯示back。

2.png

當(dāng)再產(chǎn)生新的數(shù)據(jù)時(shí),例如0011,賦值給back。


3.png

然后再合成Layer顯示后再交換。


4.png

然后再次產(chǎn)生新數(shù)據(jù)0111,函數(shù)中會(huì)執(zhí)行賦值給back。


5.png

然后在backbuffer顯示后,front和back再次進(jìn)行交換。

6.png

數(shù)據(jù)變化為:0001 0010 -> 0001 0011 -> 0111 0011,這就是雙緩沖的簡(jiǎn)化版流程。
使用了2個(gè)buffer,當(dāng)一個(gè)空間用于合成圖形時(shí),另一個(gè)空間用于接收產(chǎn)生的數(shù)據(jù),作用是改善卡頓。
但是僅僅這樣還不夠完善,因此還需要使用垂直同步(VSync),具體原因可參考這篇Android圖形顯示系統(tǒng)(一)
。

SurfaceFlingerBufferQueue的關(guān)系:SurfaceFlingerbufferwindow相關(guān)數(shù)據(jù),合成一個(gè)Layer,而這個(gè)bufferSurfaceFlingerBufferQueue獲?。ㄕ{(diào)用dequeueBuffer獲取buffer),然后放到backBuffer中的backBuffer。

簡(jiǎn)化版關(guān)系圖

后記:這篇文章雖然字?jǐn)?shù)不多,但是其實(shí)花了好幾天才寫完,看一篇文章雖然很快,但是真正理解有時(shí)候并沒有沒有那么容易。部分知識(shí)點(diǎn)其實(shí)還是存疑,例如具體運(yùn)行過程,BufferQueue的運(yùn)行原理等。但是由于這篇文的初衷是surface的雙緩沖,以及surface到底是什么,并且由于c++的代碼我很難深入看下去,因此沒有深入下去。最后,由于本人知識(shí)局限性,內(nèi)容可能會(huì)存在錯(cuò)誤,如果發(fā)現(xiàn)了,希望能指出,防止誤導(dǎo)他人。

參考鏈接:
AndroidO 下圖形顯示框架變化介紹
https://source.android.com/devices/graphics/arch-sh
SurfaceView 的雙緩沖
深入淺出Android BufferQueue
深入理解Android:卷1_8.4.5 lockCanvas和unlockCanvasAndPost分析
SurfaceView的雙緩沖機(jī)制
顯示緩沖區(qū)的作用
Android_GDI基本框架and Surface Flinger
隊(duì)列
Android圖形顯示系統(tǒng)(一)
Android graphics 學(xué)習(xí)-生產(chǎn)者、消費(fèi)者、BufferQueue介紹

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容