GUI系統之SurfaceFlinger(3)Android中的本地窗口FramebufferNativewindow

文章都是通過閱讀源碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正
轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/



1.1 Android中的本地窗口

在OpenGL的學習過程中,我們不斷提及“本地窗口”(NativeWindow)這一概念。那麼對於Android系統來說,它是如何將OpenGL ES本地化的呢,或者說,它提供了什麼樣的本地窗口?

根據整個Android系統的GUI設計理念,我們不難猜想到至少需要兩種本地窗口:

Ø  面向管理者(SurfaceFlinger)

既然SurfaceFlinger扮演了系統中所有UI界面的管理者,那麼它無可厚非地需要直接或間接地持有“本地窗口”。從前一小節我們已經知道,這個窗口就是FramebufferNativeWindow

Ø  面向應用程序

我們先給出答案,這類窗口是SurfaceTextureClient

 

有不少讀者可能會覺得困惑,爲什麼需要兩種窗口,同一個系統不是應該只有一種窗口嗎?比如這樣子:

 


圖 11‑4理想的窗口系統

 

這個圖中,由Window來管理Framebuffer。我們打個比方來說,OpenGL就像是一臺通用的打印機一樣,只要輸入正確的指令,它就能按照要求輸出結果;而Window則是“紙”,它是用來承載OpenGL的輸出結果的。OpenGL並不介意Window是A4紙或者是A6,甚至是塑料紙也沒有關係,對它來說都只是“本地窗口”。

理解了這個圖後,我們再來思考下,這樣的模型是否能符合Android的要求?假如整個系統僅有一個需要顯示UI的程序,我們有理由相信它是可以勝任的。但是如果有N個UI程序的情況呢?Framebuffer顯然只有一個,不可能讓各個應用程序自己單獨管理。

這樣子問題就來了,該如何改進呢?下面這個方法如何?

 


圖 11‑5 改進的窗口系統

 

在這個改進的窗口系統中,我們有了兩類本地窗口,即Window-1和Window-2。第一種窗口是能直接顯示在終端屏幕上的——它使用了幀緩衝區,而後一種Window實際上是從內存緩衝區分配的空間。當系統中存在多個應用程序時,這能保證它們都可以獲得一個“本地窗口”,並且這些窗口最終也能顯示到屏幕上——SurfaceFlinger會收集所有程序的顯示需求,對它們做統一的圖像混合操作(有點類似於AudioFlinger),然後輸出到自己的Window-1上。

當然,這個改進的窗口系統有一個前提,即應用程序與SurfaceFlinger都是基於OpenGL ES來實現的。有沒有其它選擇呢?答案是肯定的,比如應用程序端完全可以採用Skia等第三方的圖形庫,只要保持它們與SurfaceFlinger間的“協議”不變就可以了,如下所示:


圖 11‑6 另一種改進的窗口系統

 

理論上來說,採用哪一種方式都是可行的。不過對於開發人員,特別是沒有OpenGLES項目經驗的人而言,前一種系統的門檻相對較高。事實上,Android系統同時提供了這兩種實現來供上層選擇。正常情況下我們按照SDK嚮導生成的apk應用,就屬於後面的情況;而對於希望使用OpenGLES來完成複雜的界面渲染的應用開發者,也可以使用GLSurfaceView來達到目標。

在接下來的源碼分析中,我們將對上面所提出的假設做進一步驗證。

1.1.1 FramebufferNativeWindow

 

先把EGL創建一個Window Surface的函數原型列出如下:

EGLSurface eglCreateWindowSurface(  EGLDisplay dpy, EGLConfig config,

                          NativeWindowType  window, const EGLint *attrib_list);

顯然不論是哪一種本地窗口,它都必須要與NativeWindowType保持一致,否則就無法正常使用EGL了。先從數據類型的定義來看下這個window參數有什麼特別之處:

/*frameworks/native/opengl/include/egl/Eglplatform.h*/

typedef  EGLNativeWindowType  NativeWindowType;//注意這兩種類型其實是一樣的

#if defined(_WIN32) || defined(__VC32__) &&!defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)

                /* Win32 和WinCE系統下的定義 */

    …

    typedef  HWND    EGLNativeWindowType;

#elif defined(__WINSCW__) || defined(__SYMBIAN32__)  /* Symbian系統*/

    …

     typedef  void *  EGLNativeWindowType;

#elif defined(__ANDROID__) || defined(ANDROID) /* Android系統 */

    struct ANativeWindow;

    …

    typedef struct ANativeWindow*          EGLNativeWindowType;

    …

#elif defined(__unix__) /* Unix系統*/

    …

    typedef  Window   EGLNativeWindowType;

#else

    #error "Platform notrecognized"

#endif

我們以下表來概括在不同的操作系統平臺下EGLNativeWindowType所對應的具體數據類型:

表格 11‑2 不同平臺下的EGLNativeWindowType

操作系統

數據類型

Win32, WinCE

HWND,即句柄

Symbian

Void*

Android

ANativeWindow*          

Unix

Window

其它

暫時不支持

 

由於OpenGL ES並不是只針對某一個操作系統平臺設計的,它在很多地方都要考慮兼容性和可移植性,這個EGLNativeWindowType就是其中一個例子。它在不同的系統中對應的是不一樣的數據類型,比如Android中就指的是ANativeWindow指針。

ANativeWindow的定義在Window.h中:

/*system/core/include/system/Window.h*/

struct ANativeWindow

{…

    const uint32_t flags; //與Surface或updater有關的屬性

    const int   minSwapInterval;//所支持的最小交換間隔時間

    const int   maxSwapInterval;//所支持的最大交換間隔時間

    const float xdpi; //水平方向的密度,以dpi爲單位

    const float ydpi;//垂直方向的密度,以dpi爲單位

    intptr_t    oem[4];//爲OEM定製驅動所保留的空間

    int     (*setSwapInterval)(struct ANativeWindow*window, int interval);

    int     (*dequeueBuffer)(struct ANativeWindow*window, struct ANativeWindowBuffer** buffer);

    int     (*lockBuffer)(struct ANativeWindow*window, struct ANativeWindowBuffer* buffer);   

    int    (*queueBuffer)(struct ANativeWindow* window, struct ANativeWindowBuffer*buffer);

    int     (*query)(const struct ANativeWindow*window, int what, int* value);

    int     (*perform)(struct ANativeWindow* window,int operation, ... );

    int     (*cancelBuffer)(struct ANativeWindow*window, struct ANativeWindowBuffer* buffer);

    void* reserved_proc[2];

};

我們在下表中詳細解釋這個類的成員函數。

表格 11‑3 ANativeWindow類成員函數解析

Member Function

Description

setSwapInterval

設置交換間隔時間,後面我們會講解swap的作用

dequeueBuffer

EGL通過這個接口來申請一個buffer。以前面我們所舉的例子來說,兩個本地窗口所提供的buffer分別來自於幀緩衝區和內存空間。單詞“dequeue”的字面意思是“出隊列”,這從側面告訴我們,一個Window所包含的buffer很可能不只一份

lockBuffer

申請到的buffer並沒有被鎖定,這種情況下是不允許我們去修改其中的內容的。所以我們必須要先調用lockBuffer來獲得一個鎖

queueBuffer

當EGL對一塊buffer渲染完成後,它調用這個接口來unlock和post buffer

query

用於向本地窗口諮詢相關信息

perform

用於執行本地窗口支持的各種操作,比如:

NATIVE_WINDOW_SET_USAGE

NATIVE_WINDOW_SET_CROP

NATIVE_WINDOW_SET_BUFFER_COUNT

NATIVE_WINDOW_SET_BUFFERS_TRANSFORM

NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP

等等

cancelBuffer

這個接口可以用來取消一個已經dequeued的buffer,要特別注意同步的問題

 

從上面對ANativeWindow的描述可以看出,它更像是一份“協議”,規定了一個本地窗口的形態和功能。這對於支持多種本地窗口的系統是必須的,因爲只有這樣子我們才能針對某種特定的平臺窗口,來填充具體的實現。

這個小節中我們先來看下FramebufferNativeWindow是如何履行這份“協議”的。

FramebufferNativeWindow本身代碼並不多,下面分別選取其構造函數及dequeue()兩個函數來分析,其它部分的實現都是類似的,大家可以自行閱讀。

(1) FramebufferNativeWindow構造函數

基於FramebufferNativeWindow的功能,可以大概推測出它的構造函數裏應該至少完成如下的初始化操作:

Ø  加載GRALLOC_HARDWARE_MODULE_ID模塊,詳細流程我們在Gralloc小節已經解釋過了

Ø  分別打開fb和gralloc設備。我們在Gralloc小節也已經分析過了,打開後的設備由全局變量fbDev和grDev管理

Ø  根據設備的屬性來給FramebufferNativeWindow賦初值

Ø  根據FramebufferNativeWindow的實現來填充ANativeWindow中的“協議”

Ø  其它一些必要的初始化

 

下面從源碼入手看下每個步驟具體是怎樣實現的。

/*frameworks/native/libs/ui/FramebufferNativeWindow.cpp*/

FramebufferNativeWindow::FramebufferNativeWindow()

    : BASE(), fbDev(0),grDev(0), mUpdateOnDemand(false)

{

    hw_module_t const* module;

if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) {…

   int err;

        int i;

        err = framebuffer_open(module, &fbDev);

        err = gralloc_open(module, &grDev);

        /*上面這部分我們在前幾個小節已經分析過了,不清楚的可以回頭看下*/

        …

        mNumBuffers = NUM_FRAME_BUFFERS; //buffer個數,目前爲2

        mNumFreeBuffers =NUM_FRAME_BUFFERS; //可用的buffer個數,初始時所有buffer可用

        mBufferHead =mNumBuffers-1;

        …

        for (i = 0; i <mNumBuffers; i++) //給每個buffer初始化

        {

             buffers[i] = new NativeBuffer(fbDev->width,fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB);

        }// NativeBuffer是什麼?

 

        for (i = 0; i <mNumBuffers; i++) //給每個buffer分配空間

        {

              err =grDev->alloc(grDev, fbDev->width, fbDev->height, fbDev->format,

                        GRALLOC_USAGE_HW_FB, &buffers[i]->handle,&buffers[i]->stride);

                                                …

        }

             /*爲本地窗口賦屬性值*/

       const_cast<uint32_t&>(ANativeWindow::flags) = fbDev->flags;

        const_cast<float&>(ANativeWindow::xdpi)= fbDev->xdpi;

       const_cast<float&>(ANativeWindow::ydpi) = fbDev->ydpi;

       const_cast<int&>(ANativeWindow::minSwapInterval) =fbDev->minSwapInterval;

       const_cast<int&>(ANativeWindow::maxSwapInterval) =fbDev->maxSwapInterval;

    } else {

        ALOGE("Couldn'tget gralloc module");

    }

    /*以下履行窗口“協議”*/

   ANativeWindow::setSwapInterval = setSwapInterval;

   ANativeWindow::dequeueBuffer = dequeueBuffer;

    ANativeWindow::lockBuffer= lockBuffer;

    ANativeWindow::queueBuffer= queueBuffer;

    ANativeWindow::query =query;

    ANativeWindow::perform =perform;

}

 

這個函數邏輯上很簡單,開頭一部分我們已經分析過了,就不再贅述。需要注意的是FramebufferNativeWindow是如何分配buffer的,換句話說,後面的dequeue所獲得的緩衝區是從何而來。

成員變量mNumBuffers代表了FramebufferNativeWindow所管理的buffer總數,NUM_FRAME_BUFFERS當前定義爲2。有人可能會覺得奇怪,既然FramebufferNativeWindow對應的是真實的物理屏幕,那麼爲什麼需要兩個buffer呢?

假設我們需要繪製這樣一個畫面,包括兩個三角形和三個圓形,最終結果如下圖所示:

 


圖 11‑7 希望在屏幕上顯示的完整結果

 

先來看只有一個buffer的情況,這意味着我們是直接以屏幕爲畫板來實時做畫的——我們畫什麼,屏幕上就顯示什麼。以繪製上圖中的每一個三角形或圓形都需要0.5秒爲例,那麼總計耗時應該是0.5*5=2.5秒。換句話說,用戶在不同時間點所看到的屏幕是這樣子的:

 

圖 11‑8 只有一個buffer的情況

 

對於用戶來說,他將看到一個不斷刷新的畫面。通俗來講,就是畫面很“卡”。對於圖像刷新很頻繁的情況,比如遊戲場景,用戶的體驗就會更差。那麼有什麼解決的辦法呢?我們知道,出現這種現象的原因就是程序直接以屏幕爲繪圖板,把還沒有準備就緒的圖像直接呈現給了用戶。換句話說,如果可以等待整幅圖繪製完成以後再刷新到屏幕上,那麼對於用戶來說,他在任何時候看到的都是正確而完整的圖像,問題也就解決了。下圖解釋了當採用兩個緩衝區時的情況:

 


圖 11‑9 採用兩個緩衝區的情況

 

上圖中所述的就是通常所稱的“雙緩衝”(Double-Buffering)技術。除此以外,其實還有三緩衝(TripleBuffering)、四緩衝(Quad Buffering)等等,我們將它們統稱爲“多緩衝”(MultipleBuffering)機制。

理解了爲什麼需要雙緩衝以後,我們再回過頭來看FramebufferNativeWindow的構造函數。接下來就要解決另一個問題,即兩個緩衝區空間是從哪裏分配的?根據前幾個小節的知識,應該是要向HAL層的Gralloc申請。兩個緩衝區以全局變量buffers[NUM_FRAME_BUFFERS]來記錄,每個數據元素是一個NativeBuffer,這個類定義如下:

class NativeBuffer : public ANativeObjectBase< ANativeWindowBuffer,

       NativeBuffer,LightRefBase<NativeBuffer> >

{…

所以這個“本地緩衝區”繼承了ANativeWindowBuffer的特性,後者的定義在/system/core/include/system/Window.h中:

typedef struct ANativeWindowBuffer

{…

    int width; //寬

    int height;//高

    …

    buffer_handle_t handle;/*代表內存塊的句柄,比如ashmem機制。

                         可以參考本書的共享內存章節*/

} ANativeWindowBuffer_t;

第一個for循環裏先給各buffer創建相應的實例(new NativeBuffer),其中的屬性值都來源於fbDev,比如寬、高、格式等等。緊隨其後的就是調用Gralloc設備的alloc()方法:

err = grDev->alloc(grDev, fbDev->width,fbDev->height, fbDev->format,

               GRALLOC_USAGE_HW_FB, &buffers[i]->handle, &buffers[i]->stride);

注意第5個參數,它代表所要申請的緩衝區的用途,定義在hardware/libhardware/include/hardware/Gralloc.h中,目前已經支持幾十種,比如:

Ø  GRALLOC_USAGE_HW_TEXTURE

緩衝區將用於OpenGL ES Texture

Ø  GRALLOC_USAGE_HW_RENDER

緩衝區將用於OpenGL ES的渲染

Ø  GRALLOC_USAGE_HW_2D

緩衝區會提供給2D 硬件圖形設備

Ø  GRALLOC_USAGE_HW_COMPOSER

緩衝區用於HWComposer HAL模塊

Ø  GRALLOC_USAGE_HW_FB

緩衝區用於framebuffer設備

Ø  GRALLOC_USAGE_HW_VIDEO_ENCODER

緩衝區用於硬件視頻編碼器

等等。。。

 

這裏是要用於在終端屏幕上顯示的,所以申請的usage類型是GRALLOC_USAGE_HW_FB,對應的Gralloc中的實現是[email protected];假如是其它用途的申請,則對應[email protected]。不過,如果底層只允許一個buffer(不支持page-flipping的情況),那麼gralloc_alloc_framebuffer也同樣可能只返回一個ashmem中申請的“內存空間”,真正的“幀緩衝區”要在post時纔會被用到。

另外,當前可用(free)的buffer數量由mNumFreeBuffers管理,這個變量的初始值也是NUM_FRAME_BUFFERS,即總共有2個可用緩衝區。在程序後續的運行過程中,始終由mBufferHead來指向下一個將被申請的buffer(注意,不是下一個可用buffer)。也就是說每當用戶向FramebufferNativeWindow申請一個buffer時(dequeueBuffer),這個mBufferHead就會增加1;一旦它的值超過NUM_FRAME_BUFFERS,則還會變成0,如此就實現了循環管理,後面dequeueBuffer時我們再詳細解釋。

一個本地窗口包含了很多屬性值,比如各種標誌(flags)、橫縱座標的密度值等等。這些數值都可以從fb設備上取到,我們需要將它賦予剛生成的FramebufferNativeWindow實例。

最後,就是履行ANativeWindow的協議了。FramebufferNativeWindow會將其成員函數填充到ANativeWindow中的函數指針中,比如:

ANativeWindow::setSwapInterval = setSwapInterval;

ANativeWindow::dequeueBuffer = dequeueBuffer;

這樣子OpenGL ES才能通過一個ANativeWindow來正確地與本地窗口建立連接,下面我們就詳細分析下其中的dequeueBuffer。

(2)dequeueBuffer

這個函數很短,只有二十幾行,不過是FramebufferNativeWindow中的核心。OpenGL ES就是通過它來分配一個用於渲染的緩衝區的,與之相對應的是queueBuffer。

int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window,ANativeWindowBuffer** buffer)

{

    FramebufferNativeWindow*self = getSelf(window); /*Step1*/

    Mutex::Autolock_l(self->mutex); /*Step2*/

/*Step3. 計算mBufferHead */

    int index =self->mBufferHead++;

    if (self->mBufferHead>= self->mNumBuffers)

        self->mBufferHead =0;

 

    /*Step4. 當前沒有可用緩衝區*/

    while (!self->mNumFreeBuffers){

       self->mCondition.wait(self->mutex);

}

/*Step5. 有人釋放了緩衝區*/

   self->mNumFreeBuffers--;

   self->mCurrentBufferIndex = index;

    *buffer =self->buffers[index].get();

 

    return 0;

}

Step1@ FramebufferNativeWindow::dequeueBuffer, 這裏先將入參中ANativeWindow 類型的變量window強制轉化爲FramebufferNativeWindow。因爲前者是後者的父類,所以這樣的轉化當然是有效的。不過細心的讀者可能會發現,爲什麼函數入參中還要特別傳入一個ANativeWindow對象的內存地址,直接使用FramebufferNativeWindow的this指針不行嗎?這個問題我還沒有確定真正的原因是什麼,一個猜測是爲了兼容各種平臺的需求。大家應該注意到了ANativeWindow是一個Struct數據類型,在C語言中Struct是沒有成員函數的,所以我們通常是用函數指針的形式來模擬一個成員函數,比如這個dequeueBuffer在ANativeWindow的定義就是一個函數指針。而且我們沒有辦法確定最終填充到ANativeWindow中函數指針的實現是否有this指針,所以在參數中帶入一個window變量就是必要的了。

Step2@ FramebufferNativeWindow::dequeueBuffer,獲得一個Mutex鎖。因爲接下來的操作涉及到互斥區,自然需要有一個保護措施。這裏採用的是Autolock,意味着dequeueBuffer函數結束後會自動釋放Mutex。

Step3@ FramebufferNativeWindow::dequeueBuffer,前面我們介紹過mBufferHead變量,這裏來看下對它的實際使用。首先index得到的是mBufferHead所代表的當前位置,然後mBufferHead增加1。由於我們是循環利用兩個緩衝區的,所以如果這個變量的值超過mNumBuffers,那麼就需要把它置0。也就是說在這個場景下mBufferHead的值永遠只能是0或者1。

Step4@ FramebufferNativeWindow::dequeueBuffer,mBufferHead並不代表它所指向的緩衝區是可用的。假如當前的mNumFreeBuffers表明已經沒有多餘的緩衝區空間,那麼我們就需要等待有人釋放buffer。這裏使用到了Condition這一同步機制,如果有不清楚的請參考本書進程章節的詳細描述。可以肯定的是這裏調用了mCondition.wait,那麼必然有其它地方要喚醒它——具體的就是在queueBuffer()中,大家可以自己驗證下是否如此。

Step5@ FramebufferNativeWindow::dequeueBuffer,一旦成功獲得buffer後,要把可用的buffer計數減1(mNumFreeBuffers--),因爲mBufferHead前面已經自增過了,這裏就不用再特別處理。

這樣子我們就完成了對Android系統中本地窗口FramebufferNativeWindow的分析,接下來就講解另一個重要的Native Window。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章