文章都是通過閱讀源碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正
轉載請註明: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。