Surface阀坏,Layer城榛,SurfaceFlinger 與BufferQueue簡單記錄

根據(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ù)文檔惧互,一層層地減少信息量哎媚,到了后來都是精簡版的信息,會(huì)對理解造成阻礙喊儡,甚至有時(shí)候會(huì)造成錯(cuò)誤理解拨与。
Layer的合成過程這個(gè)過程類似于《OpenGL超級寶典》中的將繪圖坐標(biāo)映射到窗口,而繪圖坐標(biāo)艾猜,就類似于Surface买喧,SurfaceControl類似下面的映射的選擇,控制如何映射(例如存在2種不同的映射匆赃,具體如何映射是需要定義的淤毛,window 相關(guān)數(shù)據(jù)例如屏幕大小等在發(fā)送給SurfaceFlinger 后,還需要一個(gè)對象去控制映射)算柳,然后就是繪圖坐標(biāo)進(jìn)行映射低淡,這個(gè)過程相當(dāng)于SurfaceFlinger 使用Surface+ SurfaceControl去合成 Layer,最終顯示在窗口需要的圖形就是 Layer瞬项。

出處:OpenGL超級寶典第五版第一章 3D圖形和opengl簡介


1.png

2.png

3.png

Surface contain BufferQueue

Surface 包含BufferQueue蔗蹋。

image.png

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

OpenGL超級寶典第五版

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í)候真的是很大障礙亚皂,至少對我來說是這樣。

出處: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 請求一個(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ū),則系統(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è)層級相關(guān)的緩沖區(qū)的摘要。

簡單來說就是: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對象,
        // 該對象是用來更新顯示的緩沖區(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ù)中的操作畔派。
用文字表述流程可以參考下面铅碍,這就是雙緩沖的具體過程∠咭可以對照代碼多看幾遍胞谈。


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)書籍,簡單了解計(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范舀,這就是雙緩沖的簡化版流程合是。
使用了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谦炬。

簡化版關(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)容合作請聯(lián)系作者
  • 序言:七十年代末聋袋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子穴吹,更是在濱河造成了極大的恐慌幽勒,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件港令,死亡現(xiàn)場離奇詭異啥容,居然都是意外死亡锈颗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門咪惠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來击吱,“玉大人,你說我怎么就攤上這事遥昧「泊迹” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵炭臭,是天一觀的道長永脓。 經(jīng)常有香客問我,道長徽缚,這世上最難降的妖魔是什么憨奸? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮凿试,結(jié)果婚禮上排宰,老公的妹妹穿的比我還像新娘。我一直安慰自己那婉,他們只是感情好板甘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著详炬,像睡著了一般盐类。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呛谜,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天在跳,我揣著相機(jī)與錄音,去河邊找鬼隐岛。 笑死猫妙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的聚凹。 我是一名探鬼主播割坠,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妒牙!你這毒婦竟也來了彼哼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤湘今,失蹤者是張志新(化名)和其女友劉穎敢朱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔫饰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年琅豆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篓吁。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茫因,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杖剪,到底是詐尸還是另有隱情冻押,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布盛嘿,位于F島的核電站洛巢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏次兆。R本人自食惡果不足惜稿茉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芥炭。 院中可真熱鬧漓库,春花似錦、人聲如沸园蝠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彪薛。三九已至茂装,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間善延,已是汗流浹背少态。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留易遣,地道東北人况增。 一個(gè)月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像训挡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子歧强,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349