Android圖形框架

1. Android圖像框架簡介

Android framework 有一系列與硬件抽象實現和圖形驅動相關的2d和3d渲染api,所以重要的是如何對這些運行在更高層次的api有一個良好的理解。應用開發者有兩種通用的方式可以繪製物體到屏幕,分別是canvas和opengl。

android.graphics.Canvas是一組被開發都廣泛應用的2d圖形api。在Android中,所有在android.view.Views和客戶自定義android.view.Views上的繪製操作都是由Canvas完成的。在Android3.0之前,Canvas一直使用沒有硬件加速的Skia 2D圖形庫繪製。

在Android 3.0引入了,爲了方便一些canvas變換操作可以在gpu運行,Canvas APIs使用了一個叫做OpenGLRender的硬件加速庫。之前開發者必須選擇此功能,但是從android 4.0開始,硬件加速的canvas被默認使能。因此,在android4.0中的gpu必須支持opengles 2.0。
此外,硬件加速指導手冊中解釋瞭如何使用硬件加速繪製的方式和與軟件繪製方式的差別。

另外的一個主要方式是,開發者使用OpenGL ES1.x或者2.0直接渲染到surface上。開發者可以透過Android SDK提供android.opengl包使用OpenGL ES接口,或者使用Android NDK提供的native APIs.

注意:
第三種選擇是在Android 3.0中被引入的Renderscript,Renderscript是平臺無關的圖形API(使用OpenGL ES 2.0的包裝),但將會在Android 4.1發佈後被棄用。

2. Android如何渲染圖形

開發者無論什麼樣的渲染api,每一個被渲染的像素數據buffer被叫做一個”surface”. Android platform創建的每一個窗口都是surface所支撐的。被渲染的所有可見的surfaces都被surfaceflinger(Android管理surfaces合併的系統服務)合併到顯示器。自然,渲染過程還有其他組件的參與,其中主要組件會在下面進行描述。

Image Stream Producers
諸如OpenGL ES遊戲,來自media server的視頻buffer,普通的Canvas 2D應用,或者任何能生產圖像buffer的程序,都可以看做是圖像流生產者。

Image Stream Consumes
最常見的消費Image Stream的消費者是SurfaceFlinger,該系統服務消費當前可見的surfaces並且利用window manager提供的信息將他們合併到顯示器上。SurfaceFlinger是唯一可以修改顯示內容的服務。SurfaceFlinger使用OpenGL和hardware composer來合併一組surfaces。其他的OpenGL ES應用也能消費image streams,例如camera應用可以消費camera 預覽圖像數據。

SurfaceTexture
SurfaceTexture在邏輯上綁定了image stream的生產者和消費者,主要由SurfaceTextureClientISurfaceTextureSurfaceTexture(在這裏,SurfaceTexture是一個實際的C++類,並不上面所提到的組件)三部分組成。生產者(SurfaceTextureClient),Binder(ISurfaceTexture)和消費者(SurfaceTexture)模型爲SurfaceTexture組件提供諸如從Gralloc中申請內存,跨進程的共享內存,同步訪問緩衝區,配對適當的生產者消費者的操作接口。SurfaceTexture既可以工作在異步模式(生產者不阻塞等待消費者並且丟幀)也可以工作在同步模式(生產者等待消費者處理texture)。一些圖像生產者的例子是camera HAL生成camera預覽數據或者OpenGL ES遊戲。一些消費者的例子是SurfaceFlinger或者其他一些需要顯示OpenGL ES流的應用。例如camera應用顯示camera取景。

Window Manager
Window Manager是一個管理着窗口(一些views的容器)的生命週期,輸入,焦點事件,屏幕方向,過渡,動畫,位置,變換,z-order和其他的系統服務。窗口總是由surface所支撐。WindowManager傳遞所有的窗口metadata給SurfaceFlinger,以便於SurfaceFlinger使用這些數據計算出在顯示器上如何合併surfaces.

Hardware Composer
顯示子系統的硬件抽象。Surfaceflinger可以從OpenGL和GPU分擔一些合併工作給hardware composer。這樣比surfaceflinger做全部的工作要來的更快。從Jellybean MR1開始,新版本的hardware composer被引進。更詳細的信息可以參考hardware/libhardware/include/hardware/gralloc.h的Hardware composer節。

Gralloc
負責分配graphics緩衝的內存。如果你使用1.1版本及更新版本的hardware composer,這個HAL已經不再需要了。

下圖展示了這些組件如何協同工作。

在這裏插入圖片描述

哪些是你需要提供的

在你的產品中支持android圖形你應該提供下列的節的描述
OpenGL ES 1.x Driver
OpenGL ES 2.0 Driver
EGL Driver
Gralloc HAL implementation
Hardware Composer HAL implementation
Framebuffer HAL implementation
OpenGL 和EGL驅動

你必須提供OpenGL ES 1.x,OpenGL ES 2.0和EGL的驅動。提醒一下其中一些關鍵點:

  • GL的驅動應該是穩健且符合OpenGL ES標準。
  • 不要限制GL contexts的個數。因爲Android允許應用在後臺運行,並且使GL contexts存活。在你的驅動裏,你不應該限制contexts的個數。雖然一次有20-30個激活的GL contexts是罕見的,但是你同樣應該注意每一個context的內存申請。
  • 支持YV12圖像格式和系統其他組件的YUV圖像格式,比如media codecs與camera.
  • 支持強制擴展:GL_OES_texture_external, EGL_ANDROID_image_native_buffer和EGL_ANDROID_recordable。我們也強烈推薦支持EGL_ANDROID_blob_cache和EGL_KHR_fance_sync。

注意:
暴露給應用開發者的OpenGL API不同於你實現的OpenGL接口。應用不應該訪問GL驅動層,而必須通過APIs提供的接口。

預懸旋

多數時候硬件覆蓋不支持懸旋,其中的一個解決方案是預先變換buffer在到達surfaceflinger之前。一個查詢提示在ANativeWindow中被添加(NATIVE_WINDOW_TRANSFORM_HINT)代表了可能了轉換被SurfaceFlinger被應用到緩衝區。你的GL驅動可以使用這個提示去預懸旋在緩衝區到達surfaceflinger之前,當buffer正常到達surfaceflinger時,他被正確變換了。

ANativeWindow接口定義的更多詳細信息在system/core/include/system/window.h中。下面一些僞碼在hardware composer中實現。

ANativeWindow->query(ANativeWindow, NATIVE_WINDOW_DEFAULT_WIDTH, &w);
ANativeWindow->query(ANativeWindow, NATIVE_WINDOW_DEFAULT_HEIGHT, &h);
ANativeWindow->query(ANativeWindow, NATIVE_WINDOW_TRANSFORM_HINT, &hintTransform);
if (hintTransform & HAL_TRANSFORM_ROT_90)
swap(w, h);

native_window_set_buffers_dimensions(anw, w, h);
ANativeWindow->dequeueBuffer(...);

// here GL driver renders content transformed by " hintTransform "

int inverseTransform;
inverseTransform = hintTransform;
if (hintTransform & HAL_TRANSFORM_ROT_90)
   inverseTransform ^= HAL_TRANSFORM_ROT_180;

native_window_set_buffers_transform(anw, inverseTransform);

ANativeWindow->queueBuffer(...);

Gralloc HAL

Image producers中的SurfaceTextureClient中需要的顯存分配器。你可以在 hardware/libhardware/modules/gralloc.h中找到關於這個HAL的一個空實現。

Hardware Composer HAL

SurfaceFlinger使用hardware composer合併surfaces到屏幕。Hardware composer抽象諸如overlays和2d blitters的硬件,並且幫助分擔一些一般會用OpenGL的操作。
JB MR1引入了一個新版本的HAL。我們建議使用1.1版本,這個版本會提供最新的特色(顯式同步,額外的顯示器等)。請記住除了1.1版本,由於兼容性的原因我們還有1.0版本。並且1.2草案版本。我們建議你實現1.1版本直到1.2脫離草案模式。
由於hardware composer後面的物理顯示器因設備而異,所以很難定義推薦功能,但是這裏有一些指導。

  • Hardware composer手機上應該支持至少4個overlays(狀態欄,系統通知欄,應用,和壁紙),平板上至少支持除狀態欄的的3個overlays
  • 圖層應該比屏幕大,以便於hardware composer應該能處理比顯示器大的圖層(比如:牆紙)
  • 同時支持多點和平面alpha blending操作。
  • Hardware composer應該能夠消費由GPU,camera,video decoder, skia buffers生產的緩衝區,所以支持下列的圖像屬性是有用的:
    RGBA打包格式
    YUV格式
    平鋪,混合,跨越屬性。
  • 硬件方式保護視頻播放必須支持如果你想支持受保護的內容。

當實現你的hardware composer時,通用的建議是先實現一個空操作。一旦你的框架完成,實現一個簡單的算法來抽象hardware composer。例如,開始僅僅使用頭三個或四個surfaces爲代表。以後專注於更普適的應用。例如:

  • 橫屏和縱屏的全屏遊戲
  • 關閉字幕與播放控制條的全屏的視頻
  • 主頁面(合併狀態欄,系統欄,應用程序和牆紙)
  • 多顯示器支持
    實現更普適的應用之後,你應該專注於優化諸如智能選擇surfaces送到overlay硬件,以便減小gpu負載。另外的優化方式是檢測屏幕是否更新。如果不更新,使用OpenGL合併替換hardware composer以節電。當屏幕重新更新,繼續使用hardware composer減少合併的負載。
    你可以找到hardware composer的HAL模塊在
    hardware/libhardware/include/hardware/hwcomposer.h
    hardware/libhardware/include/hardware/hwcomposer_defs.h 文件.
    一個demo實現在 hardware/libhardware/modules/hwcomposer目錄是可用的。

VSYNC

VSYNC是同步更新顯示器的某些事件。應用開始繪製與Surfaceflinger合併都是VSYNC觸發的。
這樣就消除了圖像抖動和提高了圖像質量。Hardware composer有一個函數指針。

 int (waitForVsync *)(int64_t *timestamp);

這個指針指向一個你必須爲VSYNC實現的函數。這個函數阻塞直到VSYNC信號發出,並且返回一個真實VSYNC信號的時間戳。客戶端可以在特定的時間一次接收一個時間戳,也可以間隔時間爲1這麼收。你實現的VSYNC最大延時爲1ms(推薦1/2ms或更短),並且返回的時間戳必須非常精準。

顯式同步

顯式同步被需要在JB MR1及以後版本,並且需要提供同步申請與釋放gralloc buffers的機制。顯式同步允許graphics buffers的生產者和消費者發一個信號當他們處理完一塊buffer時。這使得android系統異步隊列緩衝區讀取或寫入另外一個消費者或生產者的確定性目前並不需要它們。

這種通信機制爲了便於使用同步柵欄,現在請求時需要消耗或者生產的緩衝區。同步框架主要包括三個部分:

sync_timeline: 一個單調減少的時間線,驅動應該單例實現。主要是特定硬件提交給內核的計數器。

sync_pt: 一個單獨的值或指針在sync_timeline.指針有三種狀態,激活,觸發和錯誤。指針在激活態開始發觸發態發送信號或者錯誤態。例如:當緩衝區不被消費者需要,sync_point被觸發以便於image生產者知道可以重新寫入buffer。

sync_fence: sync_pts的集合,通常有不同的sync_timeline雙親(例如顯示控制器與GPU)。允許多個客戶者與消費者同時觸發緩衝區的使用,並且他們可以通過一個函數參數通信。Fences是一個可以從內核空間傳遞到用戶空間的文件描述符。舉例來說,一個fence可以包含兩個sync_points,那意味着兩個image消費者可以同時讀取一段buffer.當fence被觸發,圖像生產者知道所以有的消費者已經處理完消費。

爲了實現顯式同步,你需要提供如下資源

  • 爲特定硬件實現同步時間線的內核空間驅動。驅動提供fence-aware通用的訪問與通信機制爲hardware composer。更多細節可以看 system/core/include/sync/sync.h文件,system/core/libsync 目錄包含了一個與內核空間通信的庫。
  • Hardware composer HAL模塊(1.1版本及以後)支持新的同步函數。你可能會需要提供適當的同步參數給HAL中的set()prepare()函數。最後一個,你可以傳遞-1給文件描述符如果你不能支持顯式同步由於一些原因。儘管這並不被推薦。
    兩個GL特別的擴展與fences相關, EGL_ANDROID_native_fence_syncEGL_ANDROID_wait_sync,將fences支持加入到你的圖形驅動中。

3. 顯示設備

這裏所說的顯示設備並不一定是指手機屏幕/顯示器這種物理設備。

但是爲什麼不把顯示設備寫死成顯示器,這樣實現起來多簡單啊。事實上真實的世界很複雜,我們沒辦法用一種實現包打天下。比如,我們顯示在屏幕上繪製了一張圖片,看着很不錯,想用打印機輸出;又或者,我有一個GTK+寫得程序,我想同時在win32和x11都能運行;我有一個FTK程序,想在iphone上運行。怎麼辦?重做一遍?顯然不合理。

實際上,我們可以這樣理解這個需求:無論我們如何繪製,都是繪製到一塊內存,不用關心這塊內存由誰提供

我們可以要求fb/gpu/printer/pdf/image/等等來提供這塊內存。我們把數據給這塊內存,然後物理設備/軟件來處理。所以GTK+能在win32上運行,也能在X11上運行,也能FB上運行。
GDI+抽象了HDC,Skia抽象了Skdevice,FTK抽象了FtkDisplay,

具體做法C++用純虛函數做爲接口,C語言規定函數指針,(實際原理還是一樣的)然後具體物理設備/軟件來實現。其他圖形庫,想必也有類似的做法。
基本接口一般包括,全屏刷新/局部刷新。

4. Canvas的實現

上面說到了顯示設備給了我一塊內存,但是我如何操作他呢?我們需要一個Canvas。
Canvas的基本需求很明確,實現畫點,畫線,畫圖像的功能。
我們來看真實的世界:
畫點,沒啥,在屏幕選一個座標,然後根據畫筆的顏色,點亮一個點。
畫線,貌似有點麻煩。標準的橫豎問題不大,從開始座標到結束座標,根據畫筆的屬性(虛實/顏色。。。)一個點一個點的畫。斜線呢?開始用到了一些微分方程了,呵呵。

畫圖片,比較麻煩。圖像有不同的格式,bmp還好辦,大部分都是原始圖像,jpeg/png/gif/h264都是壓縮過的數據,各不相同,怎麼辦?我們需要一個BitmapFactory,把圖片文件放進去,出來就是合乎我們自己定義的Bitmap的數據塊。基本實現原理依然是接口抽象,具體格式具體實現。

5. 事件的接收與派發

事件驅動模型,是很常見的GUI庫模型。
如何讓控件響應消息,自定義的一些消息如何派發與響應。

6.基本控件的實現,基類/派生類

有了抽象設備,有了canvas,如何實現一些基本控件呢?
控件是啥,一個簡直直接的描述就是,繪圖和事件響應的具體對象(這個對象不是面向對象的對象)。
首先依舊是設計基類,所有的控件都要做的事,包括繪圖和事件響應。這樣一樣,我們就可以抽象基類了,所公共的屬性放到一個類裏面,大家都來繼承他,這樣就減少了重複代碼,提高了代碼複用度,也會少很多bug。

我們來看看現實的世界如何做的。
Ftk的控件的基類是FtkWidget,裏面有兩個接口值得注意,
FtkWidgetOnEvent
FtkWidgetOnPaint
一個響應繪圖,一個響應事件,和我們上文所一致。

Skia

不那麼直觀,但是依然符合我們所說的規律。控件的基類是SkView。
首先這個類繼承了SkEventSink,這個類就是事件響應的抽象的基類,規定了事件響應的虛函數virtual bool onEvent(const SkEvent&);。另外SkView裏面有個虛函數規定了繪圖了虛函數virtual void onDraw(SkCanvas*);

MFC是很不現代的圖形庫,找不到規律。。。。。

上面我們找到了控件類要實現的接口,具體實現就是一個一一對應的關係了。比如Button,ListView,TextView這種控件,他們一定要實現的兩個接口就是Ondraw和onevent。

如果標準控件庫裏面沒有我們需要的控件,要自定義一個控件的話,如何做也很明顯了,直接繼承widget/view這種基類,實現要求的虛函數,繪製自己想要的形態。

7. 窗口管理

窗口管理是圖形庫最重要的功能。

我們不討論windows這種複雜的窗口管理機制(多窗口,一個時間點,有多個窗口存在)。只針對嵌入式gui這種,當前屏幕上,一個時間點,只有一個窗口的模式,又叫單窗口模式。

什麼叫窗口,一個直接與用戶交互的界面,就叫窗口。
單一一個控件,沒有任何意義,我們必須配合上一系列控件,放到一起,才能實現直接與用戶交互。

一個消息(按鍵消息,觸摸消息),只會派發給窗口,然後有窗口來找到當前焦點的控件,並激活該控件的事件響應函數,從而做出響應。

所謂窗口管理,就是窗口如何層疊分佈的,比如我按了返回鍵,當前窗口銷燬,應該顯示哪個窗口呢?這個事是由窗口管理器決定的,一種比較簡單的實現就是棧式窗口管理,就像盤子一樣,一個一個疊上去,取得時候一個一個取下來(不考慮一堆一堆這麼幹的情況)。

8. 其他

動畫/輸入法/2d加速/gpu加速/特效實現

9. 實踐案例

抓取第三方通話應用的視頻幀給本地通話進行視頻混合

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